1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2004-2009  The Mana World Development Team
4  *  Copyright (C) 2009-2010  The Mana Developers
5  *  Copyright (C) 2011-2019  The ManaPlus Developers
6  *  Copyright (C) 2019-2021  Andrei Karas
7  *
8  *  This file is part of The ManaPlus Client.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "gui/windows/npcdialog.h"
25 
26 #include "actormanager.h"
27 #include "configuration.h"
28 #include "settings.h"
29 #include "soundmanager.h"
30 
31 #include "const/sound.h"
32 
33 #include "being/being.h"
34 #include "being/playerinfo.h"
35 
36 #include "enums/gui/layouttype.h"
37 
38 #include "gui/gui.h"
39 #include "gui/viewport.h"
40 
41 #include "gui/fonts/font.h"
42 
43 #include "gui/popups/popupmenu.h"
44 
45 #include "gui/windows/cutinwindow.h"
46 #include "gui/windows/inventorywindow.h"
47 
48 #include "gui/widgets/browserbox.h"
49 #include "gui/widgets/button.h"
50 #include "gui/widgets/createwidget.h"
51 #include "gui/widgets/icon.h"
52 #include "gui/widgets/inttextfield.h"
53 #include "gui/widgets/itemcontainer.h"
54 #include "gui/widgets/itemlinkhandler.h"
55 #include "gui/widgets/layout.h"
56 #include "gui/widgets/extendedlistbox.h"
57 #include "gui/widgets/playerbox.h"
58 #include "gui/widgets/scrollarea.h"
59 
60 #include "resources/npcdialoginfo.h"
61 
62 #include "resources/db/avatardb.h"
63 #include "resources/db/npcdb.h"
64 #include "resources/db/npcdialogdb.h"
65 
66 #include "resources/inventory/complexinventory.h"
67 
68 #include "resources/item/complexitem.h"
69 
70 #include "resources/loaders/imageloader.h"
71 
72 #include "net/npchandler.h"
73 #include "net/packetlimiter.h"
74 
75 #include "utils/copynpaste.h"
76 #include "utils/delete2.h"
77 #include "utils/foreach.h"
78 #include "utils/gettext.h"
79 
80 #include <sstream>
81 
82 #include "debug.h"
83 
84 // TRANSLATORS: npc dialog button
85 #define CAPTION_WAITING _("Stop waiting")
86 // TRANSLATORS: npc dialog button
87 #define CAPTION_NEXT _("Next")
88 // TRANSLATORS: npc dialog button
89 #define CAPTION_CLOSE _("Close")
90 // TRANSLATORS: npc dialog button
91 #define CAPTION_SUBMIT _("Submit")
92 
93 NpcDialog::DialogList NpcDialog::instances;
94 NpcDialogs NpcDialog::mNpcDialogs;
95 
96 typedef STD_VECTOR<Image *>::iterator ImageVectorIter;
97 
NpcDialog(const BeingId npcId)98 NpcDialog::NpcDialog(const BeingId npcId) :
99     // TRANSLATORS: npc dialog name
100     Window(_("NPC"), Modal_false, nullptr, "npc.xml"),
101     ActionListener(),
102     mNpcId(npcId),
103     mDefaultInt(0),
104     mDefaultString(),
105     mTextBox(new BrowserBox(this, Opaque_true,
106         "browserbox.xml")),
107     mScrollArea(new ScrollArea(this, mTextBox,
108         fromBool(getOptionBool("showtextbackground", false), Opaque),
109         "npc_textbackground.xml")),
110     mText(),
111     mNewText(),
112     mItems(),
113     mImages(),
114     mItemList(CREATEWIDGETR(ExtendedListBox,
115         this, this, "extendedlistbox.xml", 13)),
116     mListScrollArea(new ScrollArea(this, mItemList,
117         fromBool(getOptionBool("showlistbackground", false), Opaque),
118         "npc_listbackground.xml")),
119     mSkinContainer(new Container(this)),
120     mSkinScrollArea(new ScrollArea(this, mSkinContainer,
121         fromBool(getOptionBool("showlistbackground", false), Opaque),
122         "npc_listbackground.xml")),
123     mItemLinkHandler(new ItemLinkHandler),
124     mTextField(new TextField(this, std::string(), LoseFocusOnTab_true,
125         nullptr, std::string(), false)),
126     mIntField(new IntTextField(this, 0, 0, 0, Enable_true, 0)),
127     // TRANSLATORS: npc dialog button
128     mPlusButton(new Button(this, _("+"), "inc", BUTTON_SKIN, this)),
129     // TRANSLATORS: npc dialog button
130     mMinusButton(new Button(this, _("-"), "dec", BUTTON_SKIN, this)),
131     // TRANSLATORS: npc dialog button
132     mClearButton(new Button(this, _("Clear"), "clear", BUTTON_SKIN, this)),
133     mButton(new Button(this, "", "ok", BUTTON_SKIN, this)),
134     // TRANSLATORS: npc dialog button
135     mButton2(new Button(this, _("Close"), "close", BUTTON_SKIN, this)),
136     // TRANSLATORS: npc dialog button
137     mButton3(new Button(this, _("Add"), "add", BUTTON_SKIN, this)),
138     // TRANSLATORS: npc dialog button
139     mResetButton(new Button(this, _("Reset"), "reset", BUTTON_SKIN, this)),
140     mInventory(new Inventory(InventoryType::Npc, 1)),
141     mComplexInventory(new ComplexInventory(InventoryType::Craft, 1)),
142     mItemContainer(new ItemContainer(this, mInventory,
143         10000, ShowEmptyRows_true, ForceQuantity_false)),
144     mItemScrollArea(new ScrollArea(this, mItemContainer,
145         fromBool(getOptionBool("showitemsbackground", false), Opaque),
146         "npc_listbackground.xml")),
147     mInputState(NpcInputState::NONE),
148     mActionState(NpcActionState::WAIT),
149     mSkinControls(),
150     mSkinName(),
151     mPlayerBox(new PlayerBox(nullptr, std::string(), std::string())),
152     mAvatarBeing(nullptr),
153     mDialogInfo(nullptr),
154     mLastNextTime(0),
155     mCameraMode(-1),
156     mCameraX(0),
157     mCameraY(0),
158     mShowAvatar(false),
159     mLogInteraction(config.getBoolValue("logNpcInGui"))
160 {
161     // Basic Window Setup
162     setWindowName("NpcText");
163     setResizable(true);
164     setFocusable(true);
165     setStickyButtonLock(true);
166 
167     setMinWidth(200);
168     setMinHeight(150);
169 
170     setDefaultSize(300, 578, ImagePosition::LOWER_LEFT, 0, 0);
171 
172     mPlayerBox->setWidth(70);
173     mPlayerBox->setHeight(100);
174 
175     // Setup output text box
176     mTextBox->setOpaque(Opaque_false);
177     mTextBox->setMaxRow(config.getIntValue("ChatLogLength"));
178     mTextBox->setLinkHandler(mItemLinkHandler);
179     mTextBox->setProcessVars(true);
180     mTextBox->setFont(gui->getNpcFont());
181     mTextBox->setEnableKeys(true);
182     mTextBox->setEnableTabs(true);
183     mTextBox->setEnableImages(true);
184 
185     mScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
186     mScrollArea->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
187 
188     // Setup listbox
189     mItemList->setWrappingEnabled(true);
190     mItemList->setActionEventId("ok");
191     mItemList->addActionListener(this);
192     mItemList->setDistributeMousePressed(false);
193     mItemList->setFont(gui->getNpcFont());
194     if (gui->getNpcFont()->getHeight() < 20)
195         mItemList->setRowHeight(20);
196     else
197         mItemList->setRowHeight(gui->getNpcFont()->getHeight());
198 
199     setContentSize(260, 175);
200     mListScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
201     mItemScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
202     mSkinScrollArea->setScrollPolicy(ScrollArea::SHOW_NEVER,
203         ScrollArea::SHOW_NEVER);
204     mItemList->setVisible(Visible_true);
205     mTextField->setVisible(Visible_true);
206     mIntField->setVisible(Visible_true);
207 
208     const Font *const fnt = mButton->getFont();
209     int width = std::max(fnt->getWidth(CAPTION_WAITING),
210         fnt->getWidth(CAPTION_NEXT));
211     width = std::max(width, fnt->getWidth(CAPTION_CLOSE));
212     width = std::max(width, fnt->getWidth(CAPTION_SUBMIT));
213     mButton->setWidth(8 + width);
214 
215     // Place widgets
216     buildLayout();
217 
218     center();
219     loadWindowState();
220 
221     instances.push_back(this);
222 }
223 
postInit()224 void NpcDialog::postInit()
225 {
226     Window::postInit();
227     setVisible(Visible_true);
228     requestFocus();
229     enableVisibleSound(true);
230     soundManager.playGuiSound(SOUND_SHOW_WINDOW);
231 
232     if (actorManager != nullptr)
233     {
234         const Being *const being = actorManager->findBeing(mNpcId);
235         if (being != nullptr)
236         {
237             showAvatar(NPCDB::getAvatarFor(fromInt(
238                 being->getSubType(), BeingTypeId)));
239             setCaption(being->getName());
240         }
241     }
242 
243     config.addListener("logNpcInGui", this);
244 }
245 
~NpcDialog()246 NpcDialog::~NpcDialog()
247 {
248     config.removeListeners(this);
249     CHECKLISTENERS
250     clearLayout();
251 
252     if (mPlayerBox != nullptr)
253     {
254         delete mPlayerBox->getBeing();
255         delete mPlayerBox;
256     }
257 
258     deleteSkinControls();
259 
260     delete2(mTextBox)
261     delete2(mClearButton)
262     delete2(mButton)
263     delete2(mButton2)
264     delete2(mButton3)
265     delete2(mScrollArea)
266     delete2(mItemList)
267     delete2(mTextField)
268     delete2(mIntField)
269     delete2(mResetButton)
270     delete2(mPlusButton)
271     delete2(mMinusButton)
272     delete2(mItemLinkHandler)
273     delete2(mItemContainer)
274     delete2(mInventory)
275     delete2(mComplexInventory)
276     delete2(mItemScrollArea)
277     delete2(mListScrollArea)
278     delete2(mSkinScrollArea)
279 
280     FOR_EACH (ImageVectorIter, it, mImages)
281     {
282         if (*it != nullptr)
283             (*it)->decRef();
284     }
285 
286     mImages.clear();
287 
288     instances.remove(this);
289 }
290 
addText(const std::string & text,const bool save)291 void NpcDialog::addText(const std::string &text, const bool save)
292 {
293     if (save || mLogInteraction)
294     {
295         if (mText.size() > 5000)
296             mText.clear();
297 
298         mNewText.append(text);
299         mTextBox->addRow(text,
300             false);
301     }
302     mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
303     mActionState = NpcActionState::WAIT;
304     buildLayout();
305 }
306 
showNextButton()307 void NpcDialog::showNextButton()
308 {
309     mActionState = NpcActionState::NEXT;
310     buildLayout();
311 }
312 
showCloseButton()313 void NpcDialog::showCloseButton()
314 {
315     mActionState = NpcActionState::CLOSE;
316     buildLayout();
317 }
318 
action(const ActionEvent & event)319 void NpcDialog::action(const ActionEvent &event)
320 {
321     const std::string &eventId = event.getId();
322     if (eventId == "ok")
323     {
324         if (mActionState == NpcActionState::NEXT)
325         {
326             if (!PacketLimiter::limitPackets(PacketType::PACKET_NPC_NEXT))
327                 return;
328 
329             nextDialog();
330             addText(std::string(), false);
331         }
332         else if (mActionState == NpcActionState::CLOSE
333                  || mActionState == NpcActionState::WAIT)
334         {
335             if (cutInWindow != nullptr)
336                 cutInWindow->hide();
337             closeDialog();
338         }
339         else if (mActionState == NpcActionState::INPUT)
340         {
341             std::string printText;  // Text that will get printed
342                                     // in the textbox
343             switch (mInputState)
344             {
345                 case NpcInputState::LIST:
346                 {
347                     if (mDialogInfo != nullptr)
348                         return;
349                     if (gui != nullptr)
350                         gui->resetClickCount();
351                     const int selectedIndex = mItemList->getSelected();
352 
353                     if (selectedIndex >= CAST_S32(mItems.size())
354                         || selectedIndex < 0
355                         || !PacketLimiter::limitPackets(
356                         PacketType::PACKET_NPC_INPUT))
357                     {
358                         return;
359                     }
360                     unsigned char choice = CAST_U8(
361                         selectedIndex + 1);
362                     printText = mItems[selectedIndex];
363 
364                     npcHandler->listInput(mNpcId, choice);
365                     break;
366                 }
367                 case NpcInputState::STRING:
368                 {
369                     if (!PacketLimiter::limitPackets(
370                         PacketType::PACKET_NPC_INPUT))
371                     {
372                         return;
373                     }
374                     printText = mTextField->getText();
375                     npcHandler->stringInput(mNpcId, printText);
376                     break;
377                 }
378                 case NpcInputState::INTEGER:
379                 {
380                     if (!PacketLimiter::limitPackets(
381                         PacketType::PACKET_NPC_INPUT))
382                     {
383                         return;
384                     }
385                     printText = strprintf("%d", mIntField->getValue());
386                     npcHandler->integerInput(
387                         mNpcId, mIntField->getValue());
388                     break;
389                 }
390                 case NpcInputState::ITEM:
391                 {
392                     restoreVirtuals();
393                     if (!PacketLimiter::limitPackets(
394                         PacketType::PACKET_NPC_INPUT))
395                     {
396                         return;
397                     }
398 
399                     std::string str;
400                     const int sz = mInventory->getSize();
401                     if (sz == 0)
402                     {
403                         str = "0,0";
404                     }
405                     else
406                     {
407                         const Item *item = mInventory->getItem(0);
408                         if (item != nullptr)
409                         {
410                             str = strprintf("%d,%d", item->getId(),
411                                 toInt(item->getColor(), int));
412                         }
413                         else
414                         {
415                             str = "0,0";
416                         }
417                         for (int f = 1; f < sz; f ++)
418                         {
419                             str.append(";");
420                             item = mInventory->getItem(f);
421                             if (item != nullptr)
422                             {
423                                 str.append(strprintf("%d,%d", item->getId(),
424                                     toInt(item->getColor(), int)));
425                             }
426                             else
427                             {
428                                 str.append("0,0");
429                             }
430                         }
431                     }
432 
433                     // need send selected item
434                     npcHandler->stringInput(mNpcId, str);
435                     mInventory->clear();
436                     break;
437                 }
438                 case NpcInputState::ITEM_INDEX:
439                 {
440                     restoreVirtuals();
441                     if (!PacketLimiter::limitPackets(
442                         PacketType::PACKET_NPC_INPUT))
443                     {
444                         return;
445                     }
446 
447                     std::string str;
448                     const int sz = mInventory->getSize();
449                     if (sz == 0)
450                     {
451                         str = "-1";
452                     }
453                     else
454                     {
455                         const Item *item = mInventory->getItem(0);
456                         if (item != nullptr)
457                         {
458                             str = strprintf("%d", item->getTag());
459                         }
460                         else
461                         {
462                             str = "-1";
463                         }
464                         for (int f = 1; f < sz; f ++)
465                         {
466                             str.append(";");
467                             item = mInventory->getItem(f);
468                             if (item != nullptr)
469                                 str.append(strprintf("%d", item->getTag()));
470                             else
471                                 str.append("-1");
472                         }
473                     }
474 
475                     // need send selected item
476                     npcHandler->stringInput(mNpcId, str);
477                     mInventory->clear();
478                     break;
479                 }
480                 case NpcInputState::ITEM_CRAFT:
481                 {
482                     restoreVirtuals();
483                     if (!PacketLimiter::limitPackets(
484                         PacketType::PACKET_NPC_INPUT))
485                     {
486                         return;
487                     }
488 
489                     std::string str;
490                     const int sz = mComplexInventory->getSize();
491                     if (sz == 0)
492                     {
493                         str.clear();
494                     }
495                     else
496                     {
497                         const ComplexItem *item = dynamic_cast<ComplexItem*>(
498                             mComplexInventory->getItem(0));
499                         str = complexItemToStr(item);
500                         for (int f = 1; f < sz; f ++)
501                         {
502                             str.append("|");
503                             item = dynamic_cast<ComplexItem*>(
504                                 mComplexInventory->getItem(f));
505                             str.append(complexItemToStr(item));
506                         }
507                     }
508 
509                     // need send selected item
510                     npcHandler->stringInput(mNpcId, str);
511                     mInventory->clear();
512                     break;
513                 }
514 
515                 case NpcInputState::NONE:
516                 default:
517                     break;
518             }
519             if (mInputState != NpcInputState::ITEM &&
520                 mInputState != NpcInputState::ITEM_INDEX &&
521                 mInputState != NpcInputState::ITEM_CRAFT)
522             {
523                 // addText will auto remove the input layout
524                 addText(strprintf("> \"%s\"", printText.c_str()), false);
525             }
526             mNewText.clear();
527         }
528 
529         if (!mLogInteraction)
530             mTextBox->clearRows();
531     }
532     else if (eventId == "reset")
533     {
534         switch (mInputState)
535         {
536             case NpcInputState::STRING:
537                 mTextField->setText(mDefaultString);
538                 break;
539             case NpcInputState::INTEGER:
540                 mIntField->setValue(mDefaultInt);
541                 break;
542             case NpcInputState::ITEM:
543             case NpcInputState::ITEM_INDEX:
544                 mInventory->clear();
545                 break;
546             case NpcInputState::ITEM_CRAFT:
547                 mComplexInventory->clear();
548                 break;
549             case NpcInputState::NONE:
550             case NpcInputState::LIST:
551             default:
552                 break;
553         }
554     }
555     else if (eventId == "inc")
556     {
557         mIntField->setValue(mIntField->getValue() + 1);
558     }
559     else if (eventId == "dec")
560     {
561         mIntField->setValue(mIntField->getValue() - 1);
562     }
563     else if (eventId == "clear")
564     {
565         switch (mInputState)
566         {
567             case NpcInputState::ITEM:
568             case NpcInputState::ITEM_INDEX:
569                 mInventory->clear();
570                 break;
571             case NpcInputState::ITEM_CRAFT:
572                 mComplexInventory->clear();
573                 break;
574             case NpcInputState::STRING:
575             case NpcInputState::INTEGER:
576             case NpcInputState::LIST:
577             case NpcInputState::NONE:
578             default:
579                 clearRows();
580                 break;
581         }
582     }
583     else if (eventId == "close")
584     {
585         restoreVirtuals();
586         if (mActionState == NpcActionState::INPUT)
587         {
588             switch (mInputState)
589             {
590                 case NpcInputState::ITEM:
591                     npcHandler->stringInput(mNpcId, "0,0");
592                     break;
593                 case NpcInputState::ITEM_INDEX:
594                     npcHandler->stringInput(mNpcId, "-1");
595                     break;
596                 case NpcInputState::ITEM_CRAFT:
597                     npcHandler->stringInput(mNpcId, "");
598                     break;
599                 case NpcInputState::STRING:
600                 case NpcInputState::INTEGER:
601                 case NpcInputState::NONE:
602                 case NpcInputState::LIST:
603                 default:
604                     npcHandler->listInput(mNpcId, 255);
605                     break;
606             }
607             if (cutInWindow != nullptr)
608                 cutInWindow->hide();
609             closeDialog();
610         }
611     }
612     else if (eventId == "add")
613     {
614         if (inventoryWindow != nullptr)
615         {
616             Item *const item = inventoryWindow->getSelectedItem();
617             Inventory *const inventory = PlayerInfo::getInventory();
618             if (inventory != nullptr)
619             {
620                 if (mInputState == NpcInputState::ITEM_CRAFT)
621                 {
622                     if (mComplexInventory->addVirtualItem(item, -1, 1))
623                         inventory->virtualRemove(item, 1);
624                 }
625                 else
626                 {
627                     if (mInventory->addVirtualItem(item, -1, 1))
628                         inventory->virtualRemove(item, 1);
629                 }
630             }
631         }
632     }
633     else if (eventId.find("skin_") == 0)
634     {
635         const std::string cmd = eventId.substr(5);
636         std::string printText;
637         int cnt = 0;
638         FOR_EACH (StringVectCIter, it, mItems)
639         {
640             if (cmd == *it)
641             {
642                 npcHandler->listInput(mNpcId, CAST_U8(cnt + 1));
643                 printText = mItems[cnt];
644 
645                 if (mInputState != NpcInputState::ITEM &&
646                     mInputState != NpcInputState::ITEM_INDEX &&
647                     mInputState != NpcInputState::ITEM_CRAFT)
648                 {
649                     // addText will auto remove the input layout
650                     addText(strprintf("> \"%s\"", printText.c_str()), false);
651                 }
652                 mNewText.clear();
653                 break;
654             }
655             cnt ++;
656         }
657     }
658 }
659 
nextDialog()660 void NpcDialog::nextDialog()
661 {
662     npcHandler->nextDialog(mNpcId);
663 }
664 
closeDialog()665 void NpcDialog::closeDialog()
666 {
667     restoreCamera();
668     npcHandler->closeDialog(mNpcId);
669 }
670 
getNumberOfElements()671 int NpcDialog::getNumberOfElements()
672 {
673     return CAST_S32(mItems.size());
674 }
675 
getElementAt(int i)676 std::string NpcDialog::getElementAt(int i)
677 {
678     return mItems[i];
679 }
680 
getImageAt(int i)681 const Image *NpcDialog::getImageAt(int i)
682 {
683     return mImages[i];
684 }
685 
choiceRequest()686 void NpcDialog::choiceRequest()
687 {
688     mItems.clear();
689     FOR_EACH (ImageVectorIter, it, mImages)
690     {
691         if (*it != nullptr)
692             (*it)->decRef();
693     }
694     mImages.clear();
695     mActionState = NpcActionState::INPUT;
696     mInputState = NpcInputState::LIST;
697     buildLayout();
698 }
699 
addChoice(const std::string & choice)700 void NpcDialog::addChoice(const std::string &choice)
701 {
702     mItems.push_back(choice);
703     mImages.push_back(nullptr);
704 }
705 
parseListItems(const std::string & itemString)706 void NpcDialog::parseListItems(const std::string &itemString)
707 {
708     std::istringstream iss(itemString);
709     std::string tmp;
710     const std::string path = paths.getStringValue("guiIcons");
711     while (getline(iss, tmp, ':'))
712     {
713         if (tmp.empty())
714             continue;
715         const size_t pos = tmp.find('|');
716         if (pos == std::string::npos)
717         {
718             mItems.push_back(tmp);
719             mImages.push_back(nullptr);
720         }
721         else
722         {
723             mItems.push_back(tmp.substr(pos + 1));
724             Image *const img = Loader::getImage(pathJoin(path,
725                 std::string(tmp.substr(0, pos)).append(".png")));
726             mImages.push_back(img);
727         }
728     }
729 
730     if (!mItems.empty())
731     {
732         mItemList->setSelected(0);
733         mItemList->requestFocus();
734     }
735     else
736     {
737         mItemList->setSelected(-1);
738     }
739 }
740 
refocus()741 void NpcDialog::refocus()
742 {
743     if (!mItems.empty())
744         mItemList->refocus();
745 }
746 
textRequest(const std::string & defaultText)747 void NpcDialog::textRequest(const std::string &defaultText)
748 {
749     mActionState = NpcActionState::INPUT;
750     mInputState = NpcInputState::STRING;
751     mDefaultString = defaultText;
752     mTextField->setText(defaultText);
753 
754     buildLayout();
755 }
756 
isTextInputFocused() const757 bool NpcDialog::isTextInputFocused() const
758 {
759     return mTextField->isFocused();
760 }
761 
isInputFocused() const762 bool NpcDialog::isInputFocused() const
763 {
764     return mTextField->isFocused() || mIntField->isFocused()
765         || mItemList->isFocused();
766 }
767 
isAnyInputFocused()768 bool NpcDialog::isAnyInputFocused()
769 {
770     FOR_EACH (DialogList::const_iterator, it, instances)
771     {
772         if (((*it) != nullptr) && (*it)->isInputFocused())
773             return true;
774     }
775 
776     return false;
777 }
778 
integerRequest(const int defaultValue,const int min,const int max)779 void NpcDialog::integerRequest(const int defaultValue,
780                                const int min,
781                                const int max)
782 {
783     mActionState = NpcActionState::INPUT;
784     mInputState = NpcInputState::INTEGER;
785     mDefaultInt = defaultValue;
786     mIntField->setRange(min, max);
787     mIntField->setValue(defaultValue);
788     buildLayout();
789 }
790 
itemRequest(const int size)791 void NpcDialog::itemRequest(const int size)
792 {
793     mActionState = NpcActionState::INPUT;
794     mInputState = NpcInputState::ITEM;
795     mInventory->resize(size);
796     buildLayout();
797 }
798 
itemIndexRequest(const int size)799 void NpcDialog::itemIndexRequest(const int size)
800 {
801     mActionState = NpcActionState::INPUT;
802     mInputState = NpcInputState::ITEM_INDEX;
803     mInventory->resize(size);
804     buildLayout();
805 }
806 
itemCraftRequest(const int size)807 void NpcDialog::itemCraftRequest(const int size)
808 {
809     mActionState = NpcActionState::INPUT;
810     mInputState = NpcInputState::ITEM_CRAFT;
811     mComplexInventory->resize(size);
812     buildLayout();
813 }
814 
move(const int amount)815 void NpcDialog::move(const int amount)
816 {
817     if (mActionState != NpcActionState::INPUT)
818         return;
819 
820     switch (mInputState)
821     {
822         case NpcInputState::INTEGER:
823             mIntField->setValue(mIntField->getValue() + amount);
824             break;
825         case NpcInputState::LIST:
826             mItemList->setSelected(mItemList->getSelected() - amount);
827             break;
828         case NpcInputState::NONE:
829         case NpcInputState::STRING:
830         case NpcInputState::ITEM:
831         case NpcInputState::ITEM_INDEX:
832         case NpcInputState::ITEM_CRAFT:
833         default:
834             break;
835     }
836 }
837 
setVisible(Visible visible)838 void NpcDialog::setVisible(Visible visible)
839 {
840     Window::setVisible(visible);
841 
842     if (visible == Visible_false)
843         scheduleDelete();
844 }
845 
optionChanged(const std::string & name)846 void NpcDialog::optionChanged(const std::string &name)
847 {
848     if (name == "logNpcInGui")
849         mLogInteraction = config.getBoolValue("logNpcInGui");
850 }
851 
getActive()852 NpcDialog *NpcDialog::getActive()
853 {
854     if (instances.size() == 1)
855         return instances.front();
856 
857     FOR_EACH (DialogList::const_iterator, it, instances)
858     {
859         if (((*it) != nullptr) && (*it)->isFocused())
860             return (*it);
861     }
862 
863     return nullptr;
864 }
865 
closeAll()866 void NpcDialog::closeAll()
867 {
868     FOR_EACH (DialogList::const_iterator, it, instances)
869     {
870         if (*it != nullptr)
871             (*it)->close();
872     }
873 }
874 
placeNormalControls()875 void NpcDialog::placeNormalControls()
876 {
877     if (mShowAvatar)
878     {
879         place(0, 0, mPlayerBox, 1, 1);
880         place(1, 0, mScrollArea, 5, 3);
881         place(4, 3, mClearButton, 1, 1);
882         place(5, 3, mButton, 1, 1);
883     }
884     else
885     {
886         place(0, 0, mScrollArea, 5, 3);
887         place(3, 3, mClearButton, 1, 1);
888         place(4, 3, mButton, 1, 1);
889     }
890 }
891 
placeMenuControls()892 void NpcDialog::placeMenuControls()
893 {
894     if (mShowAvatar)
895     {
896         place(0, 0, mPlayerBox, 1, 1);
897         place(1, 0, mScrollArea, 6, 3);
898         place(0, 3, mListScrollArea, 7, 3);
899         place(1, 6, mButton2, 2, 1);
900         place(3, 6, mClearButton, 2, 1);
901         place(5, 6, mButton, 2, 1);
902     }
903     else
904     {
905         place(0, 0, mScrollArea, 6, 3);
906         place(0, 3, mListScrollArea, 6, 3);
907         place(0, 6, mButton2, 2, 1);
908         place(2, 6, mClearButton, 2, 1);
909         place(4, 6, mButton, 2, 1);
910     }
911 }
912 
placeSkinControls()913 void NpcDialog::placeSkinControls()
914 {
915     createSkinControls();
916     if ((mDialogInfo != nullptr) && mDialogInfo->hideText)
917     {
918         if (mShowAvatar)
919         {
920             place(0, 0, mPlayerBox, 1, 1);
921             place(1, 0, mSkinScrollArea, 7, 3);
922             place(1, 3, mButton2, 2, 1);
923         }
924         else
925         {
926             place(0, 0, mSkinScrollArea, 6, 3);
927             place(0, 3, mButton2, 2, 1);
928         }
929     }
930     else
931     {
932         if (mShowAvatar)
933         {
934             place(0, 0, mPlayerBox, 1, 1);
935             place(1, 0, mScrollArea, 6, 3);
936             place(0, 3, mSkinScrollArea, 7, 3);
937             place(1, 6, mButton2, 2, 1);
938         }
939         else
940         {
941             place(0, 0, mScrollArea, 6, 3);
942             place(0, 3, mSkinScrollArea, 6, 3);
943             place(0, 6, mButton2, 2, 1);
944         }
945     }
946 }
947 
placeTextInputControls()948 void NpcDialog::placeTextInputControls()
949 {
950     if (mShowAvatar)
951     {
952         place(0, 0, mPlayerBox, 1, 1);
953         place(1, 0, mScrollArea, 6, 3);
954         place(1, 3, mTextField, 6, 1);
955         place(1, 4, mResetButton, 2, 1);
956         place(3, 4, mClearButton, 2, 1);
957         place(5, 4, mButton, 2, 1);
958     }
959     else
960     {
961         place(0, 0, mScrollArea, 6, 3);
962         place(0, 3, mTextField, 6, 1);
963         place(0, 4, mResetButton, 2, 1);
964         place(2, 4, mClearButton, 2, 1);
965         place(4, 4, mButton, 2, 1);
966     }
967 }
968 
placeIntInputControls()969 void NpcDialog::placeIntInputControls()
970 {
971     if (mShowAvatar)
972     {
973         place(0, 0, mPlayerBox, 1, 1);
974         place(1, 0, mScrollArea, 6, 3);
975         place(1, 3, mMinusButton, 1, 1);
976         place(2, 3, mIntField, 4, 1);
977         place(6, 3, mPlusButton, 1, 1);
978         place(1, 4, mResetButton, 2, 1);
979         place(3, 4, mClearButton, 2, 1);
980         place(5, 4, mButton, 2, 1);
981     }
982     else
983     {
984         place(0, 0, mScrollArea, 6, 3);
985         place(0, 3, mMinusButton, 1, 1);
986         place(1, 3, mIntField, 4, 1);
987         place(5, 3, mPlusButton, 1, 1);
988         place(0, 4, mResetButton, 2, 1);
989         place(2, 4, mClearButton, 2, 1);
990         place(4, 4, mButton, 2, 1);
991     }
992 }
993 
placeItemInputControls()994 void NpcDialog::placeItemInputControls()
995 {
996     if (mDialogInfo != nullptr)
997     {
998         mItemContainer->setCellBackgroundImage(mDialogInfo->inventory.cell);
999         mItemContainer->setMaxColumns(mDialogInfo->inventory.columns);
1000     }
1001     else
1002     {
1003         mItemContainer->setCellBackgroundImage("inventory_cell.xml");
1004         mItemContainer->setMaxColumns(10000);
1005     }
1006 
1007     if (mInputState == NpcInputState::ITEM_CRAFT)
1008         mItemContainer->setInventory(mComplexInventory);
1009     else
1010         mItemContainer->setInventory(mInventory);
1011 
1012     if ((mDialogInfo != nullptr) && mDialogInfo->hideText)
1013     {
1014         if (mShowAvatar)
1015         {
1016             place(0, 0, mPlayerBox, 1, 1);
1017             place(1, 0, mItemScrollArea, 7, 3);
1018             place(1, 3, mButton3, 2, 1);
1019             place(3, 3, mClearButton, 2, 1);
1020             place(5, 3, mButton, 2, 1);
1021         }
1022         else
1023         {
1024             place(0, 0, mItemScrollArea, 6, 3);
1025             place(0, 3, mButton3, 2, 1);
1026             place(2, 3, mClearButton, 2, 1);
1027             place(4, 3, mButton, 2, 1);
1028         }
1029     }
1030     else
1031     {
1032         if (mShowAvatar)
1033         {
1034             place(0, 0, mPlayerBox, 1, 1);
1035             place(1, 0, mScrollArea, 6, 3);
1036             place(0, 3, mItemScrollArea, 7, 3);
1037             place(1, 6, mButton3, 2, 1);
1038             place(3, 6, mClearButton, 2, 1);
1039             place(5, 6, mButton, 2, 1);
1040         }
1041         else
1042         {
1043             place(0, 0, mScrollArea, 6, 3);
1044             place(0, 3, mItemScrollArea, 6, 3);
1045             place(0, 6, mButton3, 2, 1);
1046             place(2, 6, mClearButton, 2, 1);
1047             place(4, 6, mButton, 2, 1);
1048         }
1049     }
1050 }
1051 
buildLayout()1052 void NpcDialog::buildLayout()
1053 {
1054     clearLayout();
1055 
1056     if (mActionState != NpcActionState::INPUT)
1057     {
1058         if (mActionState == NpcActionState::WAIT)
1059             mButton->setCaption(CAPTION_WAITING);
1060         else if (mActionState == NpcActionState::NEXT)
1061             mButton->setCaption(CAPTION_NEXT);
1062         else if (mActionState == NpcActionState::CLOSE)
1063             mButton->setCaption(CAPTION_CLOSE);
1064         placeNormalControls();
1065     }
1066     else if (mInputState != NpcInputState::NONE)
1067     {
1068         mButton->setCaption(CAPTION_SUBMIT);
1069         switch (mInputState)
1070         {
1071             case NpcInputState::LIST:
1072                 if (mDialogInfo == nullptr)
1073                     placeMenuControls();
1074                 else
1075                     placeSkinControls();
1076                 mItemList->setSelected(-1);
1077                 break;
1078 
1079             case NpcInputState::STRING:
1080                 placeTextInputControls();
1081                 break;
1082 
1083             case NpcInputState::INTEGER:
1084                 placeIntInputControls();
1085                 break;
1086 
1087             case NpcInputState::ITEM:
1088             case NpcInputState::ITEM_INDEX:
1089             case NpcInputState::ITEM_CRAFT:
1090                 placeItemInputControls();
1091                 break;
1092 
1093             case NpcInputState::NONE:
1094             default:
1095                 break;
1096         }
1097     }
1098 
1099     Layout &layout = getLayout();
1100     layout.setRowHeight(1, LayoutType::SET);
1101     redraw();
1102     mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
1103 }
1104 
saveCamera()1105 void NpcDialog::saveCamera()
1106 {
1107     if ((viewport == nullptr) || mCameraMode >= 0)
1108         return;
1109 
1110     mCameraMode = CAST_S32(settings.cameraMode);
1111     mCameraX = viewport->getCameraRelativeX();
1112     mCameraY = viewport->getCameraRelativeY();
1113 }
1114 
restoreCamera()1115 void NpcDialog::restoreCamera()
1116 {
1117     if ((viewport == nullptr) || mCameraMode == -1)
1118         return;
1119 
1120     if (CAST_S32(settings.cameraMode) != mCameraMode)
1121         viewport->toggleCameraMode();
1122     if (mCameraMode != 0)
1123     {
1124         viewport->setCameraRelativeX(mCameraX);
1125         viewport->setCameraRelativeY(mCameraY);
1126     }
1127     mCameraMode = -1;
1128 }
1129 
showAvatar(const BeingTypeId avatarId)1130 void NpcDialog::showAvatar(const BeingTypeId avatarId)
1131 {
1132     const bool needShow = (avatarId != BeingTypeId_zero);
1133     if (needShow)
1134     {
1135         delete mAvatarBeing;
1136         mAvatarBeing = Being::createBeing(BeingId_zero,
1137             ActorType::Avatar,
1138             avatarId,
1139             nullptr);
1140         mPlayerBox->setPlayer(mAvatarBeing);
1141         if (!mAvatarBeing->mSprites.empty())
1142         {
1143             mAvatarBeing->logic();
1144             const BeingInfo *const info = AvatarDB::get(avatarId);
1145             const int pad2 = 2 * mPadding;
1146             int width = 0;
1147             if (info != nullptr)
1148             {
1149                 width = info->getWidth();
1150                 mPlayerBox->setWidth(width + pad2);
1151                 mPlayerBox->setHeight(info->getHeight() + pad2);
1152             }
1153             const Sprite *const sprite = mAvatarBeing->mSprites[0];
1154             if ((sprite != nullptr) && (width == 0))
1155             {
1156                 mPlayerBox->setWidth(sprite->getWidth() + pad2);
1157                 mPlayerBox->setHeight(sprite->getHeight() + pad2);
1158             }
1159         }
1160     }
1161     else
1162     {
1163         delete2(mAvatarBeing)
1164         mPlayerBox->setPlayer(nullptr);
1165     }
1166     if (needShow != mShowAvatar)
1167     {
1168         mShowAvatar = needShow;
1169         buildLayout();
1170     }
1171     else
1172     {
1173         mShowAvatar = needShow;
1174     }
1175 }
1176 
setAvatarDirection(const uint8_t direction)1177 void NpcDialog::setAvatarDirection(const uint8_t direction)
1178 {
1179     Being *const being = mPlayerBox->getBeing();
1180     if (being != nullptr)
1181         being->setDirection(direction);
1182 }
1183 
setAvatarAction(const int actionId)1184 void NpcDialog::setAvatarAction(const int actionId)
1185 {
1186     Being *const being = mPlayerBox->getBeing();
1187     if (being != nullptr)
1188         being->setAction(static_cast<BeingActionT>(actionId), 0);
1189 }
1190 
logic()1191 void NpcDialog::logic()
1192 {
1193     BLOCK_START("NpcDialog::logic")
1194     Window::logic();
1195     if (mShowAvatar && (mAvatarBeing != nullptr))
1196     {
1197         mAvatarBeing->logic();
1198         if (mPlayerBox->getWidth() < CAST_S32(3 * getPadding()))
1199         {
1200             const Sprite *const sprite = mAvatarBeing->mSprites[0];
1201             if (sprite != nullptr)
1202             {
1203                 mPlayerBox->setWidth(sprite->getWidth() + 2 * getPadding());
1204                 mPlayerBox->setHeight(sprite->getHeight() + 2 * getPadding());
1205                 buildLayout();
1206             }
1207         }
1208     }
1209     BLOCK_END("NpcDialog::logic")
1210 }
1211 
clearRows()1212 void NpcDialog::clearRows()
1213 {
1214     mTextBox->clearRows();
1215 }
1216 
clearDialogs()1217 void NpcDialog::clearDialogs()
1218 {
1219     NpcDialogs::iterator it = mNpcDialogs.begin();
1220     const NpcDialogs::iterator it_end = mNpcDialogs.end();
1221     while (it != it_end)
1222     {
1223         delete (*it).second;
1224         ++ it;
1225     }
1226     mNpcDialogs.clear();
1227 }
1228 
mousePressed(MouseEvent & event)1229 void NpcDialog::mousePressed(MouseEvent &event)
1230 {
1231     Window::mousePressed(event);
1232     if (event.getButton() == MouseButton::RIGHT
1233         && event.getSource() == mTextBox)
1234     {
1235         event.consume();
1236         if (popupMenu != nullptr)
1237         {
1238             popupMenu->showNpcDialogPopup(mNpcId,
1239                 viewport->mMouseX,
1240                 viewport->mMouseY);
1241         }
1242     }
1243 }
1244 
copyToClipboard(const int x,const int y) const1245 void NpcDialog::copyToClipboard(const int x, const int y) const
1246 {
1247     std::string str = mTextBox->getTextAtPos(x, y);
1248     sendBuffer(str);
1249 }
1250 
setSkin(const std::string & skin)1251 void NpcDialog::setSkin(const std::string &skin)
1252 {
1253     if (skin.empty())
1254     {
1255         mSkinName = skin;
1256         mDialogInfo = nullptr;
1257         return;
1258     }
1259     const NpcDialogInfo *const dialog = NpcDialogDB::getDialog(skin);
1260     if (dialog == nullptr)
1261     {
1262         logger->log("Error: creating controls for not existing npc dialog %s",
1263             skin.c_str());
1264         return;
1265     }
1266     mSkinName = skin;
1267     mDialogInfo = dialog;
1268 }
1269 
deleteSkinControls()1270 void NpcDialog::deleteSkinControls()
1271 {
1272     mSkinContainer->removeControls();
1273 }
1274 
createSkinControls()1275 void NpcDialog::createSkinControls()
1276 {
1277     deleteSkinControls();
1278 
1279     if (mDialogInfo == nullptr)
1280         return;
1281 
1282     FOR_EACH (STD_VECTOR<NpcImageInfo*>::const_iterator,
1283         it,
1284         mDialogInfo->menu.images)
1285     {
1286         const NpcImageInfo *const info = *it;
1287         Image *const image = Theme::getImageFromTheme(info->name);
1288         if (image != nullptr)
1289         {
1290             Icon *const icon = new Icon(this, image, AutoRelease_true);
1291             icon->setPosition(info->x, info->y);
1292             mSkinContainer->add(icon);
1293         }
1294     }
1295     FOR_EACH (STD_VECTOR<NpcTextInfo*>::const_iterator,
1296         it,
1297         mDialogInfo->menu.texts)
1298     {
1299         const NpcTextInfo *const info = *it;
1300         BrowserBox *box = new BrowserBox(this,
1301             Opaque_true,
1302             "browserbox.xml");
1303         box->setOpaque(Opaque_false);
1304         box->setMaxRow(config.getIntValue("ChatLogLength"));
1305         box->setLinkHandler(mItemLinkHandler);
1306         box->setProcessVars(true);
1307         box->setFont(gui->getNpcFont());
1308         box->setEnableKeys(true);
1309         box->setEnableTabs(true);
1310         box->setPosition(info->x, info->y);
1311         mSkinContainer->add(box);
1312         box->setWidth(info->width);
1313         box->setHeight(info->height);
1314         StringVect parts;
1315         splitToStringVector(parts, info->text, '\n');
1316         FOR_EACH (StringVectCIter, it2, parts)
1317         {
1318             box->addRow(*it2,
1319                 false);
1320         }
1321     }
1322     FOR_EACH (STD_VECTOR<NpcButtonInfo*>::const_iterator,
1323         it,
1324         mDialogInfo->menu.buttons)
1325     {
1326         const NpcButtonInfo *const info = *it;
1327         Button *const button = new Button(this,
1328             BUTTON_SKIN);
1329         button->setCaption(info->name);
1330         button->setActionEventId("skin_" + info->value);
1331         button->addActionListener(this);
1332         button->setPosition(info->x, info->y);
1333         if (!info->image.empty())
1334         {
1335             button->setImageWidth(info->imageWidth);
1336             button->setImageHeight(info->imageHeight);
1337             button->loadImageSet(info->image);
1338         }
1339         mSkinContainer->add(button);
1340         button->adjustSize();
1341     }
1342 }
1343 
restoreVirtuals()1344 void NpcDialog::restoreVirtuals()
1345 {
1346     Inventory *const inventory = PlayerInfo::getInventory();
1347     if (inventory != nullptr)
1348         inventory->restoreVirtuals();
1349 }
1350 
complexItemToStr(const ComplexItem * const item)1351 std::string NpcDialog::complexItemToStr(const ComplexItem *const item)
1352 {
1353     std::string str;
1354     if (item != nullptr)
1355     {
1356         const STD_VECTOR<Item*> &items = item->getChilds();
1357         const size_t sz = items.size();
1358         if (sz == 0U)
1359             return str;
1360 
1361         const Item *item2 = items[0];
1362 
1363         str = strprintf("%d,%d",
1364             item2->getInvIndex(),
1365             item2->getQuantity());
1366         for (size_t f = 1; f < sz; f ++)
1367         {
1368             str.append(";");
1369             item2 = items[f];
1370             str.append(strprintf("%d,%d",
1371                 item2->getInvIndex(),
1372                 item2->getQuantity()));
1373         }
1374     }
1375     else
1376     {
1377         str.clear();
1378     }
1379     return str;
1380 }
1381 
addCraftItem(Item * const item,const int amount,const int slot)1382 void NpcDialog::addCraftItem(Item *const item,
1383                              const int amount,
1384                              const int slot)
1385 {
1386     if (mInputState != NpcInputState::ITEM_CRAFT)
1387         return;
1388 
1389     Inventory *const inventory = PlayerInfo::getInventory();
1390 
1391     if (inventory == nullptr)
1392         return;
1393 
1394     if (mComplexInventory->addVirtualItem(
1395         item,
1396         slot,
1397         amount))
1398     {
1399         inventory->virtualRemove(item, amount);
1400     }
1401 }
1402