GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/npcdialog.cpp Lines: 141 683 20.6 %
Date: 2021-03-17 Branches: 149 761 19.6 %

Line Branch Exec Source
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
1
NpcDialog::DialogList NpcDialog::instances;
94
1
NpcDialogs NpcDialog::mNpcDialogs;
95
96
typedef STD_VECTOR<Image *>::iterator ImageVectorIter;
97
98
1
NpcDialog::NpcDialog(const BeingId npcId) :
99
    // TRANSLATORS: npc dialog name
100
1
    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

1
        "browserbox.xml")),
107
1
    mScrollArea(new ScrollArea(this, mTextBox,
108

4
        fromBool(getOptionBool("showtextbackground", false), Opaque),
109

2
        "npc_textbackground.xml")),
110
    mText(),
111
    mNewText(),
112
    mItems(),
113
    mImages(),
114


4
    mItemList(CREATEWIDGETR(ExtendedListBox,
115
        this, this, "extendedlistbox.xml", 13)),
116
1
    mListScrollArea(new ScrollArea(this, mItemList,
117

4
        fromBool(getOptionBool("showlistbackground", false), Opaque),
118

2
        "npc_listbackground.xml")),
119

1
    mSkinContainer(new Container(this)),
120
1
    mSkinScrollArea(new ScrollArea(this, mSkinContainer,
121

4
        fromBool(getOptionBool("showlistbackground", false), Opaque),
122

2
        "npc_listbackground.xml")),
123

1
    mItemLinkHandler(new ItemLinkHandler),
124
2
    mTextField(new TextField(this, std::string(), LoseFocusOnTab_true,
125

2
        nullptr, std::string(), false)),
126

1
    mIntField(new IntTextField(this, 0, 0, 0, Enable_true, 0)),
127
    // TRANSLATORS: npc dialog button
128

2
    mPlusButton(new Button(this, _("+"), "inc", BUTTON_SKIN, this)),
129
    // TRANSLATORS: npc dialog button
130

2
    mMinusButton(new Button(this, _("-"), "dec", BUTTON_SKIN, this)),
131
    // TRANSLATORS: npc dialog button
132

2
    mClearButton(new Button(this, _("Clear"), "clear", BUTTON_SKIN, this)),
133

1
    mButton(new Button(this, "", "ok", BUTTON_SKIN, this)),
134
    // TRANSLATORS: npc dialog button
135

2
    mButton2(new Button(this, _("Close"), "close", BUTTON_SKIN, this)),
136
    // TRANSLATORS: npc dialog button
137

2
    mButton3(new Button(this, _("Add"), "add", BUTTON_SKIN, this)),
138
    // TRANSLATORS: npc dialog button
139

2
    mResetButton(new Button(this, _("Reset"), "reset", BUTTON_SKIN, this)),
140

1
    mInventory(new Inventory(InventoryType::Npc, 1)),
141

1
    mComplexInventory(new ComplexInventory(InventoryType::Craft, 1)),
142
    mItemContainer(new ItemContainer(this, mInventory,
143

1
        10000, ShowEmptyRows_true, ForceQuantity_false)),
144
1
    mItemScrollArea(new ScrollArea(this, mItemContainer,
145

4
        fromBool(getOptionBool("showitemsbackground", false), Opaque),
146

2
        "npc_listbackground.xml")),
147
    mInputState(NpcInputState::NONE),
148
    mActionState(NpcActionState::WAIT),
149
    mSkinControls(),
150
    mSkinName(),
151

4
    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











107
    mLogInteraction(config.getBoolValue("logNpcInGui"))
