1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2010  The Mana Developers
4  *  Copyright (C) 2011-2019  The ManaPlus Developers
5  *  Copyright (C) 2019-2021  Andrei Karas
6  *
7  *  This file is part of The ManaPlus Client.
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "being/playerinfo.h"
24 
25 #include "configuration.h"
26 #include "itemsoundmanager.h"
27 #include "settings.h"
28 
29 #include "being/localplayer.h"
30 
31 #include "being/homunculusinfo.h"
32 #include "being/mercenaryinfo.h"
33 #include "being/petinfo.h"
34 
35 #include "gui/windows/inventorywindow.h"
36 #include "gui/windows/npcdialog.h"
37 
38 #include "listeners/statlistener.h"
39 
40 #include "net/homunculushandler.h"
41 #include "net/inventoryhandler.h"
42 #include "net/mercenaryhandler.h"
43 #include "net/playerhandler.h"
44 
45 #include "resources/item/item.h"
46 
47 #include "utils/delete2.h"
48 
49 #include "utils/translation/translationmanager.h"
50 
51 #include "debug.h"
52 
53 namespace PlayerInfo
54 {
55 
56 PlayerInfoBackend mData;
57 int mCharId = 0;
58 
59 Inventory *mInventory = nullptr;
60 Inventory *mCartInventory = nullptr;
61 MercenaryInfo *mMercenary = nullptr;
62 HomunculusInfo *mHomunculus = nullptr;
63 PetInfo *mPet = nullptr;
64 std::string mRoomName;
65 Equipment *mEquipment = nullptr;
66 BeingId mPetBeingId = BeingId_zero;
67 GuildPositionFlags::Type mGuildPositionFlags = GuildPositionFlags::None;
68 BeingId mElementalId = BeingId_zero;
69 
70 Trading mTrading = Trading_false;
71 bool mVending = false;
72 int mLevelProgress = 0;
73 int mServerLanguage = -1;
74 std::set<int> mProtectedItems;
75 
76 // --- Triggers ---------------------------------------------------------------
77 
triggerAttr(const AttributesT id,const int64_t old)78 void triggerAttr(const AttributesT id,
79                  const int64_t old)
80 {
81     AttributeListener::distributeEvent(id, old,
82         mData.mAttributes.find(id)->second);
83 }
84 
triggerStat(const AttributesT id,const int old1,const int old2)85 void triggerStat(const AttributesT id,
86                  const int old1,
87                  const int old2)
88 {
89     StatListener::distributeEvent(id, old1, old2);
90 }
91 
92 // --- Attributes -------------------------------------------------------------
93 
getAttribute64(const AttributesT id)94 int64_t getAttribute64(const AttributesT id)
95 {
96     const AtrIntMap::const_iterator it = mData.mAttributes.find(id);
97     if (it != mData.mAttributes.end())
98         return it->second;
99     return 0;
100 }
101 
getAttribute(const AttributesT id)102 int32_t getAttribute(const AttributesT id)
103 {
104     const AtrIntMap::const_iterator it = mData.mAttributes.find(id);
105     if (it != mData.mAttributes.end())
106         return CAST_S32(it->second);
107     return 0;
108 }
109 
setAttribute(const AttributesT id,const int64_t value,const Notify notify)110 void setAttribute(const AttributesT id,
111                   const int64_t value,
112                   const Notify notify)
113 {
114     const int64_t old = mData.mAttributes[id];
115     mData.mAttributes[id] = value;
116     if (notify == Notify_true)
117         triggerAttr(id, old);
118 }
119 
getSkillLevel(const int id)120 int getSkillLevel(const int id)
121 {
122     const IntMap::const_iterator it = mData.mSkills.find(id);
123     if (it != mData.mSkills.end())
124         return it->second;
125     return 0;
126 }
127 
setSkillLevel(const int id,const int value)128 void setSkillLevel(const int id, const int value)
129 {
130     mData.mSkills[id] = value;
131 }
132 
133 // --- Stats ------------------------------------------------------------------
134 
getStatBase(const AttributesT id)135 int getStatBase(const AttributesT id)
136 {
137     const StatMap::const_iterator it = mData.mStats.find(id);
138     if (it != mData.mStats.end())
139         return it->second.base;
140     return 0;
141 }
142 
setStatBase(const AttributesT id,const int value,const Notify notify)143 void setStatBase(const AttributesT id, const int value, const Notify notify)
144 {
145     const int old = mData.mStats[id].base;
146     mData.mStats[id].base = value;
147     if (notify == Notify_true)
148         triggerStat(id, old, 0);
149 }
150 
getStatMod(const AttributesT id)151 int getStatMod(const AttributesT id)
152 {
153     const StatMap::const_iterator it = mData.mStats.find(id);
154     if (it != mData.mStats.end())
155         return it->second.mod;
156     return 0;
157 }
158 
setStatMod(const AttributesT id,const int value,const Notify notify)159 void setStatMod(const AttributesT id, const int value, const Notify notify)
160 {
161     const int old = mData.mStats[id].mod;
162     mData.mStats[id].mod = value;
163     if (notify == Notify_true)
164         triggerStat(id, old, 0);
165 }
166 
getStatEffective(const AttributesT id)167 int getStatEffective(const AttributesT id)
168 {
169     const StatMap::const_iterator it = mData.mStats.find(id);
170     if (it != mData.mStats.end())
171         return it->second.base + it->second.mod;
172     return 0;
173 }
174 
getStatExperience(const AttributesT id)175 const std::pair<int, int> getStatExperience(const AttributesT id)
176 {
177     const StatMap::const_iterator it = mData.mStats.find(id);
178     int a;
179     int b;
180     if (it != mData.mStats.end())
181     {
182         a = it->second.exp;
183         b = it->second.expNeed;
184     }
185     else
186     {
187         a = 0;
188         b = 0;
189     }
190     return std::pair<int, int>(a, b);
191 }
192 
193 // --- Inventory / Equipment --------------------------------------------------
194 
getInventory()195 Inventory *getInventory()
196 {
197     return mInventory;
198 }
199 
getStorageInventory()200 Inventory *getStorageInventory()
201 {
202     if (inventoryHandler != nullptr)
203         return inventoryHandler->getStorage();
204     return nullptr;
205 }
206 
getCartInventory()207 Inventory *getCartInventory()
208 {
209     return mCartInventory;
210 }
211 
clearInventory()212 void clearInventory()
213 {
214     if (mEquipment != nullptr)
215         mEquipment->clear();
216     if (mInventory != nullptr)
217         mInventory->clear();
218 }
219 
getEquipment()220 Equipment *getEquipment()
221 {
222     return mEquipment;
223 }
224 
getEquipment(const unsigned int slot)225 const Item *getEquipment(const unsigned int slot)
226 {
227     if (mEquipment != nullptr)
228         return mEquipment->getEquipment(slot);
229     return nullptr;
230 }
231 
setEquipmentBackend(Equipment::Backend * const backend)232 void setEquipmentBackend(Equipment::Backend *const backend)
233 {
234     if (mEquipment != nullptr)
235         mEquipment->setBackend(backend);
236 }
237 
equipItem(const Item * const item,const Sfx sfx)238 void equipItem(const Item *const item, const Sfx sfx)
239 {
240     if (sfx == Sfx_true)
241         ItemSoundManager::playSfx(item, ItemSoundEvent::EQUIP);
242     if (inventoryHandler != nullptr)
243         inventoryHandler->equipItem(item);
244 }
245 
unequipItem(const Item * const item,const Sfx sfx)246 void unequipItem(const Item *const item, const Sfx sfx)
247 {
248     if (sfx == Sfx_true)
249         ItemSoundManager::playSfx(item, ItemSoundEvent::UNEQUIP);
250     if (inventoryHandler != nullptr)
251         inventoryHandler->unequipItem(item);
252 }
253 
useItem(const Item * const item,const Sfx sfx)254 void useItem(const Item *const item, const Sfx sfx)
255 {
256     if (sfx == Sfx_true)
257         ItemSoundManager::playSfx(item, ItemSoundEvent::USE);
258     if (inventoryHandler != nullptr)
259         inventoryHandler->useItem(item);
260 }
261 
useEquipItem(const Item * const item,const int16_t useType,const Sfx sfx)262 void useEquipItem(const Item *const item,
263                   const int16_t useType,
264                   const Sfx sfx)
265 {
266     if (item != nullptr)
267     {
268         if (item->getType() == ItemType::Card)
269         {
270             if (mProtectedItems.find(item->getId()) == mProtectedItems.end())
271             {
272                 if (inventoryHandler != nullptr)
273                     inventoryHandler->useCard(item);
274                 if (sfx == Sfx_true)
275                     ItemSoundManager::playSfx(item, ItemSoundEvent::USECARD);
276             }
277         }
278         else if (item->isEquipment() == Equipm_true)
279         {
280             if (item->isEquipped() == Equipped_true)
281             {
282                 if (sfx == Sfx_true)
283                     ItemSoundManager::playSfx(item, ItemSoundEvent::UNEQUIP);
284                 if (inventoryHandler != nullptr)
285                     inventoryHandler->unequipItem(item);
286             }
287             else
288             {
289                 if (sfx == Sfx_true)
290                     ItemSoundManager::playSfx(item, ItemSoundEvent::EQUIP);
291                 if (inventoryHandler != nullptr)
292                     inventoryHandler->equipItem(item);
293             }
294         }
295         else
296         {
297             if (mProtectedItems.find(item->getId()) == mProtectedItems.end())
298             {
299                 if (inventoryHandler != nullptr)
300                 {
301                     if (useType == 0)
302                         inventoryHandler->useItem(item);
303                     else
304                         inventoryHandler->useItem(item, useType);
305                 }
306                 if (sfx == Sfx_true)
307                     ItemSoundManager::playSfx(item, ItemSoundEvent::USE);
308             }
309         }
310     }
311 }
312 
useEquipItem2(const Item * const item,const int16_t useType,const Sfx sfx)313 void useEquipItem2(const Item *const item,
314                    const int16_t useType,
315                    const Sfx sfx)
316 {
317     if (item != nullptr)
318     {
319         if (item->isEquipment() == Equipm_false)
320         {
321             if (item->isEquipped() == Equipped_true)
322             {
323                 if (sfx == Sfx_true)
324                     ItemSoundManager::playSfx(item, ItemSoundEvent::UNEQUIP);
325                 if (inventoryHandler != nullptr)
326                     inventoryHandler->unequipItem(item);
327             }
328             else
329             {
330                 if (sfx == Sfx_true)
331                     ItemSoundManager::playSfx(item, ItemSoundEvent::EQUIP);
332                 if (inventoryHandler != nullptr)
333                     inventoryHandler->equipItem(item);
334             }
335         }
336         else
337         {
338             if (mProtectedItems.find(item->getId()) == mProtectedItems.end())
339             {
340                 if (sfx == Sfx_true)
341                     ItemSoundManager::playSfx(item, ItemSoundEvent::USE);
342                 if (inventoryHandler != nullptr)
343                 {
344                     if (useType == 0)
345                         inventoryHandler->useItem(item);
346                     else
347                         inventoryHandler->useItem(item, useType);
348                 }
349             }
350         }
351     }
352 }
353 
dropItem(const Item * const item,const int amount,const Sfx sfx)354 void dropItem(const Item *const item, const int amount, const Sfx sfx)
355 {
356     if (item != nullptr &&
357         mProtectedItems.find(item->getId()) == mProtectedItems.end())
358     {
359         if (sfx == Sfx_true)
360             ItemSoundManager::playSfx(item, ItemSoundEvent::DROP);
361         if (inventoryHandler != nullptr)
362             inventoryHandler->dropItem(item, amount);
363     }
364 }
365 
pickUpItem(const FloorItem * const item,const Sfx sfx)366 void pickUpItem(const FloorItem *const item, const Sfx sfx)
367 {
368     if (sfx == Sfx_true)
369         ItemSoundManager::playSfx(item, ItemSoundEvent::PICKUP);
370     if (playerHandler != nullptr)
371         playerHandler->pickUp(item);
372 }
373 
374 // --- Misc -------------------------------------------------------------------
375 
setBackend(const PlayerInfoBackend & backend)376 void setBackend(const PlayerInfoBackend &backend)
377 {
378     mData = backend;
379 }
380 
setCharId(const int charId)381 void setCharId(const int charId)
382 {
383     mCharId = charId;
384 }
385 
getCharId()386 int getCharId()
387 {
388     return mCharId;
389 }
390 
isTrading()391 Trading isTrading()
392 {
393     return mTrading;
394 }
395 
setTrading(const Trading trading)396 void setTrading(const Trading trading)
397 {
398     mTrading = trading;
399 }
400 
401 #define updateAttackStat(atk, delay, speed) \
402     attackDelay = getStatBase(delay); \
403     if (attackDelay != 0) \
404     { \
405         setStatBase(speed, \
406             getStatBase(atk) * 1000 / attackDelay, \
407             Notify_false); \
408         setStatMod(speed, \
409             getStatMod(atk) * 1000 / attackDelay, \
410             Notify_true); \
411     } \
412     else \
413     { \
414         setStatBase(speed, 0, \
415             Notify_false); \
416         setStatMod(speed, 0, \
417             Notify_true); \
418     }
419 
updateAttrs()420 void updateAttrs()
421 {
422     int attackDelay;
423     updateAttackStat(Attributes::PLAYER_ATK,
424         Attributes::PLAYER_ATTACK_DELAY,
425         Attributes::PLAYER_ATTACK_SPEED)
426     updateAttackStat(Attributes::HOMUN_ATK,
427         Attributes::HOMUN_ATTACK_DELAY,
428         Attributes::HOMUN_ATTACK_SPEED)
429     updateAttackStat(Attributes::MERC_ATK,
430         Attributes::MERC_ATTACK_DELAY,
431         Attributes::MERC_ATTACK_SPEED)
432 }
433 
init()434 void init()
435 {
436 }
437 
deinit()438 void deinit()
439 {
440     clearInventory();
441     delete2(mMercenary)
442     mPetBeingId = BeingId_zero;
443 }
444 
loadData()445 void loadData()
446 {
447     mProtectedItems.clear();
448     splitToIntSet(mProtectedItems,
449         serverConfig.getStringValue("protectedItems"), ',');
450 }
451 
clear()452 void clear()
453 {
454     mData.mSkills.clear();
455     mPetBeingId = BeingId_zero;
456 }
457 
isTalking()458 bool isTalking()
459 {
460     return NpcDialog::isActive() || InventoryWindow::isStorageActive();
461 }
462 
gameDestroyed()463 void gameDestroyed()
464 {
465     delete2(mInventory)
466     delete2(mEquipment)
467     delete2(mCartInventory)
468 }
469 
stateChange(const StateT state)470 void stateChange(const StateT state)
471 {
472     if (state == State::GAME)
473     {
474         if (mInventory == nullptr)
475         {
476             mInventory = new Inventory(InventoryType::Inventory,
477                 settings.fixedInventorySize);
478             mEquipment = new Equipment;
479             mCartInventory = new Inventory(InventoryType::Cart, -1);
480         }
481     }
482 }
483 
saveProtectedItems()484 static void saveProtectedItems()
485 {
486     std::string str;
487     std::set<int>::const_iterator it = mProtectedItems.begin();
488     std::set<int>::const_iterator it_end = mProtectedItems.end();
489     if (it != it_end)
490     {
491         str.append(toString(*it));
492         ++ it;
493     }
494     while (it != it_end)
495     {
496         str.append(",").append(toString(*it));
497         ++ it;
498     }
499     serverConfig.setValue("protectedItems", str);
500     serverConfig.write();
501 }
502 
protectItem(const int id)503 void protectItem(const int id)
504 {
505     mProtectedItems.insert(id);
506     saveProtectedItems();
507 }
508 
unprotectItem(const int id)509 void unprotectItem(const int id)
510 {
511     mProtectedItems.erase(id);
512     saveProtectedItems();
513 }
514 
isItemProtected(const int id)515 bool isItemProtected(const int id)
516 {
517     return mProtectedItems.find(id) != mProtectedItems.end();
518 }
519 
setMercenary(MercenaryInfo * const info)520 void setMercenary(MercenaryInfo *const info)
521 {
522     delete mMercenary;
523     mMercenary = info;
524 }
525 
setMercenaryBeing(Being * const being)526 void setMercenaryBeing(Being *const being)
527 {
528     if (being == nullptr ||
529         mMercenary == nullptr)
530     {
531         return;
532     }
533     being->setName(mMercenary->name);
534     being->setOwner(localPlayer);
535     being->setLevel(mMercenary->level);
536     being->setAttackRange(mMercenary->range);
537 }
538 
setElemental(const BeingId id)539 void setElemental(const BeingId id)
540 {
541     mElementalId = id;
542 }
543 
getElementalId()544 BeingId getElementalId()
545 {
546     return mElementalId;
547 }
548 
getMercenary()549 MercenaryInfo *getMercenary()
550 {
551     return mMercenary;
552 }
553 
setPet(PetInfo * const info)554 void setPet(PetInfo *const info)
555 {
556     delete mPet;
557     mPet = info;
558 }
559 
setPetBeing(Being * const being)560 void setPetBeing(Being *const being)
561 {
562     if (being != nullptr)
563         mPetBeingId = being->getId();
564     else
565         mPetBeingId = BeingId_zero;
566     if (being == nullptr ||
567         mPet == nullptr)
568     {
569         return;
570     }
571     being->setName(mPet->name);
572     being->setOwner(localPlayer);
573     being->setLevel(mPet->level);
574 }
575 
getPet()576 PetInfo *getPet()
577 {
578     return mPet;
579 }
580 
getPetBeingId()581 BeingId getPetBeingId()
582 {
583     return mPetBeingId;
584 }
585 
setHomunculus(HomunculusInfo * const info)586 void setHomunculus(HomunculusInfo *const info)
587 {
588     delete mHomunculus;
589     mHomunculus = info;
590 }
591 
setHomunculusBeing(Being * const being)592 void setHomunculusBeing(Being *const being)
593 {
594     if (being == nullptr ||
595         mHomunculus == nullptr)
596     {
597         return;
598     }
599     being->setName(mHomunculus->name);
600     being->setOwner(localPlayer);
601 }
602 
getHomunculus()603 HomunculusInfo *getHomunculus()
604 {
605     return mHomunculus;
606 }
607 
getHomunculusId()608 BeingId getHomunculusId()
609 {
610     return mHomunculus != nullptr ? mHomunculus->id : BeingId_zero;
611 }
612 
getMercenaryId()613 BeingId getMercenaryId()
614 {
615     return mMercenary != nullptr ? mMercenary->id : BeingId_zero;
616 }
617 
updateAttackAi(const BeingId targetId,const Keep keep)618 void updateAttackAi(const BeingId targetId,
619                     const Keep keep)
620 {
621     if (mMercenary != nullptr &&
622         mercenaryHandler != nullptr)
623     {
624         mercenaryHandler->attack(targetId, keep);
625     }
626     if (mHomunculus != nullptr &&
627         homunculusHandler != nullptr)
628     {
629         homunculusHandler->attack(targetId, keep);
630     }
631 }
632 
getRoomName()633 std::string getRoomName()
634 {
635     return mRoomName;
636 }
637 
setRoomName(const std::string & name)638 void setRoomName(const std::string &name)
639 {
640     mRoomName = name;
641 }
642 
isInRoom()643 bool isInRoom()
644 {
645     return !mRoomName.empty();
646 }
647 
setGuildPositionFlags(const GuildPositionFlags::Type pos)648 void setGuildPositionFlags(const GuildPositionFlags::Type pos)
649 {
650     mGuildPositionFlags = pos;
651 }
652 
getGuildPositionFlags()653 GuildPositionFlags::Type getGuildPositionFlags()
654 {
655     return mGuildPositionFlags;
656 }
657 
enableVending(const bool b)658 void enableVending(const bool b)
659 {
660     mVending = b;
661 }
662 
isVending()663 bool isVending()
664 {
665     return mVending;
666 }
667 
setServerLanguage(const int lang)668 void setServerLanguage(const int lang)
669 {
670     if (lang != mServerLanguage)
671     {
672         mServerLanguage = lang;
673         TranslationManager::loadDictionaryLang();
674     }
675 }
676 
getServerLanguage()677 int getServerLanguage()
678 {
679     return mServerLanguage;
680 }
681 
682 }  // namespace PlayerInfo
683