Fix css style order when using external css files
[ryzomcore.git] / ryzom / client / src / item_group_manager.cpp
blobe1fc52eb793c6261178787a1ab4f5b46c0308006
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 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 "libxml/tree.h"
31 #include "game_share/item_type.h"
32 #include "client_sheets/item_sheet.h"
33 #include "net_manager.h"
34 #include "connection.h" // Used to access PlayerSelectedFileName for xml filename
35 #include "nel/gui/db_manager.h"
36 #include "interface_v3/interface_manager.h"
37 #include "nel/gui/group_menu.h"
38 #include "nel/misc/i18n.h"
39 #include "nel/misc/algo.h"
40 CItemGroupManager *CItemGroupManager::_Instance = NULL;
42 CItemGroup::CItemGroup()
46 bool CItemGroup::contains(CDBCtrlSheet *other)
48 SLOT_EQUIPMENT::TSlotEquipment slot = SLOT_EQUIPMENT::UNDEFINED;
49 return contains(other, slot);
51 bool CItemGroup::contains(CDBCtrlSheet *other, SLOT_EQUIPMENT::TSlotEquipment &slot)
53 slot = SLOT_EQUIPMENT::UNDEFINED;
54 for(int i=0;i<Items.size();i++)
56 CItem item = Items[i];
57 if(item.useCreateTime() && item.createTime == other->getItemCreateTime() && item.serial == other->getItemSerial())
59 slot = item.slot;
60 return true;
62 // Present for compatibility reasons
63 NLMISC::CSheetId sheet = NLMISC::CSheetId(other->getSheetId());
64 if (sheet.toString() == item.sheetName && other->getQuality() == item.quality &&
65 other->getItemWeight() == item.weight && other->getItemColor() == item.color &&
66 (!item.usePrice || (other->getItemPrice() >= item.minPrice && other->getItemPrice() <= item.maxPrice))
69 slot = item.slot;
70 return true;
74 return false;
77 void CItemGroup::addItem(sint32 createTime, sint32 serial, SLOT_EQUIPMENT::TSlotEquipment slot)
79 //Don't add an item if it already exists, this could cause issue
80 // It's happening either if we are creating a group with a 2 hands items (and the item is found both in handR and handL)
81 // Or if an user incorrectly edit his group file
82 for(int i=0; i<Items.size(); i++)
84 if( Items[i].createTime == createTime && Items[i].serial == serial)
86 nldebug("Not adding duplicate item, createTime: %d, serial: %d", createTime, serial);
87 //In this case, we are adding the duplicate item for a 2 hands item
88 //If it's saved as a left hand item, save it as a right hand item instead (so we have only 1 correct item)
89 if(Items[i].slot == SLOT_EQUIPMENT::HANDL && slot == SLOT_EQUIPMENT::HANDR)
90 Items[i].slot = SLOT_EQUIPMENT::HANDR;
91 return;
94 Items.push_back(CItem(createTime, serial, slot));
97 void CItemGroup::addRemove(std::string slotName)
99 SLOT_EQUIPMENT::TSlotEquipment slot = SLOT_EQUIPMENT::stringToSlotEquipment(NLMISC::toUpperAscii(slotName));
100 if(slot != SLOT_EQUIPMENT::UNDEFINED)
101 addRemove(slot);
104 void CItemGroup::addRemove(SLOT_EQUIPMENT::TSlotEquipment slot)
106 removeBeforeEquip.push_back(slot);
109 void CItemGroup::writeTo(xmlNodePtr node)
111 xmlNodePtr groupNode = xmlNewChild (node, NULL, (const xmlChar*)"group", NULL );
112 xmlSetProp(groupNode, (const xmlChar*)"name", (const xmlChar*)name.c_str());
113 for(int i=0;i<Items.size();i++)
115 CItem item = Items[i];
116 xmlNodePtr itemNode = xmlNewChild(groupNode, NULL, (const xmlChar*)"item", NULL);
117 if(item.useCreateTime())
119 xmlSetProp (itemNode, (const xmlChar*)"createTime", (const xmlChar*)NLMISC::toString(item.createTime).c_str());
120 xmlSetProp (itemNode, (const xmlChar*)"serial", (const xmlChar*)NLMISC::toString(item.serial).c_str());
122 // Present for compatibility reasons
123 else
125 xmlSetProp (itemNode, (const xmlChar*)"sheetName", (const xmlChar*)item.sheetName.c_str());
126 xmlSetProp (itemNode, (const xmlChar*)"quality", (const xmlChar*)NLMISC::toString(item.quality).c_str());
127 xmlSetProp (itemNode, (const xmlChar*)"weight", (const xmlChar*)NLMISC::toString(item.weight).c_str());
128 xmlSetProp (itemNode, (const xmlChar*)"color", (const xmlChar*)NLMISC::toString(item.color).c_str());
129 xmlSetProp (itemNode, (const xmlChar*)"minPrice", (const xmlChar*)NLMISC::toString(item.minPrice).c_str());
130 xmlSetProp (itemNode, (const xmlChar*)"maxPrice", (const xmlChar*)NLMISC::toString(item.maxPrice).c_str());
132 // We need to save slot only if it's useful for clarity
133 //if(item.slot == SLOT_EQUIPMENT::HANDL || item.slot == SLOT_EQUIPMENT::HANDR)
134 xmlSetProp(itemNode, (const xmlChar*)"slot", (const xmlChar*)SLOT_EQUIPMENT::toString(item.slot).c_str());
136 for(int i=0;i<removeBeforeEquip.size();i++)
138 xmlNodePtr removeNode = xmlNewChild(groupNode, NULL, (const xmlChar*)"remove", NULL);
139 xmlSetProp(removeNode, (const xmlChar*)"slot", (xmlChar*)SLOT_EQUIPMENT::toString(removeBeforeEquip[i]).c_str());
144 void CItemGroup::readFrom(xmlNodePtr node)
146 CXMLAutoPtr ptrName;
147 ptrName = (char*) xmlGetProp( node, (xmlChar*)"name" );
148 if (ptrName) NLMISC::fromString((const char*)ptrName, name);
150 xmlNodePtr curNode = node->children;
151 while(curNode)
153 if (strcmp((char*)curNode->name, "item") == 0)
156 CItem item;
157 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"createTime");
158 if (ptrName) NLMISC::fromString((const char*)ptrName, item.createTime);
159 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"serial");
160 if (ptrName) NLMISC::fromString((const char*)ptrName, item.serial);
161 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"slot");
162 std::string slot;
163 if (ptrName) NLMISC::fromString((const char*)ptrName, slot);
164 item.slot = SLOT_EQUIPMENT::stringToSlotEquipment(NLMISC::toUpperAscii(slot));
165 // Old read, keep for compatibility reasons
166 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"sheetName");
167 if (ptrName) NLMISC::fromString((const char*)ptrName, item.sheetName);
168 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"quality");
169 if (ptrName) NLMISC::fromString((const char*)ptrName, item.quality);
170 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"weight");
171 if (ptrName) NLMISC::fromString((const char*)ptrName, item.weight);
172 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"color");
173 if (ptrName) NLMISC::fromString((const char*)ptrName, item.color);
174 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"minPrice");
175 if (ptrName) NLMISC::fromString((const char*)ptrName, item.minPrice);
176 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"maxPrice");
177 if (ptrName) NLMISC::fromString((const char*)ptrName, item.maxPrice);
178 item.usePrice = (item.minPrice != 0 || item.maxPrice != std::numeric_limits<uint32>::max());
179 if(item.createTime != 0)
181 addItem(item.createTime, item.serial, item.slot);
183 // Old load : keep for compatibility / migration reasons
184 else
186 Items.push_back(item);
189 if (strcmp((char*)curNode->name, "remove") == 0)
191 std::string slot;
192 ptrName = (char*) xmlGetProp(curNode, (xmlChar*)"slot");
193 if (ptrName) NLMISC::fromString((const char*)ptrName, slot);
194 addRemove(slot);
197 curNode = curNode->next;
202 CItemGroupManager::CItemGroupManager()
204 _EndInvalidAction = 0;
205 _StartInvalidAction = 0;
206 _MigrationDone = false;
209 void CItemGroupManager::init()
211 _MigrationDone = false;
212 loadGroups();
213 linkInterface();
216 void CItemGroupManager::linkInterface()
218 //attach item group subgroup to right-click in bag group
219 CWidgetManager* pWM = CWidgetManager::getInstance();
220 CGroupMenu *pRootMenu = dynamic_cast<CGroupMenu*>(pWM->getElementFromId("ui:interface:item_menu_in_bag"));
221 CGroupSubMenu *pMenu = pRootMenu->getRootMenu();
222 //get item subgroup
223 CGroupMenu *pGroupMenu = dynamic_cast<CGroupMenu*>(pWM->getElementFromId("ui:interface:item_menu_in_bag:item_group_menu"));
224 CGroupSubMenu *pGroupSubMenu = NULL;
225 if (pGroupMenu) pGroupSubMenu = pGroupMenu->getRootMenu();
226 if (pMenu && pGroupSubMenu)
227 pMenu->setSubMenu(pMenu->getNumLine() - 1, pGroupSubMenu);
228 else
229 nlwarning("Couldn't link group submenu to item_menu_in_bag, check your widgets.xml file");
232 void CItemGroupManager::uninit()
234 saveGroups();
235 unlinkInterface();
236 _Groups.clear();
239 void CItemGroupManager::unlinkInterface()
241 // We need to unlink our menu to avoid crash on interface release
242 CWidgetManager* pWM = CWidgetManager::getInstance();
243 CGroupMenu *pGroupMenu = dynamic_cast<CGroupMenu*>(pWM->getElementFromId("ui:interface:item_menu_in_bag:item_group_menu"));
244 CGroupSubMenu *pGroupSubMenu = NULL;
245 if (pGroupMenu) pGroupSubMenu = pGroupMenu->getRootMenu();
246 if (pGroupMenu) pGroupMenu->reset();
247 if (pGroupMenu && pGroupSubMenu) pGroupMenu->delGroup(pGroupSubMenu, true);
250 // Inspired from macro parsing
251 void CItemGroupManager::saveGroups()
253 std::string userGroupFileName = "save/groups_" + PlayerSelectedFileName + ".xml";
254 if(PlayerSelectedFileName.empty())
256 nlwarning("Trying to save group with an empty PlayerSelectedFileName, aborting");
257 return;
259 try {
260 NLMISC::COFile f;
261 if(f.open(userGroupFileName, false, false, true))
264 NLMISC::COXml xmlStream;
265 xmlStream.init(&f);
266 xmlDocPtr doc = xmlStream.getDocument ();
267 xmlNodePtr node = xmlNewDocNode(doc, NULL, (const xmlChar*)"item_groups", NULL);
268 xmlDocSetRootElement (doc, node);
269 for(int i=0;i<_Groups.size();i++)
271 CItemGroup group = _Groups[i];
272 group.writeTo(node);
274 xmlStream.flush();
275 f.close();
277 else
279 nlwarning ("Can't open the file %s", userGroupFileName.c_str());
283 catch (const NLMISC::Exception &e)
285 nlwarning ("Error while writing the file %s : %s.", userGroupFileName.c_str(), e.what ());
289 bool CItemGroupManager::loadGroups()
292 std::string userGroupFileName = "save/groups_" + PlayerSelectedFileName + ".xml";
293 if(PlayerSelectedFileName.empty())
295 nlwarning("Trying to load group with an empty PlayerSelectedFileName, aborting");
296 return false;
298 if (!NLMISC::CFile::fileExists(userGroupFileName) || NLMISC::CFile::getFileSize(userGroupFileName) == 0)
300 nlinfo("No item groups file found !");
301 return false;
303 //Init loading
304 NLMISC::CIFile f;
305 f.open(userGroupFileName);
306 NLMISC::CIXml xmlStream;
307 xmlNodePtr globalEnclosing;
310 xmlStream.init(f);
311 // Actual loading
312 globalEnclosing = xmlStream.getRootNode();
314 catch (const NLMISC::EXmlParsingError &ex)
316 nlwarning("Failed to parse '%s', skip", userGroupFileName.c_str());
317 return false;
319 if(!globalEnclosing)
321 nlwarning("no root element in item_group xml, skipping xml parsing");
322 return false;
324 if(strcmp(( (char*)globalEnclosing->name), "item_groups"))
326 nlwarning("wrong root element in item_group xml, skipping xml parsing");
327 return false;
329 xmlNodePtr curNode = globalEnclosing->children;
330 while (curNode)
332 if (strcmp((char*)curNode->name, "group") == 0)
334 CItemGroup group;
335 group.readFrom(curNode);
336 _Groups.push_back(group);
338 curNode = curNode->next;
340 f.close();
342 return true;
345 void CItemGroupManager::update()
347 if(_StartInvalidAction != 0 && _StartInvalidAction <= NetMngr.getCurrentServerTick())
349 invalidActions(_StartInvalidAction, _EndInvalidAction);
350 _StartInvalidAction = 0;
352 if(_EndInvalidAction != 0 && _EndInvalidAction <= NetMngr.getCurrentServerTick())
354 _EndInvalidAction = 0;
355 validActions();
357 //Migration code, present for compatibility reasons
358 CInterfaceManager *pIM = CInterfaceManager::getInstance();
359 if(!_MigrationDone && pIM)
361 NLMISC::CCDBNodeLeaf *node = NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:CDB_INIT_IN_PROGRESS");
362 if(node)
364 if(!node->getValueBool())
366 nlinfo("Starting migration");
367 migrateGroups();
368 _MigrationDone = true;
369 nlinfo("Item group migration from old system to new system is done !");
376 bool CItemGroupManager::migrateGroups()
378 std::vector<CItemGroup> newGroups;
379 //This is not very optimised, but this will be executed only once (and removed in the near future)
380 for(int i=0; i < _Groups.size(); i++)
382 CItemGroup group = _Groups[i];
383 //Migrate the group only if there is items inside, and the first one hasn't been migrated
384 bool needMigration = group.Items.size() > 0 && !group.Items[0].useCreateTime();
385 if(!needMigration)
387 newGroups.push_back(group);
388 continue;
390 //If we are here, migrate the group
391 newGroups.push_back(migrateGroup(group));
393 _Groups.clear();
394 _Groups = newGroups;
395 return true;
398 CItemGroup CItemGroupManager::migrateGroup(CItemGroup group)
400 //Get all matching items from all inventory
401 CItemGroup out;
402 out.name = group.name;
403 for (int i=0; i < INVENTORIES::NUM_ALL_INVENTORY; i++)
405 INVENTORIES::TInventory inventory = (INVENTORIES::TInventory)i;
406 std::vector<CInventoryItem> items = matchingItems(&group, inventory);
407 for(int j = 0; j < items.size(); j++)
409 SLOT_EQUIPMENT::TSlotEquipment slot = SLOT_EQUIPMENT::UNDEFINED;
410 //slot might be undefined here, but we want it for clarity purpose in the xml (to easily find lines)
411 if(items[j].slot != SLOT_EQUIPMENT::UNDEFINED)
412 slot = items[j].slot;
413 // We can't get a perfect match (can't know if it's a right/left jewel for example), but it's good enough
414 else
417 //jewels
418 const CItemSheet* sheet = items[j].pCS->asItemSheet();
419 if(!sheet)
421 nlinfo("Could not get as itemSheet, strange");
424 else if (sheet->hasSlot(SLOTTYPE::HEADDRESS)) slot = SLOT_EQUIPMENT::HEADDRESS;
425 else if (sheet->hasSlot(SLOTTYPE::NECKLACE)) slot = SLOT_EQUIPMENT::NECKLACE;
426 else if (sheet->hasSlot(SLOTTYPE::FINGERS)) slot = SLOT_EQUIPMENT::FINGERL;
427 else if (sheet->hasSlot(SLOTTYPE::ANKLE)) slot = SLOT_EQUIPMENT::ANKLEL;
428 else if (sheet->hasSlot(SLOTTYPE::WRIST)) slot = SLOT_EQUIPMENT::WRISTL;
429 else if (sheet->hasSlot(SLOTTYPE::EARS)) slot = SLOT_EQUIPMENT::EARL;
430 //Armor
431 //Helmet
432 else if (sheet->hasSlot(SLOTTYPE::HEAD)) slot = SLOT_EQUIPMENT::HEAD;
433 //Gloves
434 else if (sheet->hasSlot(SLOTTYPE::HANDS)) slot = SLOT_EQUIPMENT::HANDS;
435 //Sleeves
436 else if (sheet->hasSlot(SLOTTYPE::ARMS)) slot = SLOT_EQUIPMENT::ARMS;
437 //Vest
438 else if (sheet->hasSlot(SLOTTYPE::CHEST)) slot = SLOT_EQUIPMENT::CHEST;
439 //Boots
440 else if (sheet->hasSlot(SLOTTYPE::FEET)) slot = SLOT_EQUIPMENT::FEET;
441 // pants
442 else if (sheet->hasSlot(SLOTTYPE::LEGS)) slot = SLOT_EQUIPMENT::LEGS;
443 else slot = SLOT_EQUIPMENT::UNDEFINED;
445 out.addItem(items[j].pCS->getItemCreateTime(), items[j].pCS->getItemSerial(), slot);
448 return out;
451 void CItemGroupManager::fakeInvalidActions(NLMISC::TGameCycle time)
453 // We cannot directly ivnalidate action or our invalidate will be overriden by the server
454 // (and that means we won't actually have one because it's buggy with multiple equip in a short time)
455 // So we wait a bit (currently 6 ticks is enough) to do it
456 _StartInvalidAction = NetMngr.getCurrentServerTick() + 6;
457 _EndInvalidAction = NetMngr.getCurrentServerTick() + time;
458 invalidActions(NetMngr.getCurrentServerTick(), _EndInvalidAction);
461 void CItemGroupManager::invalidActions(NLMISC::TGameCycle begin, NLMISC::TGameCycle end)
463 NLGUI::CDBManager *pDB = NLGUI::CDBManager::getInstance();
464 NLMISC::CCDBNodeLeaf *node;
465 // This are the db update server sends when an user equip an item, see egs/player_manager/gear_latency.cpp CGearLatency::setSlot
466 node = pDB->getDbProp("SERVER:USER:ACT_TSTART", false);
467 if (node) node->setValue64(begin);
469 node = pDB->getDbProp("SERVER:USER:ACT_TEND", false);
470 if(node) node->setValue64(end);
472 node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:SHEET", false);
473 static NLMISC::CSheetId equipSheet("big_equip_item.sbrick");
474 if(node) node->setValue64((sint64)equipSheet.asInt());
476 node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:PHRASE", false);
477 if(node) node->setValue64(0);
480 void CItemGroupManager::validActions()
482 NLGUI::CDBManager *pDB = NLGUI::CDBManager::getInstance();
483 NLMISC::CCDBNodeLeaf *node;
484 node = pDB->getDbProp("SERVER:USER:ACT_TSTART", false);
485 if (node) node->setValue64(0);
487 node = pDB->getDbProp("SERVER:USER:ACT_TEND", false);
488 if(node) node->setValue64(0);
490 node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:SHEET", false);
491 if(node) node->setValue32(0);
493 node = pDB->getDbProp("SERVER:EXECUTE_PHRASE:PHRASE", false);
494 if(node) node->setValue32(0);
497 //move a group from all available inventory to dst
498 bool CItemGroupManager::moveGroup(std::string name, INVENTORIES::TInventory dst)
500 CItemGroup* group = findGroup(name);
501 if(!group)
503 nlinfo("group %s not found", name.c_str());
504 return false;
506 if(dst == INVENTORIES::UNDEFINED)
508 nlinfo("Destination inventory not found");
509 return false;
511 CInventoryManager* pIM = CInventoryManager::getInstance();
513 std::string moveParams = "to=lists|nblist=1|listsheet0=" + toDbPath(dst);
514 // Grab all matching item from all available inventory and put it in dst
515 for (int i=0; i< INVENTORIES::NUM_ALL_INVENTORY; i ++)
517 INVENTORIES::TInventory inventory = (INVENTORIES::TInventory)i;
518 if (inventory != dst && pIM->isInventoryAvailable(inventory))
520 std::vector<CInventoryItem> items = matchingItems(group, inventory);
521 for(int i=0;i<items.size();i++)
523 CInventoryItem item = items[i];
524 //Workaround: sometimes item are marked as equipped by pIM->isBagItemWeared() even tho they aren't really
525 //Because of a synchronisation error between client and server
526 if(isItemReallyEquipped(item.pCS)) continue;
527 CAHManager::getInstance()->runActionHandler("move_item", item.pCS, moveParams);
532 return true;
536 bool CItemGroupManager::equipGroup(std::string name, bool pullBefore)
538 CItemGroup* group = findGroup(name);
539 if(!group)
541 nlinfo("group %s not found", name.c_str());
542 return false;
545 if(pullBefore) moveGroup(name, INVENTORIES::bag);
546 //Start by unequipping all slot that user wants to unequip
547 for(int i=0; i < group->removeBeforeEquip.size(); i++)
549 SLOT_EQUIPMENT::TSlotEquipment slot = group->removeBeforeEquip[i];
550 std::string dbPath;
551 // For hands equip, dbPath obviously starts at 0, we need to offset correctly
552 if(slot == SLOT_EQUIPMENT::HANDL || slot == SLOT_EQUIPMENT::HANDR)
553 dbPath = "LOCAL:INVENTORY:HAND:" + NLMISC::toString((uint32)slot - SLOT_EQUIPMENT::HANDL);
554 else
555 dbPath = "LOCAL:INVENTORY:EQUIP:" + NLMISC::toString((uint32)slot);
556 CInventoryManager::getInstance()->unequip(dbPath);
559 uint32 maxEquipTime = 0;
561 #ifdef NL_ISO_CPP0X_AVAILABLE
562 std::map<ITEM_TYPE::TItemType, bool> possiblyDual =
564 {ITEM_TYPE::ANKLET, false},
565 {ITEM_TYPE::BRACELET, false},
566 {ITEM_TYPE::EARING, false},
567 {ITEM_TYPE::RING, false},
569 #else
570 std::map<ITEM_TYPE::TItemType, bool> possiblyDual;
572 possiblyDual[ITEM_TYPE::ANKLET] = false;
573 possiblyDual[ITEM_TYPE::BRACELET] = false;
574 possiblyDual[ITEM_TYPE::EARING] = false;
575 possiblyDual[ITEM_TYPE::RING] = false;
576 #endif
578 std::vector<CInventoryItem> duals;
579 std::vector<CInventoryItem> items = matchingItems(group, INVENTORIES::bag);
580 for(int i=0; i < items.size(); i++)
582 CInventoryItem item = items[i];
583 ITEM_TYPE::TItemType itemType = item.pCS->asItemSheet()->ItemType;
584 // We'll equip items in left hand later (the right hand will be normally equipped)
585 // This way, if we switch from 2 hands to 2 * 1 hands, both hands will be equipped correctly (first right, which will remove the 2 hands, then left)
586 // If we don't, we might try to equip the left hand first, which will do nothing because we have a 2 hands equipped
587 if(item.slot == SLOT_EQUIPMENT::HANDL)
589 duals.push_back(item);
590 continue;
593 // If the item can be weared 2 times, don't automatically equip the second one
594 // Or else it will simply replace the first. We'll deal with them later
595 if(possiblyDual.find(itemType) != possiblyDual.end())
597 if (possiblyDual[itemType])
599 duals.push_back(item);
600 continue;
602 possiblyDual[itemType] = true;
605 maxEquipTime = std::max(maxEquipTime, item.pCS->asItemSheet()->EquipTime);
606 CInventoryManager::getInstance()->autoEquip(item.indexInBag, true);
608 // Manually equip dual items
609 for(int i=0;i < duals.size();i++)
611 CInventoryItem item = duals[i];
612 ITEM_TYPE::TItemType itemType = item.pCS->asItemSheet()->ItemType;
613 std::string dstPath = string(LOCAL_INVENTORY);
614 switch(itemType)
616 case ITEM_TYPE::ANKLET:
617 dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::ANKLER); break;
618 case ITEM_TYPE::BRACELET:
619 dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::WRISTR);; break;
620 case ITEM_TYPE::EARING:
621 dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::EARR);; break;
622 case ITEM_TYPE::RING:
623 dstPath += ":EQUIP:" + NLMISC::toString((int)SLOT_EQUIPMENT::FINGERR);;break;
624 case ITEM_TYPE::DAGGER:
625 case ITEM_TYPE::BUCKLER:
626 case ITEM_TYPE::SHIELD:
627 dstPath += ":HAND:1"; break;
628 default:
629 break;
632 std::string srcPath = item.pCS->getSheet();
633 maxEquipTime = std::max(maxEquipTime, item.pCS->asItemSheet()->EquipTime);
634 CInventoryManager::getInstance()->equip(srcPath, dstPath);
636 // For some reason, there is no (visual) invalidation (server still blocks any action), force one
637 // Unfortunately, there is no clean way to do this, so we'll simulate one
638 if(maxEquipTime > 0)
639 fakeInvalidActions((NLMISC::TGameCycle)maxEquipTime);
640 return true;
644 bool CItemGroupManager::createGroup(std::string name, bool removeUnequiped)
646 if(findGroup(name)) return false;
647 CItemGroup group = CItemGroup();
648 group.name = name;
649 uint i;
650 CDBCtrlSheet* pCS;
651 for (i = 0; i < MAX_EQUIPINV_ENTRIES; ++i)
653 SLOT_EQUIPMENT::TSlotEquipment slot = (SLOT_EQUIPMENT::TSlotEquipment)i;
654 //Instead of doing two separate for, just be a bit tricky for hand equipment
655 if(slot == SLOT_EQUIPMENT::HANDR)
656 pCS = CInventoryManager::getInstance()->getHandSheet(0);
657 else if(slot == SLOT_EQUIPMENT::HANDL)
658 pCS = CInventoryManager::getInstance()->getHandSheet(1);
659 else
660 pCS = CInventoryManager::getInstance()->getEquipSheet(i);
661 if(!pCS) continue;
662 if(pCS->isSheetValid())
664 group.addItem(pCS->getItemCreateTime(), pCS->getItemSerial(), slot);
666 else if(removeUnequiped)
668 if(slot != SLOT_EQUIPMENT::UNDEFINED && slot != SLOT_EQUIPMENT::FACE)
669 group.addRemove(slot);
673 _Groups.push_back(group);
674 return true;
678 bool CItemGroupManager::deleteGroup(std::string name)
680 std::vector<CItemGroup> tmp;
681 for(int i=0;i<_Groups.size();i++)
683 CItemGroup group = _Groups[i];
684 if(group.name == name) continue;
685 tmp.push_back(group);
687 // Nothing removed, error
688 if(tmp.size() == _Groups.size()) return false;
689 _Groups = tmp;
690 return true;
693 void CItemGroupManager::listGroup()
695 CInterfaceManager *pIM = CInterfaceManager::getInstance();
696 pIM->displaySystemInfo(NLMISC::CI18N::get("cmdListGroupHeader"));
697 for(int i=0;i<_Groups.size();i++)
699 CItemGroup group = _Groups[i];
700 string msg = NLMISC::CI18N::get("cmdListGroupLine");
701 //Use utf-8 string because group name can contain accentued characters (and stuff like that)
702 string nameUC = group.name;
703 NLMISC::strFindReplace(msg, "%name", nameUC);
704 NLMISC::strFindReplace(msg, "%size", NLMISC::toString(group.Items.size()));
705 pIM->displaySystemInfo(msg);
709 //Used by AH
711 std::vector<std::string> CItemGroupManager::getGroupNames(CDBCtrlSheet* pCS)
713 std::vector<std::string> out;
714 for(int i=0;i<_Groups.size();i++)
716 CItemGroup group = _Groups[i];
717 if(group.contains(pCS))
718 out.push_back(group.name);
720 return out;
723 //Private methods
724 CItemGroup* CItemGroupManager::findGroup(std::string name)
726 for(int i=0;i<_Groups.size();i++)
728 if (_Groups[i].name == name) return &_Groups[i];
730 return NULL;
732 std::string CItemGroupManager::toDbPath(INVENTORIES::TInventory inventory)
734 switch(inventory)
736 case INVENTORIES::bag:
737 return LIST_BAG_TEXT; break;
738 case INVENTORIES::pet_animal1:
739 return LIST_PA0_TEXT; break;
740 case INVENTORIES::pet_animal2:
741 return LIST_PA1_TEXT; break;
742 case INVENTORIES::pet_animal3:
743 return LIST_PA2_TEXT; break;
744 case INVENTORIES::pet_animal4:
745 return LIST_PA3_TEXT; break;
746 case INVENTORIES::player_room:
747 return LIST_ROOM_TEXT;break;
748 case INVENTORIES::guild:
749 return ClientCfg.ItemGroupAllowGuild ? LIST_GUILD_TEXT : ""; break;
750 default:
751 return "";
755 bool CItemGroupManager::isItemReallyEquipped(CDBCtrlSheet* item)
757 CDBCtrlSheet* pCS;
758 for (uint32 i = 0; i < MAX_EQUIPINV_ENTRIES; ++i)
760 SLOT_EQUIPMENT::TSlotEquipment slot = (SLOT_EQUIPMENT::TSlotEquipment)i;
761 //Instead of doing two separate for, just be a bit tricky for hand equipment
762 if(slot == SLOT_EQUIPMENT::HANDR)
763 pCS = CInventoryManager::getInstance()->getHandSheet(0);
764 else if(slot == SLOT_EQUIPMENT::HANDL)
765 pCS = CInventoryManager::getInstance()->getHandSheet(1);
766 else
767 pCS = CInventoryManager::getInstance()->getEquipSheet(i);
768 if(!pCS) continue;
769 //Can't directly compare ID (as pCS is like "ui:interface:inv_equip:content:equip:armors:feet" and item is like "ui:interface:inv_pa3:content:iil:bag_list:list:sheet57")
770 //Instead check inventory + slot
771 if((pCS->getInventoryIndex() == item->getInventoryIndex())
772 && (pCS->getIndexInDB() == item->getIndexInDB()))
774 return true;
779 return false;
782 std::vector<CInventoryItem> CItemGroupManager::matchingItems(CItemGroup *group, INVENTORIES::TInventory inventory)
784 //Not very clean, but no choice, it's ugly time
785 std::vector<CInventoryItem> out;
786 std::string dbPath = toDbPath(inventory);
788 if (dbPath.empty())
790 nldebug("Inventory type %s not supported", INVENTORIES::toString(inventory).c_str());
791 return out;
794 IListSheetBase *pList = dynamic_cast<IListSheetBase*>(CWidgetManager::getInstance()->getElementFromId(dbPath));
796 for(uint i=0; i < MAX_BAGINV_ENTRIES; i++)
798 CDBCtrlSheet *pCS = pList->getSheet(i);
799 SLOT_EQUIPMENT::TSlotEquipment slot;
801 if (group->contains(pCS, slot))
803 //Sometimes, index in the list differ from the index in DB, and we need the index in DB, not the one from the list
804 std::string dbPath = pCS->getSheet();
805 std::size_t found = dbPath.find_last_of(":");
806 std::string indexS = dbPath.substr(found+1);
807 uint32 index;
808 NLMISC::fromString(indexS, index);
810 if (i != index)
812 nldebug("Index from list is %d, where index from DB is %d", i, index);
815 out.push_back(CInventoryItem(pCS, inventory, index, slot));
819 return out;
823 // Singleton management
824 CItemGroupManager *CItemGroupManager::getInstance()
826 if (!_Instance)
827 _Instance = new CItemGroupManager();
828 return _Instance;
830 void CItemGroupManager::releaseInstance()
832 if (_Instance)
833 delete _Instance;
834 _Instance = NULL;