160
{
161
    // Basic Window Setup
162
5
    setWindowName("NpcText");
163
1
    setResizable(true);
164
1
    setFocusable(true);
165
1
    setStickyButtonLock(true);
166
167
1
    setMinWidth(200);
168
1
    setMinHeight(150);
169
170
1
    setDefaultSize(300, 578, ImagePosition::LOWER_LEFT, 0, 0);
171
172
1
    mPlayerBox->setWidth(70);
173
1
    mPlayerBox->setHeight(100);
174
175
    // Setup output text box
176
2
    mTextBox->setOpaque(Opaque_false);
177

5
    mTextBox->setMaxRow(config.getIntValue("ChatLogLength"));
178
1
    mTextBox->setLinkHandler(mItemLinkHandler);
179
2
    mTextBox->setProcessVars(true);
180
2
    mTextBox->setFont(gui->getNpcFont());
181
2
    mTextBox->setEnableKeys(true);
182
2
    mTextBox->setEnableTabs(true);
183
2
    mTextBox->setEnableImages(true);
184
185
1
    mScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
186
1
    mScrollArea->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
187
188
    // Setup listbox
189
2
    mItemList->setWrappingEnabled(true);
190
5
    mItemList->setActionEventId("ok");
191
1
    mItemList->addActionListener(this);
192
2
    mItemList->setDistributeMousePressed(false);
193
2
    mItemList->setFont(gui->getNpcFont());
194

2
    if (gui->getNpcFont()->getHeight() < 20)
195
1
        mItemList->setRowHeight(20);
196
    else
197
        mItemList->setRowHeight(gui->getNpcFont()->getHeight());
198
199
1
    setContentSize(260, 175);
200
1
    mListScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
201
1
    mItemScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
202
1
    mSkinScrollArea->setScrollPolicy(ScrollArea::SHOW_NEVER,
203
1
        ScrollArea::SHOW_NEVER);
204
1
    mItemList->setVisible(Visible_true);
205
1
    mTextField->setVisible(Visible_true);
206
1
    mIntField->setVisible(Visible_true);
207
208
1
    const Font *const fnt = mButton->getFont();
209

6
    int width = std::max(fnt->getWidth(CAPTION_WAITING),
210

7
        fnt->getWidth(CAPTION_NEXT));
211

5
    width = std::max(width, fnt->getWidth(CAPTION_CLOSE));
212

5
    width = std::max(width, fnt->getWidth(CAPTION_SUBMIT));
213
1
    mButton->setWidth(8 + width);
214
215
    // Place widgets
216
1
    buildLayout();
217
218
1
    center();
219
1
    loadWindowState();
220
221
2
    instances.push_back(this);
222
1
}
223
224
1
void NpcDialog::postInit()
225
{
226
1
    Window::postInit();
227
1
    setVisible(Visible_true);
228
1
    requestFocus();
229
2
    enableVisibleSound(true);
230
1
    soundManager.playGuiSound(SOUND_SHOW_WINDOW);
231
232
1
    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
4
    config.addListener("logNpcInGui", this);
244
1
}
245
246
12
NpcDialog::~NpcDialog()
247
{
248
1
    config.removeListeners(this);
249
    CHECKLISTENERS
250
1
    clearLayout();
251
252
1
    if (mPlayerBox != nullptr)
253
    {
254
2
        delete mPlayerBox->getBeing();
255
1
        delete mPlayerBox;
256
    }
257
258
1
    deleteSkinControls();
259
260
1
    delete2(mTextBox)
261
1
    delete2(mClearButton)
262
1
    delete2(mButton)
263
1
    delete2(mButton2)
264
1
    delete2(mButton3)
265
1
    delete2(mScrollArea)
266
1
    delete2(mItemList)
267
1
    delete2(mTextField)
268
1
    delete2(mIntField)
269
1
    delete2(mResetButton)
270
1
    delete2(mPlusButton)
271
1
    delete2(mMinusButton)
272
1
    delete2(mItemLinkHandler)
273
1
    delete2(mItemContainer)
274
1
    delete2(mInventory)
275
1
    delete2(mComplexInventory)
276
1
    delete2(mItemScrollArea)
277
1
    delete2(mListScrollArea)
278
1
    delete2(mSkinScrollArea)
279
280
4
    FOR_EACH (ImageVectorIter, it, mImages)
281
    {
282
        if (*it != nullptr)
283
            (*it)->decRef();
284
    }
285
286
2
    mImages.clear();
287
288
1
    instances.remove(this);
289
2
}
290
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
307
void NpcDialog::showNextButton()
308
{
309
    mActionState = NpcActionState::NEXT;
310
    buildLayout();
311
}
312
313
void NpcDialog::showCloseButton()
314
{
315
    mActionState = NpcActionState::CLOSE;
316
    buildLayout();
317
}
318
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
660
void NpcDialog::nextDialog()
661
{
662
    npcHandler->nextDialog(mNpcId);
663
}
664
665
void NpcDialog::closeDialog()
666
{
667
    restoreCamera();
668
    npcHandler->closeDialog(mNpcId);
669
}
670
671
1
int NpcDialog::getNumberOfElements()
672
{
673
2
    return CAST_S32(mItems.size());
674
}
675
676
std::string NpcDialog::getElementAt(int i)
677
{
678
    return mItems[i];
679
}
680
681
const Image *NpcDialog::getImageAt(int i)
682
{
683
    return mImages[i];
684
}
685
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
700
void NpcDialog::addChoice(const std::string &choice)
701
{
702
    mItems.push_back(choice);
703
    mImages.push_back(nullptr);
704
}
705
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
741
void NpcDialog::refocus()
742
{
743
    if (!mItems.empty())
744
        mItemList->refocus();
745
}
746
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
757
bool NpcDialog::isTextInputFocused() const
758
{
759
    return mTextField->isFocused();
760
}
761
762
bool NpcDialog::isInputFocused() const
763
{
764
    return mTextField->isFocused() || mIntField->isFocused()
765
        || mItemList->isFocused();
766
}
767
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
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
791
void NpcDialog::itemRequest(const int size)
792
{
793
    mActionState = NpcActionState::INPUT;
794
    mInputState = NpcInputState::ITEM;
795
    mInventory->resize(size);
796
    buildLayout();
797
}
798
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
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
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
838
void NpcDialog::setVisible(Visible visible)
839
{
840
1
    Window::setVisible(visible);
841
842
    if (visible == Visible_false)
843
        scheduleDelete();
844
}
845
846
void NpcDialog::optionChanged(const std::string &name)
847
{
848
    if (name == "logNpcInGui")
849
        mLogInteraction = config.getBoolValue("logNpcInGui");
850
}
851
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
866
void NpcDialog::closeAll()
867
{
868
    FOR_EACH (DialogList::const_iterator, it, instances)
869
    {
870
        if (*it != nullptr)
871
            (*it)->close();
872
    }
873
}
874
875
1
void NpcDialog::placeNormalControls()
876
{
877
1
    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
1
        place(0, 0, mScrollArea, 5, 3);
887
1
        place(3, 3, mClearButton, 1, 1);
888
1
        place(4, 3, mButton, 1, 1);
889
    }
890
1
}
891
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
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
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
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
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
1052
1
void NpcDialog::buildLayout()
1053
{
1054
1
    clearLayout();
1055
1056
1
    if (mActionState != NpcActionState::INPUT)
1057
    {
1058
1
        if (mActionState == NpcActionState::WAIT)
1059
5
            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
1
        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
1
    Layout &layout = getLayout();
1100
1
    layout.setRowHeight(1, LayoutType::SET);
1101
1
    redraw();
1102
1
    mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
1103
1
}
1104
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
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
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
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
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
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
1212
void NpcDialog::clearRows()
1213
{
1214
    mTextBox->clearRows();
1215
}
1216
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
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
1245
void NpcDialog::copyToClipboard(const int x, const int y) const
1246
{
1247
    std::string str = mTextBox->getTextAtPos(x, y);
1248
    sendBuffer(str);
1249
}
1250
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
1270
void NpcDialog::deleteSkinControls()
1271
{
1272
1
    mSkinContainer->removeControls();
1273
}
1274
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
1344
void NpcDialog::restoreVirtuals()
1345
{
1346
    Inventory *const inventory = PlayerInfo::getInventory();
1347
    if (inventory != nullptr)
1348
        inventory->restoreVirtuals();
1349
}
1350
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
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

3
}