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);