1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2017 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
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())
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
))
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
;
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
)
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
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
)
147 ptrName
= (char*) xmlGetProp( node
, (xmlChar
*)"name" );
148 if (ptrName
) NLMISC::fromString((const char*)ptrName
, name
);
150 xmlNodePtr curNode
= node
->children
;
153 if (strcmp((char*)curNode
->name
, "item") == 0)
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");
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
186 Items
.push_back(item
);
189 if (strcmp((char*)curNode
->name
, "remove") == 0)
192 ptrName
= (char*) xmlGetProp(curNode
, (xmlChar
*)"slot");
193 if (ptrName
) NLMISC::fromString((const char*)ptrName
, slot
);
197 curNode
= curNode
->next
;
202 CItemGroupManager::CItemGroupManager()
204 _EndInvalidAction
= 0;
205 _StartInvalidAction
= 0;
206 _MigrationDone
= false;
209 void CItemGroupManager::init()
211 _MigrationDone
= false;
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();
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
);
229 nlwarning("Couldn't link group submenu to item_menu_in_bag, check your widgets.xml file");
232 void CItemGroupManager::uninit()
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");
261 if(f
.open(userGroupFileName
, false, false, true))
264 NLMISC::COXml xmlStream
;
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
];
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");
298 if (!NLMISC::CFile::fileExists(userGroupFileName
) || NLMISC::CFile::getFileSize(userGroupFileName
) == 0)
300 nlinfo("No item groups file found !");
305 f
.open(userGroupFileName
);
306 NLMISC::CIXml xmlStream
;
307 xmlNodePtr globalEnclosing
;
312 globalEnclosing
= xmlStream
.getRootNode();
314 catch (const NLMISC::EXmlParsingError
&ex
)
316 nlwarning("Failed to parse '%s', skip", userGroupFileName
.c_str());
321 nlwarning("no root element in item_group xml, skipping xml parsing");
324 if(strcmp(( (char*)globalEnclosing
->name
), "item_groups"))
326 nlwarning("wrong root element in item_group xml, skipping xml parsing");
329 xmlNodePtr curNode
= globalEnclosing
->children
;
332 if (strcmp((char*)curNode
->name
, "group") == 0)
335 group
.readFrom(curNode
);
336 _Groups
.push_back(group
);
338 curNode
= curNode
->next
;
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;
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");
364 if(!node
->getValueBool())
366 nlinfo("Starting migration");
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();
387 newGroups
.push_back(group
);
390 //If we are here, migrate the group
391 newGroups
.push_back(migrateGroup(group
));
398 CItemGroup
CItemGroupManager::migrateGroup(CItemGroup group
)
400 //Get all matching items from all inventory
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
418 const CItemSheet
* sheet
= items
[j
].pCS
->asItemSheet();
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
;
432 else if (sheet
->hasSlot(SLOTTYPE::HEAD
)) slot
= SLOT_EQUIPMENT::HEAD
;
434 else if (sheet
->hasSlot(SLOTTYPE::HANDS
)) slot
= SLOT_EQUIPMENT::HANDS
;
436 else if (sheet
->hasSlot(SLOTTYPE::ARMS
)) slot
= SLOT_EQUIPMENT::ARMS
;
438 else if (sheet
->hasSlot(SLOTTYPE::CHEST
)) slot
= SLOT_EQUIPMENT::CHEST
;
440 else if (sheet
->hasSlot(SLOTTYPE::FEET
)) slot
= SLOT_EQUIPMENT::FEET
;
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
);
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
);
503 nlinfo("group %s not found", name
.c_str());
506 if(dst
== INVENTORIES::UNDEFINED
)
508 nlinfo("Destination inventory not found");
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
);
536 bool CItemGroupManager::equipGroup(std::string name
, bool pullBefore
)
538 CItemGroup
* group
= findGroup(name
);
541 nlinfo("group %s not found", name
.c_str());
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
];
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
);
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},
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;
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
);
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
);
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
);
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;
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
639 fakeInvalidActions((NLMISC::TGameCycle
)maxEquipTime
);
644 bool CItemGroupManager::createGroup(std::string name
, bool removeUnequiped
)
646 if(findGroup(name
)) return false;
647 CItemGroup group
= CItemGroup();
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);
660 pCS
= CInventoryManager::getInstance()->getEquipSheet(i
);
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
);
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;
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
);
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
);
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
];
732 std::string
CItemGroupManager::toDbPath(INVENTORIES::TInventory 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;
755 bool CItemGroupManager::isItemReallyEquipped(CDBCtrlSheet
* item
)
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);
767 pCS
= CInventoryManager::getInstance()->getEquipSheet(i
);
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()))
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
);
790 nldebug("Inventory type %s not supported", INVENTORIES::toString(inventory
).c_str());
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);
808 NLMISC::fromString(indexS
, 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
));
823 // Singleton management
824 CItemGroupManager
*CItemGroupManager::getInstance()
827 _Instance
= new CItemGroupManager();
830 void CItemGroupManager::releaseInstance()