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