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