diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml b/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml index 24fa399b2..193678ec8 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml @@ -1819,6 +1819,14 @@ name="uimItemTextEdit" handler="item_text_edition" params="ui:interface:edit_custom" /> + + + + + listGroup(); + return true; +} + +NLMISC_COMMAND(equipGroup, "equip group ", "name") +{ + CInterfaceManager *pIM = CInterfaceManager::getInstance(); + + if(args.empty()) + { + pIM->displaySystemInfo(CI18N::get("cmdEquipGroupUsage1")); + pIM->displaySystemInfo(CI18N::get("cmdEquipGroupUsage2")); + return false; + } + if(CItemGroupManager::getInstance()->equipGroup(args[0])) + { + ucstring msg = CI18N::get("cmdEquipGroupSuccess"); + strFindReplace(msg, "%name", args[0]); + pIM->displaySystemInfo(msg); + return true; + } + else + { + ucstring msg = CI18N::get("cmdEquipGroupError"); + strFindReplace(msg, "%name", args[0]); + pIM->displaySystemInfo(msg); + return false; + } +} + +NLMISC_COMMAND(moveGroup, "move group to ", "name dst") +{ + CInterfaceManager *pIM = CInterfaceManager::getInstance(); + + if(args.empty() || args.size() < 2) + { + pIM->displaySystemInfo(CI18N::get("cmdMoveGroupUsage1")); + pIM->displaySystemInfo(CI18N::get("cmdMoveGroupUsage2")); + pIM->displaySystemInfo(CI18N::get("cmdMoveGroupUsage3")); + return false; + } + + if(CItemGroupManager::getInstance()->moveGroup(args[0], INVENTORIES::toInventory(args[1]))) + { + ucstring msg = CI18N::get("cmdMoveGroupSuccess"); + strFindReplace(msg, "%name", args[0]); + strFindReplace(msg, "%inventory", args[1]); + pIM->displaySystemInfo(msg); + return true; + } + else + { + ucstring msg = CI18N::get("cmdMoveGroupError"); + strFindReplace(msg, "%name", args[0]); + strFindReplace(msg, "%inventory", args[1]); + pIM->displaySystemInfo(msg); + return false; + } +} + + +NLMISC_COMMAND(createGroup, "create group [true](create a for every unequiped item)", "name [removeUnequiped]") +{ + CInterfaceManager *pIM = CInterfaceManager::getInstance(); + if(args.empty()) + { + pIM->displaySystemInfo(CI18N::get("cmdCreateGroupUsage1")); + pIM->displaySystemInfo(CI18N::get("cmdCreateGroupUsage2")); + pIM->displaySystemInfo(CI18N::get("cmdCreateGroupUsage3")); + return false; + } + bool removeUnequiped = false; + if(args.size() > 1) + removeUnequiped = !args[1].empty(); + if(CItemGroupManager::getInstance()->createGroup(args[0], removeUnequiped)) + { + ucstring msg; + if(removeUnequiped) + msg = CI18N::get("cmdCreateGroupSuccess2"); + else + msg = CI18N::get("cmdCreateGroupSuccess1"); + strFindReplace(msg, "%name", args[0]); + pIM->displaySystemInfo(msg); + return true; + } + else + { + ucstring msg = CI18N::get("cmdCreateGroupError"); + strFindReplace(msg, "%name", args[0]); + pIM->displaySystemInfo(msg); + return false; + } + +} + + + +NLMISC_COMMAND(deleteGroup, "delete group ", "name") +{ + CInterfaceManager *pIM = CInterfaceManager::getInstance(); + if(args.empty()) + { + pIM->displaySystemInfo(CI18N::get("cmdDeleteGroupUsage1")); + pIM->displaySystemInfo(CI18N::get("cmdDeleteGroupUsage2")); + return false; + } + if(CItemGroupManager::getInstance()->deleteGroup(args[0])) + { + ucstring msg = CI18N::get("cmdDeleteGroupSuccess"); + strFindReplace(msg, "%name", args[0]); + pIM->displaySystemInfo(msg); + return true; + } + else + { + ucstring msg = CI18N::get("cmdDeleteGroupError"); + strFindReplace(msg, "%name", args[0]); + pIM->displaySystemInfo(msg); + return false; + } +} + +NLMISC_COMMAND(naked, "get naked !", "") +{ + std::string handPath = "LOCAL:INVENTORY:HAND:"; + std::string equipPath = "LOCAL:INVENTORY:EQUIP:"; + uint32 i; + for (i = 0; i < MAX_HANDINV_ENTRIES; ++i) + { + CInventoryManager::getInstance()->unequip(handPath + NLMISC::toString(i)); + } + + + for (i = 0; i < MAX_EQUIPINV_ENTRIES; ++i) + { + CInventoryManager::getInstance()->unequip(equipPath + NLMISC::toString(i)); + + } + return true; +} + NLMISC_COMMAND(afk, "Set the player as 'away from keyboard'","[]") { string customText; diff --git a/code/ryzom/client/src/events_listener.cpp b/code/ryzom/client/src/events_listener.cpp index 028083b29..969c0191a 100644 --- a/code/ryzom/client/src/events_listener.cpp +++ b/code/ryzom/client/src/events_listener.cpp @@ -29,7 +29,7 @@ #include "input.h" #include "interface_v3/interface_manager.h" #include "global.h" - +#include "item_group_manager.h" using namespace NLMISC; @@ -131,6 +131,8 @@ void CEventsListener::operator()(const CEvent& event) { // Interface saving CInterfaceManager::getInstance()->uninitInGame0(); + CItemGroupManager::getInstance()->uninit(); + /* YOYO: quitting safely sometimes crash in CContinentMngr::select() diff --git a/code/ryzom/client/src/far_tp.cpp b/code/ryzom/client/src/far_tp.cpp index 689b1d7cb..b5a0b19d1 100644 --- a/code/ryzom/client/src/far_tp.cpp +++ b/code/ryzom/client/src/far_tp.cpp @@ -44,7 +44,7 @@ #include "bg_downloader_access.h" #include "login_progress_post_thread.h" #include "interface_v3/action_handler_base.h" - +#include "item_group_manager.h" using namespace NLMISC; using namespace NLNET; using namespace NL3D; @@ -1248,6 +1248,8 @@ void CFarTP::sendReady() // Instead of doing it in disconnectFromPreviousShard(), we do it here, only when it's needed ClientCfg.R2EDEnabled = ! ClientCfg.R2EDEnabled; pIM->uninitInGame0(); + CItemGroupManager::getInstance()->uninit(); + ClientCfg.R2EDEnabled = ! ClientCfg.R2EDEnabled; ActionsContext.removeAllCombos(); diff --git a/code/ryzom/client/src/init_main_loop.cpp b/code/ryzom/client/src/init_main_loop.cpp index c3f219278..ced1c85a6 100644 --- a/code/ryzom/client/src/init_main_loop.cpp +++ b/code/ryzom/client/src/init_main_loop.cpp @@ -85,7 +85,7 @@ #include "teleport.h" #include "movie_shooter.h" #include "interface_v3/input_handler_manager.h" - +#include "item_group_manager.h" #include "time_client.h" #include "auto_anim.h" #include "release.h" @@ -692,7 +692,7 @@ void initMainLoop() ProgressBar.newMessage ( ClientCfg.buildLoadingString(nmsg) ); //nlinfo("****** InGame Interface Parsing and Init START ******"); pIM->initInGame(); // must be called after waitForUserCharReceived() because Ring information is used by initInGame() - + CItemGroupManager::getInstance()->init(); // Init at the same time keys.xml is loaded initLast = initCurrent; initCurrent = ryzomGetLocalTime(); //nlinfo ("PROFILE: %d seconds (%d total) for Initializing ingame", (uint32)(initCurrent-initLast)/1000, (uint32)(initCurrent-initStart)/1000); diff --git a/code/ryzom/client/src/interface_v3/action_handler_item.cpp b/code/ryzom/client/src/interface_v3/action_handler_item.cpp index f3192a9ee..371316882 100644 --- a/code/ryzom/client/src/interface_v3/action_handler_item.cpp +++ b/code/ryzom/client/src/interface_v3/action_handler_item.cpp @@ -43,7 +43,7 @@ #include "nel/gui/ctrl_base_button.h" #include "../connection.h" #include "nel/gui/view_bitmap.h" - +#include "../item_group_manager.h" extern CSheetManager SheetMngr; extern NLMISC::CLog g_log; @@ -2014,6 +2014,77 @@ class CHandlerItemMenuCheck : public IActionHandler } } + //Item GROUP logic + CGroupMenu *pGroupRootMenu = dynamic_cast(CWidgetManager::getInstance()->getElementFromId("ui:interface:item_menu_in_bag:item_group_menu")); + if(pGroupRootMenu) + { + CGroupSubMenu *pGroupMenu = pGroupRootMenu->getRootMenu(); + std::vector groupNames = CItemGroupManager::getInstance()->getGroupNames(pCS); + CViewText *pGroup = dynamic_cast(pMenu->getView("item_group")); + //First, hide/show the menu if pertinent (we need to hide the action due to it beeing a submenu) + if(pGroup) + { + if(groupNames.empty()) + { + pGroup->setActive(false); + } + else + { + pGroup->setActive(true); + } + } + //Reset everything and recreate the submenu for current item + // We do it the lazy way : active/gray options matching regular options (when you do things on a single item) + // Same for translated name of interface + pGroupMenu->reset(); + for(i=0; iaddLine(ucstring(name), "", "", name); + CGroupSubMenu* pNewSubMenu = new CGroupSubMenu(CViewBase::TCtorParam()); + pGroupMenu->setSubMenu(pGroupMenu->getNumLine()-1, pNewSubMenu); + if(pNewSubMenu) + { + if(pEquip) + pNewSubMenu->addLine(pEquip->getHardText(), "item_group_equip", ahParams, name + "_equip"); + // Add move sub menu + if (pMoveSubMenu) + { + pNewSubMenu->addLine(pMoveSubMenu->getHardText(), "", "", name + "_move"); + CGroupSubMenu* tmp = new CGroupSubMenu(CViewBase::TCtorParam()); + pNewSubMenu->setSubMenu(pNewSubMenu->getNumLine() - 1, tmp); + pNewSubMenu = tmp; + } + if(pMoveToBag && pMoveToBag->getActive()) + { + CViewTextMenu* tmp = pNewSubMenu->addLine(pMoveToBag->getHardText(),"item_group_move", "destination=bag|" + ahParams, name + "_bag"); + if(tmp) tmp->setGrayed(pMoveToBag->getGrayed()); + } + for(int j=0;j< MAX_INVENTORY_ANIMAL; j++) + { + if(pMoveToPa[j] && pMoveToPa[j]->getActive()) + { + //there is an offset of 1 because TInventory names are pet_animal1/2/3/4 + std::string dst = toString("destination=pet_animal%d|", j + 1); + CViewTextMenu* tmp = pNewSubMenu->addLine(ucstring(pMoveToPa[j]->getHardText()),"item_group_move", dst + ahParams, name + toString("_pa%d", j + 1)); + if(tmp) tmp->setGrayed(pMoveToPa[j]->getGrayed()); + } + } + if(pMoveToRoom && pMoveToRoom->getActive()) + { + CViewTextMenu* tmp = pNewSubMenu->addLine(pMoveToRoom->getHardText(), "item_group_move", "destination=player_room|" + ahParams, name + "_room"); + if(tmp) tmp->setGrayed(pMoveToRoom->getGrayed()); + } + if(pMoveToGuild && pMoveToGuild->getActive() && ClientCfg.ItemGroupAllowGuild) + { + CViewTextMenu* tmp = pNewSubMenu->addLine(pMoveToGuild->getHardText(),"item_group_move", "destination=guild|" + ahParams, name + "_guild"); + if(tmp) tmp->setGrayed(pMoveToRoom->getGrayed()); + } + } + } + + } } }; REGISTER_ACTION_HANDLER( CHandlerItemMenuCheck, "item_menu_check" ); @@ -2247,4 +2318,51 @@ class CHandlerRingXpCatalyserStopUse : public IActionHandler REGISTER_ACTION_HANDLER( CHandlerRingXpCatalyserStopUse, "ring_xp_catalyser_stop_use" ); +// *************************************************************************** +// item groups +class CHandlerItemGroupMove : public IActionHandler +{ + void execute (CCtrlBase *caller, const std::string &sParams) + { + CDBCtrlSheet* pCS = dynamic_cast(caller); + if(!pCS) + { + nlinfo("Wrong cast"); + return; + } + std::string destination = getParam(sParams, "destination"); + std::string name = getParam(sParams, "name"); + if(name.empty()) + { + nlinfo("Trying to move a group with a caller not part of any group"); + return; + } + CItemGroupManager::getInstance()->moveGroup(name, INVENTORIES::toInventory(destination)); + } +}; +REGISTER_ACTION_HANDLER(CHandlerItemGroupMove, "item_group_move"); + + +// *************************************************************************** +class CHandlerItemGroupEquip : public IActionHandler +{ + void execute (CCtrlBase *caller, const std::string & sParams) + { + CDBCtrlSheet* pCS = dynamic_cast(caller); + if(!pCS) + { + nlinfo("Wrong cast"); + return; + } + std::string name = getParam(sParams, "name"); + if(name.empty()) + { + nlinfo("Trying to move a group with a caller not part of any group"); + return; + } + + CItemGroupManager::getInstance()->equipGroup(name); + } +}; +REGISTER_ACTION_HANDLER(CHandlerItemGroupEquip, "item_group_equip"); diff --git a/code/ryzom/client/src/interface_v3/interface_manager.cpp b/code/ryzom/client/src/interface_v3/interface_manager.cpp index 413b8bcc5..d939df144 100644 --- a/code/ryzom/client/src/interface_v3/interface_manager.cpp +++ b/code/ryzom/client/src/interface_v3/interface_manager.cpp @@ -130,6 +130,7 @@ using namespace NLGUI; #include "../global.h" #include "user_agent.h" +#include "../item_group_manager.h" using namespace NLMISC; @@ -1542,6 +1543,8 @@ void CInterfaceManager::updateFrameEvents() CBGDownloaderAccess::getInstance().update(); + CItemGroupManager::getInstance()->update(); + } // ------------------------------------------------------------------------------------------------ diff --git a/code/ryzom/client/src/item_group_manager.cpp b/code/ryzom/client/src/item_group_manager.cpp new file mode 100644 index 000000000..14ea4aceb --- /dev/null +++ b/code/ryzom/client/src/item_group_manager.cpp @@ -0,0 +1,627 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +#include "item_group_manager.h" +#include "interface_v3/inventory_manager.h" +#include "nel/gui/widget_manager.h" +#include "nel/misc/sheet_id.h" +#include "nel/misc/stream.h" +#include "nel/misc/o_xml.h" +#include "nel/misc/i_xml.h" +#include "nel/misc/file.h" +#include "libxml/tree.h" +#include "game_share/item_type.h" +#include "client_sheets/item_sheet.h" +#include "net_manager.h" +#include "connection.h" // Used to access PlayerSelectedFileName for xml filename +#include "nel/gui/db_manager.h" +#include "interface_v3/interface_manager.h" +#include "nel/gui/group_menu.h" +#include "nel/misc/i18n.h" +#include "nel/misc/algo.h" +CItemGroupManager *CItemGroupManager::_Instance = NULL; + +CItemGroup::CItemGroup() +{ +} + +bool CItemGroup::contains(CDBCtrlSheet *other) +{ + SLOT_EQUIPMENT::TSlotEquipment slot = SLOT_EQUIPMENT::UNDEFINED; + return contains(other, slot); +} +bool CItemGroup::contains(CDBCtrlSheet *other, SLOT_EQUIPMENT::TSlotEquipment &slot) +{ + slot = SLOT_EQUIPMENT::UNDEFINED; + for(int i=0;igetSheetId()); + if (sheet.toString() == item.sheetName && other->getQuality() == item.quality && + other->getItemWeight() == item.weight && other->getItemColor() == item.color && + (!item.usePrice || (other->getItemPrice() >= item.minPrice && other->getItemPrice() <= item.maxPrice)) + ) + { + slot = item.slot; + return true; + } + } + + return false; +} + +void CItemGroup::addItem(std::string sheetName, uint16 quality, uint32 weight, uint8 color, SLOT_EQUIPMENT::TSlotEquipment slot) +{ + Items.push_back(CItem(sheetName, quality, weight, color, slot)); +} + +void CItemGroup::addRemove(std::string slotName) +{ + SLOT_EQUIPMENT::TSlotEquipment slot = SLOT_EQUIPMENT::stringToSlotEquipment(NLMISC::toUpper(slotName)); + if(slot) + removeBeforeEquip.push_back(slot); +} + +void CItemGroup::addRemove(SLOT_EQUIPMENT::TSlotEquipment slot) +{ + removeBeforeEquip.push_back(slot); +} + +void CItemGroup::writeTo(xmlNodePtr node) +{ + xmlNodePtr groupNode = xmlNewChild (node, NULL, (const xmlChar*)"group", NULL ); + xmlSetProp(groupNode, (const xmlChar*)"name", (const xmlChar*)name.c_str()); + for(int i=0;ichildren; + while(curNode) + { + if (strcmp((char*)curNode->name, "item") == 0) + { + + CItem item; + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"sheetName"); + if (ptrName) NLMISC::fromString((const char*)ptrName, item.sheetName); + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"quality"); + if (ptrName) NLMISC::fromString((const char*)ptrName, item.quality); + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"weight"); + if (ptrName) NLMISC::fromString((const char*)ptrName, item.weight); + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"color"); + if (ptrName) NLMISC::fromString((const char*)ptrName, item.color); + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"minPrice"); + if (ptrName) NLMISC::fromString((const char*)ptrName, item.minPrice); + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"maxPrice"); + if (ptrName) NLMISC::fromString((const char*)ptrName, item.maxPrice); + item.usePrice = (item.minPrice != 0 || item.maxPrice != std::numeric_limits::max()); + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"slot"); + std::string slot; + if (ptrName) NLMISC::fromString((const char*)ptrName, slot); + item.slot = SLOT_EQUIPMENT::stringToSlotEquipment(NLMISC::toUpper(slot)); + //Old version of groups.xml could save unknown sheets, remove them for clarity + if(item.sheetName != "unknown.unknown") + Items.push_back(item); + } + if (strcmp((char*)curNode->name, "remove") == 0) + { + std::string slot; + ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"slot"); + if (ptrName) NLMISC::fromString((const char*)ptrName, slot); + addRemove(slot); + } + + curNode = curNode->next; + } + +} + +CItemGroupManager::CItemGroupManager() +{ + _EndInvalidAction = 0; + _StartInvalidAction = 0; +} + +void CItemGroupManager::init() +{ + loadGroups(); + linkInterface(); +} + +void CItemGroupManager::linkInterface() +{ + //attach item group subgroup to right-click in bag group + CWidgetManager* pWM = CWidgetManager::getInstance(); + CGroupMenu *pRootMenu = dynamic_cast(pWM->getElementFromId("ui:interface:item_menu_in_bag")); + CGroupSubMenu *pMenu = pRootMenu->getRootMenu(); + //get item subgroup + CGroupMenu *pGroupMenu = dynamic_cast(pWM->getElementFromId("ui:interface:item_menu_in_bag:item_group_menu")); + CGroupSubMenu *pGroupSubMenu = NULL; + if (pGroupMenu) pGroupSubMenu = pGroupMenu->getRootMenu(); + if (pMenu && pGroupSubMenu) + pMenu->setSubMenu(pMenu->getNumLine() - 1, pGroupSubMenu); + else + nlwarning("Couldn't link group submenu to item_menu_in_bag, check your widgets.xml file"); +} + +void CItemGroupManager::uninit() +{ + saveGroups(); + unlinkInterface(); + _Groups.clear(); +} + +void CItemGroupManager::unlinkInterface() +{ + // We need to unlink our menu to avoid crash on interface release + CWidgetManager* pWM = CWidgetManager::getInstance(); + CGroupMenu *pGroupMenu = dynamic_cast(pWM->getElementFromId("ui:interface:item_menu_in_bag:item_group_menu")); + CGroupSubMenu *pGroupSubMenu = NULL; + if (pGroupMenu) pGroupSubMenu = pGroupMenu->getRootMenu(); + if (pGroupMenu) pGroupMenu->reset(); + if (pGroupMenu && pGroupSubMenu) pGroupMenu->delGroup(pGroupSubMenu, true); +} + +// Inspired from macro parsing +void CItemGroupManager::saveGroups() +{ + std::string userGroupFileName = "save/groups_" + PlayerSelectedFileName + ".xml"; + if(PlayerSelectedFileName.empty()) + { + nlwarning("Trying to save group with an empty PlayerSelectedFileName, aborting"); + return; + } + try { + NLMISC::COFile f; + if(f.open(userGroupFileName, false, false, true)) + { + + NLMISC::COXml xmlStream; + xmlStream.init(&f); + xmlDocPtr doc = xmlStream.getDocument (); + xmlNodePtr node = xmlNewDocNode(doc, NULL, (const xmlChar*)"item_groups", NULL); + xmlDocSetRootElement (doc, node); + for(int i=0;i<_Groups.size();i++) + { + CItemGroup group = _Groups[i]; + group.writeTo(node); + } + xmlStream.flush(); + f.close(); + } + else + { + nlwarning ("Can't open the file %s", userGroupFileName.c_str()); + + } + } + catch (const NLMISC::Exception &e) + { + nlwarning ("Error while writing the file %s : %s.", userGroupFileName.c_str(), e.what ()); + } +} + +bool CItemGroupManager::loadGroups() +{ + + std::string userGroupFileName = "save/groups_" + PlayerSelectedFileName + ".xml"; + if(PlayerSelectedFileName.empty()) + { + nlwarning("Trying to load group with an empty PlayerSelectedFileName, aborting"); + return false; + } + if (!NLMISC::CFile::fileExists(userGroupFileName) || NLMISC::CFile::getFileSize(userGroupFileName) == 0) + { + nlinfo("No item groups file found !"); + return false; + } + //Init loading + NLMISC::CIFile f; + f.open(userGroupFileName); + NLMISC::CIXml xmlStream; + xmlStream.init(f); + // Actual loading + xmlNodePtr globalEnclosing; + globalEnclosing = xmlStream.getRootNode(); + if(!globalEnclosing) + { + nlwarning("no root element in item_group xml, skipping xml parsing"); + return false; + } + if(strcmp(( (char*)globalEnclosing->name), "item_groups")) + { + nlwarning("wrong root element in item_group xml, skipping xml parsing"); + return false; + } + xmlNodePtr curNode = globalEnclosing->children; + while (curNode) + { + if (strcmp((char*)curNode->name, "group") == 0) + { + CItemGroup group; + group.readFrom(curNode); + _Groups.push_back(group); + } + curNode = curNode->next; + } + f.close(); + + return true; +} + +void CItemGroupManager::update() +{ + if(_StartInvalidAction != 0 && _StartInvalidAction <= NetMngr.getCurrentServerTick()) + { + invalidActions(_StartInvalidAction, _EndInvalidAction); + _StartInvalidAction = 0; + } + if(_EndInvalidAction != 0 && _EndInvalidAction <= NetMngr.getCurrentServerTick()) + { + _EndInvalidAction = 0; + validActions(); + } +} + +void CItemGroupManager::fakeInvalidActions(NLMISC::TGameCycle time) +{ + // We cannot directly ivnalidate action or our invalidate will be overriden by the server + // (and that means we won't actually have one because it's buggy with multiple equip in a short time) + // So we wait a bit (currently 6 ticks is enough) to do it + _StartInvalidAction = NetMngr.getCurrentServerTick() + 6; + _EndInvalidAction = NetMngr.getCurrentServerTick() + time; + invalidActions(NetMngr.getCurrentServerTick(), _EndInvalidAction); +} + +void CItemGroupManager::invalidActions(NLMISC::TGameCycle begin, NLMISC::TGameCycle end) +{ + NLGUI::CDBManager *pDB = NLGUI::CDBManager::getInstance(); + NLMISC::CCDBNodeLeaf *node; + // This are the db update server sends when an user equip an item, see egs/player_manager/gear_latency.cpp CGearLatency::setSlot + node = pDB->getDbProp("SERVER:USER:ACT_TSTART", false); + if (node) node->setValue64(begin); + + node = pDB->getDbProp("SERVER:USER:ACT_TEND", false); + if(node) node->setValue64(end); + + node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:SHEET", false); + static NLMISC::CSheetId equipSheet("big_equip_item.sbrick"); + if(node) node->setValue64((sint64)equipSheet.asInt()); + + node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:PHRASE", false); + if(node) node->setValue64(0); +} + +void CItemGroupManager::validActions() +{ + NLGUI::CDBManager *pDB = NLGUI::CDBManager::getInstance(); + NLMISC::CCDBNodeLeaf *node; + node = pDB->getDbProp("SERVER:USER:ACT_TSTART", false); + if (node) node->setValue64(0); + + node = pDB->getDbProp("SERVER:USER:ACT_TEND", false); + if(node) node->setValue64(0); + + node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:SHEET", false); + if(node) node->setValue32(0); + + node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:PHRASE", false); + if(node) node->setValue32(0); +} + +//move a group from all available inventory to dst +bool CItemGroupManager::moveGroup(std::string name, INVENTORIES::TInventory dst) +{ + CItemGroup* group = findGroup(name); + if(!group) + { + nlinfo("group %s not found", name.c_str()); + return false; + } + if(dst == INVENTORIES::UNDEFINED) + { + nlinfo("Destination inventory not found"); + return false; + } + CInventoryManager* pIM = CInventoryManager::getInstance(); + + std::string moveParams = "to=lists|nblist=1|listsheet0=" + toDbPath(dst); + // Grab all matching item from all available inventory and put it in dst + for (int i=0; i< INVENTORIES::TInventory::NUM_ALL_INVENTORY; i ++) + { + INVENTORIES::TInventory inventory = (INVENTORIES::TInventory)i; + if (inventory != dst && pIM->isInventoryAvailable(inventory)) + { + std::vector items = matchingItems(group, inventory); + for(int i=0;iisBagItemWeared(item.indexInBag)) continue; + CAHManager::getInstance()->runActionHandler("move_item", item.pCS, moveParams); + } + + } + } + return true; + +} + +bool CItemGroupManager::equipGroup(std::string name, bool pullBefore) +{ + CItemGroup* group = findGroup(name); + if(!group) + { + nlinfo("group %s not found", name.c_str()); + return false; + } + + if(pullBefore) moveGroup(name, INVENTORIES::TInventory::bag); + //Start by unequipping all slot that user wants to unequip + for(int i=0; i < group->removeBeforeEquip.size(); i++) + { + SLOT_EQUIPMENT::TSlotEquipment slot = group->removeBeforeEquip[i]; + std::string dbPath; + // For hands equip, dbPath obviously starts at 0, we need to offset correctly + if(slot == SLOT_EQUIPMENT::HANDL || slot == SLOT_EQUIPMENT::HANDR) + dbPath = "LOCAL:INVENTORY:HAND:" + NLMISC::toString((uint32)slot - SLOT_EQUIPMENT::HANDL); + else + dbPath = "LOCAL:INVENTORY:EQUIP:" + NLMISC::toString((uint32)slot); + CInventoryManager::getInstance()->unequip(dbPath); + } + + uint32 maxEquipTime = 0; + + std::map possiblyDual = + { + {ITEM_TYPE::ANKLET, false}, + {ITEM_TYPE::BRACELET, false}, + {ITEM_TYPE::EARING, false}, + {ITEM_TYPE::RING, false}, + {ITEM_TYPE::DAGGER, false}, + }; + std::vector duals; + std::vector items = matchingItems(group, INVENTORIES::TInventory::bag); + for(int i=0; i < items.size(); i++) + { + CInventoryItem item = items[i]; + ITEM_TYPE::TItemType itemType = item.pCS->asItemSheet()->ItemType; + // Special case for dagger (and all other items that can be equipped both right AND left hand, currently it's only dagger) + // We don't equip the one intended for left hand right away (it will be done in duals items later), let right hand be normally equipped + if(itemType == ITEM_TYPE::DAGGER && item.slot == SLOT_EQUIPMENT::HANDL) + { + duals.push_back(item); + continue; + } + + // If the item can be weared 2 times, don't automatically equip the second one + // Or else it will simply replace the first. We'll deal with them later + if(possiblyDual.find(itemType) != possiblyDual.end()) + { + if (possiblyDual[itemType]) + { + duals.push_back(item); + continue; + } + possiblyDual[itemType] = true; + } + + maxEquipTime = std::max(maxEquipTime, item.pCS->asItemSheet()->EquipTime); + CInventoryManager::getInstance()->autoEquip(item.indexInBag, true); + } + // Manually equip dual items + for(int i=0;i < duals.size();i++) + { + CInventoryItem item = duals[i]; + ITEM_TYPE::TItemType itemType = item.pCS->asItemSheet()->ItemType; + std::string dstPath = string(LOCAL_INVENTORY); + switch(itemType) + { + case ITEM_TYPE::ANKLET: + dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::ANKLER); break; + case ITEM_TYPE::BRACELET: + dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::WRISTR);; break; + case ITEM_TYPE::EARING: + dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::EARR);; break; + case ITEM_TYPE::RING: + dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::FINGERR);;break; + case ITEM_TYPE::DAGGER: + dstPath += ":HAND:1"; break; + default: + break; + } + + std::string srcPath = item.pCS->getSheet(); + maxEquipTime = std::max(maxEquipTime, item.pCS->asItemSheet()->EquipTime); + CInventoryManager::getInstance()->equip(srcPath, dstPath); + } + // For some reason, there is no (visual) invalidation (server still blocks any action), force one + // Unfortunately, there is no clean way to do this, so we'll simulate one + fakeInvalidActions((NLMISC::TGameCycle)maxEquipTime); + return true; + +} + +bool CItemGroupManager::createGroup(std::string name, bool removeUnequiped) +{ + if(findGroup(name)) return false; + CItemGroup group = CItemGroup(); + group.name = name; + uint i; + CDBCtrlSheet* pCS; + for (i = 0; i < MAX_EQUIPINV_ENTRIES; ++i) + { + SLOT_EQUIPMENT::TSlotEquipment slot = (SLOT_EQUIPMENT::TSlotEquipment)i; + //Instead of doing two separate for, just be a bit tricky for hand equipment + if(slot == SLOT_EQUIPMENT::HANDR || slot == SLOT_EQUIPMENT::HANDL) + pCS = CInventoryManager::getInstance()->getHandSheet((uint32)(slot - SLOT_EQUIPMENT::HANDL)); + else + pCS = CInventoryManager::getInstance()->getEquipSheet(i); + if(!pCS) continue; + if(pCS->isSheetValid()) + { + NLMISC::CSheetId sheet(pCS->getSheetId()); + group.addItem(sheet.toString(), pCS->getQuality(), pCS->getItemWeight(), pCS->getItemColor(), slot); + } + else if(removeUnequiped) + { + if(slot != SLOT_EQUIPMENT::UNDEFINED && slot != SLOT_EQUIPMENT::FACE) + group.addRemove(slot); + } + } + + _Groups.push_back(group); + return true; + + +} +bool CItemGroupManager::deleteGroup(std::string name) +{ + std::vector tmp; + for(int i=0;i<_Groups.size();i++) + { + CItemGroup group = _Groups[i]; + if(group.name == name) continue; + tmp.push_back(group); + } + // Nothing removed, error + if(tmp.size() == _Groups.size()) return false; + _Groups = tmp; + return true; +} + +void CItemGroupManager::listGroup() +{ + CInterfaceManager *pIM = CInterfaceManager::getInstance(); + pIM->displaySystemInfo(NLMISC::CI18N::get("cmdListGroupHeader")); + for(int i=0;i<_Groups.size();i++) + { + CItemGroup group = _Groups[i]; + ucstring msg = NLMISC::CI18N::get("cmdListGroupLine"); + NLMISC::strFindReplace(msg, "%name", group.name); + NLMISC::strFindReplace(msg, "%size", NLMISC::toString(group.Items.size())); + pIM->displaySystemInfo(msg); + } +} + +//Used by AH + +std::vector CItemGroupManager::getGroupNames(CDBCtrlSheet* pCS) +{ + std::vector out; + for(int i=0;i<_Groups.size();i++) + { + CItemGroup group = _Groups[i]; + if(group.contains(pCS)) + out.push_back(group.name); + } + return out; +} + +//Private methods +CItemGroup* CItemGroupManager::findGroup(std::string name) +{ + for(int i=0;i<_Groups.size();i++) + { + if (_Groups[i].name == name) return &_Groups[i]; + } + return NULL; +} +std::string CItemGroupManager::toDbPath(INVENTORIES::TInventory inventory) +{ + switch(inventory) + { + case INVENTORIES::TInventory::bag: + return LIST_BAG_TEXT; break; + case INVENTORIES::TInventory::pet_animal1: + return LIST_PA0_TEXT; break; + case INVENTORIES::TInventory::pet_animal2: + return LIST_PA1_TEXT; break; + case INVENTORIES::TInventory::pet_animal3: + return LIST_PA2_TEXT; break; + case INVENTORIES::TInventory::pet_animal4: + return LIST_PA3_TEXT; break; + case INVENTORIES::TInventory::player_room: + return LIST_ROOM_TEXT;break; + case INVENTORIES::TInventory::guild: + return ClientCfg.ItemGroupAllowGuild ? LIST_GUILD_TEXT : ""; break; + default: + return ""; + } +} + +std::vector CItemGroupManager::matchingItems(CItemGroup *group, INVENTORIES::TInventory inventory) +{ + //Not very clean, but no choice, it's ugly time + std::vector out; + std::string dbPath = toDbPath(inventory); + if(dbPath.empty()) + { + nldebug("Inventory type %s not supported", INVENTORIES::toString(inventory).c_str()); + return out; + } + + IListSheetBase *pList = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(dbPath)); + for(uint i=0; i < MAX_BAGINV_ENTRIES; i++) + { + CDBCtrlSheet *pCS = pList->getSheet(i); + SLOT_EQUIPMENT::TSlotEquipment slot; + if(group->contains(pCS, slot)) + { + out.push_back(CInventoryItem(pCS, inventory, i, slot)); + } + } + + return out; + +} + +// Singleton management +CItemGroupManager *CItemGroupManager::getInstance() +{ + if (!_Instance) + _Instance = new CItemGroupManager(); + return _Instance; +} +void CItemGroupManager::releaseInstance() +{ + if (_Instance) + delete _Instance; + _Instance = NULL; +} diff --git a/code/ryzom/client/src/item_group_manager.h b/code/ryzom/client/src/item_group_manager.h new file mode 100644 index 000000000..c74985f70 --- /dev/null +++ b/code/ryzom/client/src/item_group_manager.h @@ -0,0 +1,112 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#ifndef RY_ITEM_GROUP_MANAGER_H +#define RY_ITEM_GROUP_MANAGER_H +#include +#include "interface_v3/inventory_manager.h" +#include "interface_v3/dbctrl_sheet.h" +#include "game_share/inventories.h" +#include "game_share/slot_equipment.h" + +struct CInventoryItem { +public: + CDBCtrlSheet* pCS; + INVENTORIES::TInventory origin; + uint32 indexInBag; + SLOT_EQUIPMENT::TSlotEquipment slot; // Used only for dagger (right/left hand slot) + CInventoryItem(CDBCtrlSheet *pCS, INVENTORIES::TInventory origin, uint32 indexInBag, SLOT_EQUIPMENT::TSlotEquipment slot = SLOT_EQUIPMENT::UNDEFINED) : + pCS(pCS), origin(origin), indexInBag(indexInBag), slot(slot) {} + +}; + +class CItemGroup { +public: + struct CItem { + std::string sheetName; + uint16 quality; + uint32 weight; + uint8 color; + SLOT_EQUIPMENT::TSlotEquipment slot; // Used only for dagger (right/left hand slot) + uint32 minPrice; + uint32 maxPrice; + bool usePrice; + CItem(std::string sheetName = "", uint16 quality = 0, uint32 weight = 0, uint8 color = 0, SLOT_EQUIPMENT::TSlotEquipment slot = SLOT_EQUIPMENT::UNDEFINED, uint32 minPrice = 0, uint32 maxPrice = std::numeric_limits::max(), bool usePrice = false) : + sheetName(sheetName), quality(quality), weight(weight), color(color), slot(slot), minPrice(minPrice), maxPrice(maxPrice), usePrice(usePrice) {} + +}; + +public: + CItemGroup(); + + // return true if any item in the group match the parameter ; slot is UNDEFINED unless the item has been found in the group + bool contains(CDBCtrlSheet *other); + bool contains(CDBCtrlSheet* other, SLOT_EQUIPMENT::TSlotEquipment &slot); + void addItem(std::string sheetName, uint16 quality, uint32 weight, uint8 color, SLOT_EQUIPMENT::TSlotEquipment slot); + void addRemove(std::string slotName); + void addRemove(SLOT_EQUIPMENT::TSlotEquipment slot); + void writeTo(xmlNodePtr node); + void readFrom(xmlNodePtr node); + + std::string name; + std::vector Items; + std::vector removeBeforeEquip; +}; + +class CItemGroupManager { +public: + // Singleton management + static CItemGroupManager* getInstance(); + static void releaseInstance(); + //Ctor + CItemGroupManager(); + // Regular function + void init(); + void uninit(); + void saveGroups(); + bool loadGroups(); + void linkInterface(); + void unlinkInterface(); + //Return NULL if no group was found + //Return false if no group was found + bool moveGroup(std::string name, INVENTORIES::TInventory dst); + bool equipGroup(std::string name, bool pullBefore=true); + bool createGroup(std::string name, bool removeUnequiped=false); + bool deleteGroup(std::string name); + void listGroup(); + std::vector getGroupNames(CDBCtrlSheet *pCS); + //Used to fake invalid actions + void update(); + +private: + CItemGroup* findGroup(std::string name); + std::vector matchingItems(CItemGroup* group, INVENTORIES::TInventory inventory); + + std::vector _Groups; + std::string toDbPath(INVENTORIES::TInventory inventory); + // Singleton's instance + static CItemGroupManager *_Instance; + // + void fakeInvalidActions(NLMISC::TGameCycle time); + void invalidActions(NLMISC::TGameCycle begin, NLMISC::TGameCycle end); + void validActions(); + NLMISC::TGameCycle _EndInvalidAction; + NLMISC::TGameCycle _StartInvalidAction; + +}; + +#endif // RY_ITEM_GROUP_MANAGER_H diff --git a/code/ryzom/client/src/main_loop.cpp b/code/ryzom/client/src/main_loop.cpp index fafae2b45..fe96d4da3 100644 --- a/code/ryzom/client/src/main_loop.cpp +++ b/code/ryzom/client/src/main_loop.cpp @@ -121,7 +121,7 @@ #include "bg_downloader_access.h" #include "login_progress_post_thread.h" #include "npc_icon.h" - +#include "item_group_manager.h" // R2ED #include "r2/editor.h" @@ -2566,6 +2566,7 @@ bool mainLoop() // Interface saving CInterfaceManager::getInstance()->uninitInGame0(); + CItemGroupManager::getInstance()->uninit(); ///////////////////////////////// // Display the end background. // diff --git a/code/ryzom/client/src/release.cpp b/code/ryzom/client/src/release.cpp index 96673db89..689f0bf56 100644 --- a/code/ryzom/client/src/release.cpp +++ b/code/ryzom/client/src/release.cpp @@ -93,6 +93,7 @@ #include "interface_v3/interface_ddx.h" #include "bg_downloader_access.h" #include "nel/gui/lua_manager.h" +#include "item_group_manager.h" /////////// @@ -228,6 +229,7 @@ void releaseMainLoopReselect() // save keys loaded and interface cfg (not done in releaseMainLoop() because done at end of mainLoop()...) pIM->uninitInGame0(); + CItemGroupManager::getInstance()->uninit(); // alredy called from farTPMainLoop() // --R2::getEditor().autoConfigRelease(IsInRingSession);