Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / client / src / item_group_manager.cpp
blob47bc9d2fe878b06e72a437d5b21b72dc66903d16
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2017 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdpch.h"
22 #include "item_group_manager.h"
23 #include "interface_v3/inventory_manager.h"
24 #include "nel/gui/widget_manager.h"
25 #include "nel/misc/sheet_id.h"
26 #include "nel/misc/stream.h"
27 #include "nel/misc/o_xml.h"
28 #include "nel/misc/i_xml.h"
29 #include "nel/misc/file.h"
30 #include "nel/misc/xml_macros.h"
31 #include "libxml/tree.h"
32 #include "game_share/item_type.h"
33 #include "client_sheets/item_sheet.h"
34 #include "net_manager.h"
35 #include "connection.h" // Used to access PlayerSelectedFileName for xml filename
36 #include "nel/gui/db_manager.h"
37 #include "interface_v3/interface_manager.h"
38 #include "nel/gui/group_menu.h"
39 #include "nel/misc/i18n.h"
40 #include "nel/misc/algo.h"
41 CItemGroupManager *CItemGroupManager::_Instance = NULL;
43 CItemGroup::CItemGroup()
47 // return true if any item in the group match the parameter ; slot is UNDEFINED unless the item has been found in the group
48 bool CItemGroup::contains(CDBCtrlSheet *pCS)
50 for (int i = 0; i < items.size(); i++)
52 CItem item = items[i];
53 if (item.createTime == pCS->getItemCreateTime() && item.serial == pCS->getItemSerial())
54 return true;
56 return false;
59 void CItemGroup::addSheet(CDBCtrlSheet *pCS, CSlot slot, bool removeEmpty)
61 if (!pCS)
62 return;
64 if (pCS->isSheetValid())
66 addItem(CItem(pCS->getItemCreateTime(), pCS->getItemSerial(), slot));
68 else if (removeEmpty)
70 addRemoveSlot(slot);
74 void CItemGroup::addItem(CItem item)
76 if (!item.dstSlot.isValid())
77 return;
79 // Check if item already exists in group, this could happen if:
80 // 1. Creating a group with a 2-hand item (and the item is found both in handR and handL)
81 // 2. If user incorrectly edits the xml file
82 for (int i = 0; i < items.size(); i++)
84 CItem existingItem = items[i];
85 if (existingItem.createTime == item.createTime && existingItem.serial == item.serial)
87 nldebug("<CItemGroup::addItem> Not adding duplicate item, createTime: %d, serial: %d", item.createTime, item.serial);
88 // If duplicate item because of a 2-hand item:
89 // If it's existing as a left hand item, overwrite it as a right hand item instead (so we have only 1 correct item)
90 if (existingItem.dstSlot == CSlot::handSlot(1) && item.dstSlot == CSlot::handSlot(0))
91 existingItem.dstSlot = CSlot::handSlot(0);
92 return;
96 items.push_back(item);
99 void CItemGroup::addRemoveSlot(CSlot slot)
101 if (!slot.isValid())
102 return;
104 removeSlots.push_back(slot);
107 void CItemGroup::CItem::equip(uint32 &equipTime)
109 if (!pCS)
111 nlwarning("<CItemGroup::CItem::equip> inv item is invalid");
112 return;
115 if (!dstSlot.isValid())
117 nlwarning("<CItemGroup::CItem::equip> item destination slot is invalid");
118 return;
121 // if item is already equipped, and in the good slot, no need to equip
122 if (dstSlot.getSheet()->isSheetEqual(pCS))
124 return;
127 // make sure the item can go into the slot (for example left-hand item can't always in left-hand slot)
128 // this is also checked on the server, but avoids visual glitch if checked on client too
129 if (!dstSlot.getSheet()->canDropItem(pCS))
131 nlwarning("<CItemGroup::CItem::equip> item %d can't be dropped in slot %s", pCS->getSheetId(), dstSlot.toDbPath().c_str());
132 return;
135 std::string srcPath = pCS->getSheet();
136 equipTime = std::max(equipTime, pCS->asItemSheet()->EquipTime);
137 CInventoryManager::getInstance()->equip(srcPath, dstSlot.toDbPath());
140 // return if the item is already in it's destination slot
141 bool CItemGroup::CItem::isInDestinationSlot()
143 return dstSlot.getSheet()->getInventoryIndex() == pCS->getInventoryIndex() && dstSlot.getSheet()->getIndexInDB() == pCS->getIndexInDB();
146 void CItemGroup::CSlot::writeTo(xmlNodePtr node)
148 xmlSetProp(node, (const xmlChar *)"branch", (const xmlChar *)INVENTORIES::toString(branch).c_str());
149 xmlSetProp(node, (const xmlChar *)"index", (const xmlChar *)NLMISC::toString(index).c_str());
152 void CItemGroup::CItem::writeTo(xmlNodePtr node)
154 xmlSetProp(node, (const xmlChar *)"createTime", (const xmlChar *)NLMISC::toString(createTime).c_str());
155 xmlSetProp(node, (const xmlChar *)"serial", (const xmlChar *)NLMISC::toString(serial).c_str());
156 dstSlot.writeTo(node);
159 void CItemGroup::writeTo(xmlNodePtr node)
161 xmlNodePtr groupNode = xmlNewChild(node, NULL, (const xmlChar *)"group", NULL);
162 xmlSetProp(groupNode, (const xmlChar *)"name", (const xmlChar *)name.c_str());
163 for (int i = 0; i < items.size(); i++)
165 xmlNodePtr itemNode = xmlNewChild(groupNode, NULL, (const xmlChar *)"item", NULL);
166 items[i].writeTo(itemNode);
168 for (int i = 0; i < removeSlots.size(); i++)
170 xmlNodePtr removeNode = xmlNewChild(groupNode, NULL, (const xmlChar *)"remove", NULL);
171 removeSlots[i].writeTo(removeNode);
175 CItemGroup::CSlot CItemGroup::CSlot::readFromV1(xmlNodePtr node)
177 CXMLAutoPtr prop;
178 string equipSlot;
179 XML_READ_STRING(node, "slot", equipSlot, "");
180 return CItemGroup::CSlot::fromSlotEquipment(SLOT_EQUIPMENT::stringToSlotEquipment(NLMISC::toUpperAscii(equipSlot)));
183 CItemGroup::CSlot CItemGroup::CSlot::readFromV2(xmlNodePtr node)
185 CXMLAutoPtr prop;
186 INVENTORIES::TInventory branch = INVENTORIES::UNDEFINED;
187 prop = xmlGetProp(node, (xmlChar *)"branch");
188 if (prop)
189 branch = INVENTORIES::toInventory((const char *)prop);
191 uint16 index = 0;
192 prop = (char *)xmlGetProp(node, (xmlChar *)"index");
193 if (prop)
194 NLMISC::fromString((const char *)prop, index);
196 return CSlot(branch, index);
199 CItemGroup::CItem CItemGroup::CItem::readFrom(xmlNodePtr node)
201 CXMLAutoPtr prop;
203 sint32 createTime = 0;
204 prop = (char *)xmlGetProp(node, (xmlChar *)"createTime");
205 if (prop)
206 NLMISC::fromString((const char *)prop, createTime);
208 // Version 0 does not use createTime, version 0 is not supported for migrations
209 if (createTime == 0)
210 nlwarning("<CItemGroup::CItem::readFrom> Possibly on unsupported item group version, please remake the item group or contact support.");
212 sint32 serial = 0;
213 prop = (char *)xmlGetProp(node, (xmlChar *)"serial");
214 if (prop)
215 NLMISC::fromString((const char *)prop, serial);
217 return CItem(createTime, serial, CSlot());
220 void CItemGroup::deserialize(xmlNodePtr node, std::string version)
222 CXMLAutoPtr prop;
224 prop = (char *)xmlGetProp(node, (xmlChar *)"name");
225 if (prop)
226 NLMISC::fromString((const char *)prop, name);
228 xmlNodePtr curNode = node->children;
229 while (curNode)
231 CSlot slot;
232 if (version == "1")
233 slot = CSlot::readFromV1(curNode);
234 else if (version == "2")
235 slot = CSlot::readFromV2(curNode);
236 else
238 nlwarning("<CItemGroup::deserialize> unknown version, can't deserialize group");
239 return;
242 if (strcmp((char *)curNode->name, "item") == 0)
244 CItem item = CItem::readFrom(curNode);
245 item.dstSlot = slot;
246 addItem(item);
248 else if (strcmp((char *)curNode->name, "remove") == 0)
249 addRemoveSlot(slot);
251 curNode = curNode->next;
254 // sort the items by slot (important so that left hand is equipped after right hand)
255 std::sort(items.begin(), items.end());
258 // Updates the CDBCtrlSheet in each CItem
259 // ! This function must be called before every interaction with items of a CItemGroup
261 // Explanation: We must link the CItem (which just represents identifiers of an item) of the CItemGroup
262 // with an item in the current inventory. Basically, here we are locating the position of the item in the inventory
263 // and we are setting it to the pCS property of CItem, so we can then equip or move CItem via its CDBCtrlSheet.
264 // It is possible that the item is deleted or moved, so we must check for this.
265 void CItemGroup::updateSheets()
267 CInventoryManager *pIM = CInventoryManager::getInstance();
268 for (uint i = 0; i < items.size(); i++)
270 CItem item = items[i];
271 bool found = false;
272 for (int i = 0; i < INVENTORIES::NUM_ALL_INVENTORY; i++)
274 INVENTORIES::TInventory inventory = (INVENTORIES::TInventory)i;
275 if (pIM->isInventoryAvailable(inventory))
277 std::string dbPath = CInventoryManager::invToDbPath(inventory);
278 if (dbPath.empty() || (inventory == INVENTORIES::guild && !ClientCfg.ItemGroupAllowGuild))
280 nldebug("<CItemGroup::updateSheets> Inventory type %s not supported", INVENTORIES::toString(inventory).c_str());
281 continue;
283 IListSheetBase *pList = dynamic_cast<IListSheetBase *>(CWidgetManager::getInstance()->getElementFromId(dbPath));
284 for (int i = 0; i < pList->getNbSheet(); i++)
286 CDBCtrlSheet *pCS = pList->getSheet(i);
287 if (item.createTime == pCS->getItemCreateTime() && item.serial == pCS->getItemSerial())
289 item.pCS = pCS;
290 found = true;
291 break;
294 if (found)
295 break;
298 if (!found)
300 nlinfo("<CItemGroup::updateSheets> Item not found in inventory: createTime: %d, serial: %d", item.createTime, item.serial);
301 item.pCS = NULL;
303 items.at(i) = item;
307 const std::string CItemGroup::CSlot::toDbPath()
309 std::string dbPath = "";
310 std::string dbBranch = INVENTORIES::toLocalDbBranch(branch);
312 if (!dbBranch.empty())
313 dbPath = string(LOCAL_INVENTORY) + ":" + dbBranch + ":" + NLMISC::toString(index);
315 return dbPath;
318 const std::string CItemGroup::CSlot::toString()
320 std::string commonName;
321 if (branch == INVENTORIES::handling)
322 commonName = "Hand" + string(index == 0 ? "R" : "L");
323 else if (branch == INVENTORIES::equipment)
324 commonName = SLOT_EQUIPMENT::toString((SLOT_EQUIPMENT::TSlotEquipment)index);
325 else if (branch == INVENTORIES::hotbar)
326 commonName = "Pocket #" + NLMISC::toString(index + 1);
327 else
328 commonName = "unknown";
329 return NLMISC::toString("%s:%sindex=\"%d\"", commonName.c_str(), string(10 - commonName.length(), ' ').c_str(), index);
332 bool CItemGroup::CSlot::isValid()
334 return ((branch == INVENTORIES::handling && index < MAX_HANDINV_ENTRIES)
335 || (branch == INVENTORIES::equipment && index < MAX_EQUIPINV_ENTRIES)
336 || (branch == INVENTORIES::hotbar && index < MAX_HOTBARINV_ENTRIES));
339 CDBCtrlSheet *CItemGroup::CSlot::getSheet()
341 CInventoryManager *pIM = CInventoryManager::getInstance();
342 CDBCtrlSheet *pCS = NULL;
343 if (branch == INVENTORIES::handling)
344 pCS = pIM->getHandSheet(index);
345 else if (branch == INVENTORIES::equipment)
346 pCS = pIM->getEquipSheet(index);
347 else if (branch == INVENTORIES::hotbar)
348 pCS = pIM->getHotbarSheet(index);
349 return pCS;
352 CItemGroup::CSlot CItemGroup::CSlot::fromSlotEquipment(SLOT_EQUIPMENT::TSlotEquipment slotEquipment)
354 CSlot slot;
355 if (slotEquipment == SLOT_EQUIPMENT::HANDR)
356 slot = handSlot(0);
357 else if (slotEquipment == SLOT_EQUIPMENT::HANDL)
358 slot = handSlot(1);
359 else
360 slot = equipSlot(slotEquipment);
361 return slot;
364 CItemGroupManager::CItemGroupManager()
368 void CItemGroupManager::init()
370 loadGroups();
371 linkInterface();
374 void CItemGroupManager::linkInterface()
376 // attach item group subgroup to right-click in bag group
377 CWidgetManager *pWM = CWidgetManager::getInstance();
378 CGroupMenu *pRootMenu = dynamic_cast<CGroupMenu *>(pWM->getElementFromId(MENU_IN_BAG));
379 CGroupSubMenu *pMenu = pRootMenu->getRootMenu();
380 // get item subgroup
381 CGroupMenu *pGroupMenu = dynamic_cast<CGroupMenu *>(pWM->getElementFromId(ITEMGROUP_MENU));
382 CGroupSubMenu *pGroupSubMenu = NULL;
383 if (pGroupMenu)
384 pGroupSubMenu = pGroupMenu->getRootMenu();
385 if (pMenu && pGroupSubMenu)
386 pMenu->setSubMenu(pMenu->getNumLine() - 1, pGroupSubMenu);
387 else
388 nlwarning("<CItemGroupManager::linkInterface> Couldn't link group submenu to item_menu_in_bag, check your widgets.xml file");
390 // must draw the list equip tab
391 drawGroupsList();
394 void CItemGroupManager::uninit()
396 saveGroups();
397 unlinkInterface();
398 _Groups.clear();
401 void CItemGroupManager::unlinkInterface()
403 // We need to unlink our menu to avoid crash on interface release
404 CWidgetManager *pWM = CWidgetManager::getInstance();
405 CGroupMenu *pGroupMenu = dynamic_cast<CGroupMenu *>(pWM->getElementFromId(ITEMGROUP_MENU));
406 CGroupSubMenu *pGroupSubMenu = NULL;
407 if (pGroupMenu)
408 pGroupSubMenu = pGroupMenu->getRootMenu();
409 if (pGroupMenu)
410 pGroupMenu->reset();
411 if (pGroupMenu && pGroupSubMenu)
412 pGroupMenu->delGroup(pGroupSubMenu, true);
413 undrawGroupsList();
416 std::string CItemGroupManager::getFilePath(std::string playerName)
418 return "save/groups_" + playerName + ".xml";
421 // Inspired from macro parsing
422 void CItemGroupManager::saveGroups()
424 if (PlayerSelectedFileName.empty())
426 nlwarning("<CItemGroupManager::saveGroups> Trying to save group with an empty PlayerSelectedFileName, aborting");
427 return;
430 std::string userGroupFileName = getFilePath(PlayerSelectedFileName);
433 NLMISC::COFile f;
434 if (f.open(userGroupFileName, false, false, true))
436 NLMISC::COXml xmlStream;
437 xmlStream.init(&f);
438 xmlDocPtr doc = xmlStream.getDocument();
439 xmlNodePtr comment = xmlNewDocComment(doc, (const xmlChar *)generateDocumentation().c_str());
440 xmlNodePtr node = xmlNewDocNode(doc, NULL, (const xmlChar *)"item_groups", NULL);
441 xmlSetProp(node, (const xmlChar *)"version", (const xmlChar *)ITEMGROUPS_CURRENT_VERSION);
442 xmlDocSetRootElement(doc, comment);
443 xmlAddSibling(comment, node);
444 for (int i = 0; i < _Groups.size(); i++)
446 CItemGroup group = _Groups[i];
447 group.writeTo(node);
449 xmlStream.flush();
450 f.close();
452 else
453 nlwarning("<CItemGroupManager::saveGroups> Can't open the file %s", userGroupFileName.c_str());
455 catch (const NLMISC::Exception &e)
457 nlwarning("<CItemGroupManager::saveGroups> Error while writing the file %s : %s.", userGroupFileName.c_str(), e.what());
461 bool CItemGroupManager::loadGroups()
463 if (PlayerSelectedFileName.empty())
465 nlwarning("<CItemGroupManager::loadGroups> Trying to load group with an empty PlayerSelectedFileName, aborting");
466 return false;
469 std::string userGroupFileName = getFilePath(PlayerSelectedFileName);
470 if (!NLMISC::CFile::fileExists(userGroupFileName) || NLMISC::CFile::getFileSize(userGroupFileName) == 0)
472 nlinfo("<CItemGroupManager::loadGroups> No item groups file found !");
473 return false;
475 // Init loading
476 NLMISC::CIFile f;
477 f.open(userGroupFileName);
478 NLMISC::CIXml xmlStream;
479 xmlNodePtr globalEnclosing;
482 xmlStream.init(f);
483 // Actual loading
484 globalEnclosing = xmlStream.getRootNode();
486 catch (const NLMISC::EXmlParsingError &ex)
488 nlwarning("<CItemGroupManager::loadGroups> Failed to parse '%s', skip", userGroupFileName.c_str());
489 return false;
491 if (!globalEnclosing)
493 nlwarning("<CItemGroupManager::loadGroups> no root element in item_group xml, skipping xml parsing");
494 return false;
496 if (strcmp(((char *)globalEnclosing->name), "item_groups"))
498 nlwarning("<CItemGroupManager::loadGroups> wrong root element in item_group xml, skipping xml parsing");
499 return false;
502 // get version of the item groups file
503 CXMLAutoPtr prop;
504 string version;
505 XML_READ_STRING(globalEnclosing, "version", version, "1");
507 // check if we need to migrate item groups save file
508 if (version != ITEMGROUPS_CURRENT_VERSION)
510 nlinfo("<CItemGroupManager::loadGroups> item group version mismatch, performing migration if possible");
511 // backup current file
512 NLMISC::CFile::copyFile(getFilePath(PlayerSelectedFileName + "_backup"), getFilePath(PlayerSelectedFileName));
515 xmlNodePtr curNode = globalEnclosing->children;
516 while (curNode)
518 if (strcmp((char *)curNode->name, "group") == 0)
520 CItemGroup group;
521 group.deserialize(curNode, version);
522 if (group.empty())
523 nlwarning("<CItemGroupManager::loadGroups> Item group '%s' loaded empty. Possibly on unsupported item group version, please remake the item group or contact support.", group.name.c_str());
524 _Groups.push_back(group);
526 curNode = curNode->next;
528 f.close();
530 return true;
533 void CItemGroupManager::undrawGroupsList()
535 CGroupList *pParent = dynamic_cast<CGroupList *>(CWidgetManager::getInstance()->getElementFromId(LIST_ITEMGROUPS));
536 if (!pParent)
537 return;
538 pParent->clearGroups();
539 pParent->setDynamicDisplaySize(false);
540 CGroupList *pParent2 = dynamic_cast<CGroupList *>(CWidgetManager::getInstance()->getElementFromId(LIST_ITEMGROUPS_2));
541 if (!pParent2)
542 return;
543 pParent2->clearGroups();
544 pParent2->setDynamicDisplaySize(false);
547 void CItemGroupManager::drawGroupsList()
549 // rebuild groups list
550 undrawGroupsList();
551 CGroupList *pParent = dynamic_cast<CGroupList *>(CWidgetManager::getInstance()->getElementFromId(LIST_ITEMGROUPS));
552 if (!pParent)
553 return;
554 CGroupList *pParent2 = dynamic_cast<CGroupList *>(CWidgetManager::getInstance()->getElementFromId(LIST_ITEMGROUPS_2));
555 if (!pParent2)
556 return;
557 CViewBase *pView = pParent->getParent()->getView(LIST_EMPTY_TEXT);
558 CViewBase *pView2 = pParent2->getParent()->getView(LIST_EMPTY_TEXT);
559 if (_Groups.empty())
561 pView->setActive(true);
562 pView2->setActive(true);
563 return;
565 else
567 pView->setActive(false);
568 pView2->setActive(false);
570 for (uint i = 0; i < _Groups.size(); i++)
572 CInterfaceGroup *pLine = generateGroupsListLine(LIST_ITEMGROUPS, i);
573 if (!pLine)
574 continue;
575 CInterfaceGroup *pLine2 = generateGroupsListLine(LIST_ITEMGROUPS_2, i);
576 if (!pLine2)
577 continue;
579 // Add to the list
580 pLine->setParent(pParent);
581 pParent->addChild(pLine);
582 pLine2->setParent(pParent2);
583 pParent2->addChild(pLine2);
587 CInterfaceGroup *CItemGroupManager::generateGroupsListLine(std::string parent, uint i)
589 // create the group line
590 std::string templateId = parent + ":g" + NLMISC::toString(i).c_str();
591 std::vector<std::pair<string, string>> vParams;
592 vParams.push_back(std::pair<string, string>("id", templateId));
593 vParams.push_back(std::pair<string, string>("name", _Groups[i].name));
594 CInterfaceGroup *pLine = NULL;
595 pLine = CWidgetManager::getInstance()->getParser()->createGroupInstance(TEMPLATE_ITEMGROUP_ITEM, parent, vParams);
596 if (!pLine)
597 return NULL;
599 // Set name
600 CViewText *pViewName = dynamic_cast<CViewText *>(pLine->getView(TEMPLATE_ITEMGROUP_ITEM_NAME));
601 if (pViewName)
602 pViewName->setText(_Groups[i].name);
604 return pLine;
607 // move a group from all available inventory to dst
608 bool CItemGroupManager::moveGroup(std::string name, INVENTORIES::TInventory dst)
610 CItemGroup *group = findGroup(name);
611 if (!group)
613 nlinfo("<CItemGroupManager::moveGroup> group %s not found", name.c_str());
614 return false;
617 if (dst == INVENTORIES::UNDEFINED)
619 nlinfo("<CItemGroupManager::moveGroup> Destination inventory not found");
620 return false;
623 // update location of items in the inventory
624 group->updateSheets();
626 CInventoryManager *pIM = CInventoryManager::getInstance();
627 for (uint i = 0; i < group->items.size(); i++)
629 CItemGroup::CItem item = group->items[i];
631 if (!item.pCS)
632 continue;
634 // if the item is equipped, don't move
635 CItemGroup::CSlot currentSlot;
636 if (isItemEquipped(item.pCS, currentSlot))
638 // if item is equipped, but not already in the wanted slot, unequip it first
639 if (!item.dstSlot.getSheet()->isSheetEqual(item.pCS))
640 pIM->unequip(currentSlot.toDbPath());
641 continue;
644 // If the item is already in dst inventory, no need to move it
645 if (item.pCS->getInventoryIndex() == dst)
646 continue;
648 CAHManager::getInstance()->runActionHandler("move_item", item.pCS, "to=lists|nblist=1|listsheet0=" + CInventoryManager::invToDbPath(dst));
651 return true;
654 bool CItemGroupManager::equipGroup(std::string name, bool pullBefore)
656 CItemGroup *pGroup = findGroup(name);
657 if (!pGroup)
659 nlinfo("<CItemGroupManager::equipGroup> group %s not found", name.c_str());
660 return false;
663 if (pullBefore)
664 moveGroup(name, INVENTORIES::bag);
666 // we must update the location of items, even after they were moved
667 pGroup->updateSheets();
669 // first, unequip all remove slots
670 for (int i = 0; i < pGroup->removeSlots.size(); i++)
672 CItemGroup::CSlot slot = pGroup->removeSlots[i];
673 std::string dbPath = slot.toDbPath();
674 if (!dbPath.empty())
675 CInventoryManager::getInstance()->unequip(dbPath);
678 // then, equip items
679 uint32 equipTime = 0;
680 for (int i = 0; i < pGroup->items.size(); i++)
682 CItemGroup::CItem item = pGroup->items[i];
684 item.equip(equipTime);
687 return true;
690 bool CItemGroupManager::createGroup(std::string name, bool removeEmpty)
692 if (findGroup(name))
693 return false;
695 CInventoryManager *pIM = CInventoryManager::getInstance();
696 CItemGroup group = CItemGroup();
697 group.name = name;
698 uint16 i;
699 for (i = 0; i < MAX_HANDINV_ENTRIES; i++)
701 group.addSheet(pIM->getHandSheet(i), CItemGroup::CSlot::handSlot(i), removeEmpty);
703 for (i = 0; i < MAX_EQUIPINV_ENTRIES; ++i)
705 group.addSheet(pIM->getEquipSheet(i), CItemGroup::CSlot::equipSlot(i), removeEmpty);
707 for (i = 0; i < MAX_HOTBARINV_ENTRIES; ++i)
709 group.addSheet(pIM->getHotbarSheet(i), CItemGroup::CSlot::hotbarSlot(i), removeEmpty);
712 _Groups.push_back(group);
714 // must redraw the list equip tab
715 drawGroupsList();
717 return true;
720 bool CItemGroupManager::deleteGroup(std::string name)
722 std::vector<CItemGroup> tmp;
723 for (int i = 0; i < _Groups.size(); i++)
725 CItemGroup group = _Groups[i];
726 if (group.name == name)
727 continue;
728 tmp.push_back(group);
730 // Nothing removed, error
731 if (tmp.size() == _Groups.size())
732 return false;
733 _Groups = tmp;
735 // must redraw the list equip tab
736 drawGroupsList();
738 return true;
741 void CItemGroupManager::listGroup()
743 CInterfaceManager *pIM = CInterfaceManager::getInstance();
744 pIM->displaySystemInfo(NLMISC::CI18N::get("cmdListGroupHeader"));
745 for (int i = 0; i < _Groups.size(); i++)
747 CItemGroup group = _Groups[i];
748 string msg = NLMISC::CI18N::get("cmdListGroupLine");
749 NLMISC::strFindReplace(msg, "%name", group.name);
750 NLMISC::strFindReplace(msg, "%size", NLMISC::toString(group.items.size()));
751 pIM->displaySystemInfo(msg);
755 // Used by AH
756 std::vector<std::string> CItemGroupManager::getGroupNames(CDBCtrlSheet *pCS)
758 std::vector<std::string> out;
759 for (int i = 0; i < _Groups.size(); i++)
761 CItemGroup group = _Groups[i];
762 if (group.contains(pCS))
763 out.push_back(group.name);
765 return out;
768 // Private methods
769 CItemGroup *CItemGroupManager::findGroup(std::string name)
771 for (int i = 0; i < _Groups.size(); i++)
773 if (_Groups[i].name == name)
774 return &_Groups[i];
776 return NULL;
779 // Workaround: sometimes item are marked as equipped by pIM->isBagItemWeared() even though they aren't really
780 // Because of a synchronisation error between client and server
781 bool CItemGroupManager::isItemEquipped(CDBCtrlSheet *pItem, CItemGroup::CSlot &equipSlot)
783 if (!pItem)
785 nlinfo("<CItemGroupManager::isItemEquipped> item is null");
786 return true;
789 CInventoryManager *pIM = CInventoryManager::getInstance();
790 CDBCtrlSheet *pCS;
791 for (uint i = 0; i < MAX_HANDINV_ENTRIES; i++)
793 pCS = pIM->getHandSheet(i);
794 equipSlot = CItemGroup::CSlot::handSlot(i);
795 if (pItem->isSheetEqual(pCS))
796 return true;
798 for (uint32 i = 0; i < MAX_EQUIPINV_ENTRIES; ++i)
800 pCS = pIM->getEquipSheet(i);
801 equipSlot = CItemGroup::CSlot::equipSlot(i);
802 if (pItem->isSheetEqual(pCS))
803 return true;
806 for (uint32 i = 0; i < MAX_HOTBARINV_ENTRIES; ++i)
808 pCS = pIM->getHotbarSheet(i);
809 equipSlot = CItemGroup::CSlot::hotbarSlot(i);
810 if (pItem->isSheetEqual(pCS))
811 return true;
813 return false;
816 std::string CItemGroupManager::generateDocumentation()
818 std::string out;
819 uint16 i;
820 out += "\r\nItem Groups - Version " + string(ITEMGROUPS_CURRENT_VERSION) + "\r\n\r\n/// branch=\"handling\" ///\r\n";
821 for (i = 0; i < MAX_HANDINV_ENTRIES; i++)
822 out += CItemGroup::CSlot::handSlot(i).toString() + "\r\n";
823 out += "\r\n/// branch=\"equipment\" ///\r\n";
824 for (i = 0; i < MAX_EQUIPINV_ENTRIES; ++i)
825 if (i != SLOT_EQUIPMENT::HANDL && i != SLOT_EQUIPMENT::HANDR)
826 out += CItemGroup::CSlot::equipSlot(i).toString() + "\r\n";
827 out += "\r\n/// branch=\"hotbar\" ///\r\n";
828 for (i = 0; i < MAX_HOTBARINV_ENTRIES; ++i)
829 out += CItemGroup::CSlot::hotbarSlot(i).toString() + "\r\n";
830 return out;
833 // Singleton management
834 CItemGroupManager *CItemGroupManager::getInstance()
836 if (!_Instance)
837 _Instance = new CItemGroupManager();
838 return _Instance;
841 void CItemGroupManager::releaseInstance()
843 if (_Instance)
844 delete _Instance;
845 _Instance = NULL;