GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/skilldialog.cpp Lines: 49 688 7.1 %
Date: 2021-03-17 Branches: 32 791 4.0 %

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/skilldialog.h"
25
26
#include "configuration.h"
27
#include "effectmanager.h"
28
#include "spellmanager.h"
29
30
#include "being/localplayer.h"
31
#include "being/playerinfo.h"
32
33
#include "const/resources/spriteaction.h"
34
35
#include "enums/resources/skill/skillsettype.h"
36
37
#include "gui/shortcut/itemshortcut.h"
38
39
#include "gui/windows/setupwindow.h"
40
#include "gui/windows/shortcutwindow.h"
41
42
#include "gui/widgets/button.h"
43
#include "gui/widgets/createwidget.h"
44
#include "gui/widgets/label.h"
45
#include "gui/widgets/scrollarea.h"
46
#include "gui/widgets/tabbedarea.h"
47
48
#include "gui/widgets/tabs/skilltab.h"
49
50
#include "gui/windows/textdialog.h"
51
52
#include "listeners/textskilllistener.h"
53
54
#include "net/playerhandler.h"
55
#include "net/skillhandler.h"
56
57
#include "utils/checkutils.h"
58
#include "utils/dtor.h"
59
#include "utils/gettext.h"
60
#include "utils/timer.h"
61
62
#include "resources/beingcommon.h"
63
64
#include "debug.h"
65
66
SkillDialog *skillDialog = nullptr;
67
68
namespace
69
{
70
1
    TextSkillListener textSkillListener;
71
}  // namespace
72
73
static SkillOwner::Type parseOwner(const std::string &str)
74
{
75
    if (str == "player")
76
        return SkillOwner::Player;
77
    else if (str == "mercenary")
78
        return SkillOwner::Mercenary;
79
    else if (str == "homunculus")
80
        return SkillOwner::Homunculus;
81
    return SkillOwner::Player;
82
}
83
84
1
SkillDialog::SkillDialog() :
85
    // TRANSLATORS: skills dialog name
86
1
    Window(_("Skills"), Modal_false, nullptr, "skills.xml"),
87
    ActionListener(),
88
    mSkills(),
89
    mDurations(),
90


1
    mTabs(CREATEWIDGETR(TabbedArea, this)),
91
    mDeleteTabs(),
92

1
    mPointsLabel(new Label(this, "0")),
93
    // TRANSLATORS: skills dialog button
94

2
    mUseButton(new Button(this, _("Use"), "use", BUTTON_SKIN, this)),
95
    // TRANSLATORS: skills dialog button
96

2
    mIncreaseButton(new Button(this, _("Up"), "inc", BUTTON_SKIN, this)),
97
    mDefaultModel(nullptr),
98



30
    mDefaultTab(nullptr)
99
{
100
5
    setWindowName("Skills");
101
1
    setCloseButton(true);
102
1
    setResizable(true);
103
2
    setSaveVisible(true);
104
1
    setStickyButtonLock(true);
105
2
    setDefaultSize(windowContainer->getWidth() - 280, 30, 275, 425);
106
1
    if (setupWindow != nullptr)
107
        setupWindow->registerWindowForReset(this);
108
109
2
    mUseButton->setEnabled(false);
110
2
    mIncreaseButton->setEnabled(false);
111
2
    mTabs->setSelectable(false);
112
3
    mTabs->getTabContainer()->setSelectable(false);
113
3
    mTabs->getWidgetContainer()->setSelectable(false);
114
115

1
    place(0, 0, mTabs, 5, 5);
116
1
    place(0, 5, mPointsLabel, 4, 1);
117
1
    place(3, 5, mUseButton, 1, 1);
118
1
    place(4, 5, mIncreaseButton, 1, 1);
119
1
}
120
121
1
void SkillDialog::postInit()
122
{
123
1
    Window::postInit();
124
2
    setLocationRelativeTo(getParent());
125
1
    loadWindowState();
126
2
    enableVisibleSound(true);
127
1
}
128
129
7
SkillDialog::~SkillDialog()
130
{
131
1
    clearSkills();
132
2
}
133
134
void SkillDialog::addDefaultTab()
135
{
136
    mDefaultModel = new SkillModel;
137
    SkillListBox *const listbox = new SkillListBox(this,
138
        mDefaultModel);
139
    listbox->setActionEventId("sel");
140
    listbox->addActionListener(this);
141
    ScrollArea *const scroll = new ScrollArea(this,
142
        listbox,
143
        Opaque_false,
144
        std::string());
145
    scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
146
    scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
147
    // TRANSLATORS: unknown skills tab name
148
    mDefaultTab = new SkillTab(this, _("Unknown"), listbox);
149
    mDeleteTabs.push_back(mDefaultTab);
150
    mDefaultTab->setVisible(Visible_false);
151
    mTabs->addTab(mDefaultTab, scroll);
152
    mTabs->adjustTabPositions();
153
    mTabs->setSelectedTabDefault();
154
}
155
156
void SkillDialog::action(const ActionEvent &event)
157
{
158
    const std::string &eventId = event.getId();
159
    if (eventId == "inc")
160
    {
161
        if (playerHandler == nullptr)
162
            return;
163
        const SkillTab *const tab = static_cast<const SkillTab *>(
164
            mTabs->getSelectedTab());
165
        if (tab != nullptr)
166
        {
167
            if (const SkillInfo *const info = tab->getSelectedInfo())
168
                playerHandler->increaseSkill(CAST_U16(info->id));
169
        }
170
    }
171
    else if (eventId == "sel")
172
    {
173
        const SkillTab *const tab = static_cast<const SkillTab *>(
174
            mTabs->getSelectedTab());
175
        if (tab != nullptr)
176
        {
177
            if (const SkillInfo *const info = tab->getSelectedInfo())
178
            {
179
                mUseButton->setEnabled(info->isUsable());
180
                mUseButton->setCaption(info->useButton);
181
                mIncreaseButton->setEnabled(info->id < SKILL_VAR_MIN_ID);
182
                const int num = itemShortcutWindow->getTabIndex();
183
                if (num >= 0 && num < CAST_S32(SHORTCUT_TABS)
184
                    && (itemShortcut[num] != nullptr))
185
                {
186
                    itemShortcut[num]->setItemSelected(
187
                        info->id + SKILL_MIN_ID);
188
                }
189
            }
190
            else
191
            {
192
                mUseButton->setEnabled(false);
193
                mIncreaseButton->setEnabled(false);
194
                // TRANSLATORS: skills dialog button
195
                mUseButton->setCaption(_("Use"));
196
            }
197
        }
198
    }
199
    else if (eventId == "use")
200
    {
201
        const SkillTab *const tab = static_cast<const SkillTab *>(
202
            mTabs->getSelectedTab());
203
        if (tab != nullptr)
204
        {
205
            const SkillInfo *const info = tab->getSelectedInfo();
206
            if (info == nullptr)
207
                return;
208
            useSkill(info,
209
                fromBool(config.getBoolValue("skillAutotarget"), AutoTarget),
210
                info->customSelectedLevel,
211
                info->useTextParameter,
212
                std::string(),
213
                info->customCastType,
214
                info->customOffsetX,
215
                info->customOffsetY);
216
        }
217
    }
218
    else if (eventId == "close")
219
    {
220
        setVisible(Visible_false);
221
    }
222
}
223
224
std::string SkillDialog::update(const int id)
225
{
226
    const SkillMap::const_iterator i = mSkills.find(id);
227
228
    if (i != mSkills.end())
229
    {
230
        SkillInfo *const info = i->second;
231
        if (info != nullptr)
232
        {
233
            info->update();
234
            return info->data->name;
235
        }
236
    }
237
238
    return std::string();
239
}
240
241
void SkillDialog::update()
242
{
243
    // TRANSLATORS: skills dialog label
244
    mPointsLabel->setCaption(strprintf(_("Skill points available: %d"),
245
        PlayerInfo::getAttribute(Attributes::PLAYER_SKILL_POINTS)));
246
    mPointsLabel->adjustSize();
247
248
    ItemShortcut *const shortcuts = itemShortcut[SHORTCUT_AUTO_TAB];
249
    shortcuts->clear();
250
    size_t idx = 0;
251
252
    FOR_EACH (SkillMap::const_iterator, it, mSkills)
253
    {
254
        SkillInfo *const info = (*it).second;
255
        if (info == nullptr)
256
            continue;
257
        if (info->modifiable == Modifiable_true)
258
            info->update();
259
        if (info->visible == Visible_false ||
260
            idx >= SHORTCUT_ITEMS ||
261
            !info->data->autoTab)
262
        {
263
            continue;
264
        }
265
        const SkillType::SkillType type = info->type;
266
        if (type == SkillType::Attack ||
267
            type == SkillType::Ground ||
268
            type == SkillType::Self ||
269
            type == SkillType::Support)
270
        {
271
            shortcuts->setItemFast(idx,
272
                info->id + SKILL_MIN_ID,
273
                fromInt(info->customSelectedLevel, ItemColor));
274
275
            shortcuts->setItemData(idx,
276
                info->toDataStr());
277
            idx ++;
278
        }
279
    }
280
281
    skillPopup->reset();
282
}
283
284
void SkillDialog::updateModels()
285
{
286
    std::set<SkillModel*> models;
287
288
    FOR_EACH (SkillMap::const_iterator, it, mSkills)
289
    {
290
        SkillInfo *const info = (*it).second;
291
        if (info != nullptr)
292
        {
293
            SkillModel *const model = info->model;
294
            if (model != nullptr)
295
                models.insert(model);
296
        }
297
    }
298
    FOR_EACH (std::set<SkillModel*>::iterator, it, models)
299
    {
300
        SkillModel *const model = *it;
301
        if (model != nullptr)
302
            model->updateVisibilities();
303
    }
304
}
305
306
void SkillDialog::updateModelsHidden()
307
{
308
    std::set<SkillModel*> models;
309
310
    FOR_EACH (SkillMap::const_iterator, it, mSkills)
311
    {
312
        SkillInfo *const info = (*it).second;
313
        if (info != nullptr)
314
        {
315
            if (info->visible == Visible_false)
316
            {
317
                SkillModel *const model = info->model;
318
                if (model != nullptr)
319
                    models.insert(model);
320
            }
321
        }
322
    }
323
    FOR_EACH (std::set<SkillModel*>::iterator, it, models)
324
    {
325
        SkillModel *const model = *it;
326
        if (model != nullptr)
327
            model->updateVisibilities();
328
    }
329
}
330
331
1
void SkillDialog::clearSkills()
332
{
333
1
    mTabs->removeAll(true);
334
2
    mDeleteTabs.clear();
335
1
    mDefaultTab = nullptr;
336
1
    mDefaultModel = nullptr;
337
338
2
    delete_all(mSkills);
339
2
    mSkills.clear();
340
2
    mDurations.clear();
341
1
}
342
343
void SkillDialog::hideSkills(const SkillOwner::Type owner)
344
{
345
    FOR_EACH (SkillMap::iterator, it, mSkills)
346
    {
347
        SkillInfo *const info = (*it).second;
348
        if ((info != nullptr) && info->owner == owner)
349
        {
350
            PlayerInfo::setSkillLevel(info->id, 0);
351
            if (info->alwaysVisible == Visible_false)
352
                info->visible = Visible_false;
353
        }
354
    }
355
}
356
357
void SkillDialog::loadSkills()
358
{
359
    clearSkills();
360
    loadXmlFile(paths.getStringValue("skillsFile"), SkipError_false);
361
    if (mSkills.empty())
362
        loadXmlFile(paths.getStringValue("skillsFile2"), SkipError_false);
363
    loadXmlFile(paths.getStringValue("skillsPatchFile"), SkipError_true);
364
    loadXmlDir("skillsPatchDir", loadXmlFile)
365
    addDefaultTab();
366
367
    update();
368
}
369
370
void SkillDialog::loadXmlFile(const std::string &fileName,
371
                              const SkipError skipError)
372
{
373
    XML::Document doc(fileName,
374
        UseVirtFs_true,
375
        skipError);
376
    XmlNodePtrConst root = doc.rootNode();
377
378
    int setCount = 0;
379
380
    if ((root == nullptr) || !xmlNameEqual(root, "skills"))
381
    {
382
        logger->log("Error loading skills: " + fileName);
383
        return;
384
    }
385
386
    for_each_xml_child_node(set, root)
387
    {
388
        if (xmlNameEqual(set, "include"))
389
        {
390
            const std::string name = XML::getProperty(set, "name", "");
391
            if (!name.empty())
392
                loadXmlFile(name, skipError);
393
            continue;
394
        }
395
        else if (xmlNameEqual(set, "set"))
396
        {
397
            setCount++;
398
            const std::string setName = XML::getProperty(set, "name",
399
                // TRANSLATORS: skills dialog default skill tab
400
                strprintf(_("Skill Set %d"), setCount));
401
402
            const std::string setTypeStr = XML::getProperty(set, "type", "");
403
            SkillSetTypeT setType = SkillSetType::VerticalList;
404
            if (setTypeStr.empty() ||
405
                setTypeStr == "list" ||
406
                setTypeStr == "vertical")
407
            {
408
                setType = SkillSetType::VerticalList;
409
            }
410
            else if (setTypeStr == "rectangle")
411
            {
412
                setType = SkillSetType::Rectangle;
413
            }
414
415
            bool alwaysVisible = false;
416
            SkillModel *const model = new SkillModel;
417
            SkillTab *tab = nullptr;
418
            ScrollArea *scroll = nullptr;
419
420
            switch (setType)
421
            {
422
                case SkillSetType::VerticalList:
423
                {
424
                    // possible leak listbox, scroll
425
                    SkillListBox *const listbox = new SkillListBox(this,
426
                        model);
427
                    listbox->setActionEventId("sel");
428
                    listbox->addActionListener(this);
429
                    scroll = new ScrollArea(this,
430
                        listbox,
431
                        Opaque_false,
432
                        std::string());
433
                    scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
434
                    scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
435
                    tab = new SkillTab(this, setName, listbox);
436
                    break;
437
                }
438
                case SkillSetType::Rectangle:
439
                {
440
                    SkillRectangleListBox *const listbox =
441
                        new SkillRectangleListBox(this,
442
                        model);
443
                    listbox->setActionEventId("sel");
444
                    listbox->addActionListener(this);
445
                    scroll = new ScrollArea(this,
446
                        listbox,
447
                        Opaque_false,
448
                        std::string());
449
                    scroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
450
                    scroll->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
451
                    tab = new SkillTab(this, setName, listbox);
452
                    break;
453
                }
454
                default:
455
                    reportAlways("Unsupported skillset type: %s",
456
                        setTypeStr.c_str())
457
                    return;
458
            }
459
            if (mDefaultModel == nullptr)
460
            {
461
                mDefaultModel = model;
462
                mDefaultTab = tab;
463
            }
464
465
            mDeleteTabs.push_back(tab);
466
            if (alwaysVisible == true)
467
                tab->setVisible(Visible_true);
468
            else
469
                tab->setVisible(Visible_false);
470
            mTabs->addTab(tab, scroll);
471
472
            for_each_xml_child_node(node, set)
473
            {
474
                if (xmlNameEqual(node, "skill"))
475
                {
476
                    SkillInfo *const skill = loadSkill(node, model);
477
                    if (skill == nullptr)
478
                        continue;
479
                    if (skill->alwaysVisible == Visible_true)
480
                        alwaysVisible = true;
481
                    skill->tab = tab;
482
                    for_each_xml_child_node(levelNode, node)
483
                    {
484
                        if (!xmlNameEqual(levelNode, "level"))
485
                            continue;
486
                        loadSkillData(node, skill);
487
                    }
488
                }
489
            }
490
491
            model->updateVisibilities();
492
        }
493
    }
494
}
495
496
SkillInfo *SkillDialog::loadSkill(XmlNodeConstPtr node,
497
                                  SkillModel *const model)
498
{
499
    int id = XML::getIntProperty(node, "id", -1, -1, 1000000);
500
    if (id == -1)
501
    {
502
        id = XML::getIntProperty(node, "var", -1, -1, 100000);
503
        if (id == -1)
504
            return nullptr;
505
        id += SKILL_VAR_MIN_ID;
506
    }
507
508
    SkillInfo *skill = getSkill(id);
509
    if (skill == nullptr)
510
    {
511
        std::string name = XML::langProperty(node, "name",
512
            // TRANSLATORS: skills dialog. skill id
513
            strprintf(_("Skill %d"), id));
514
515
        skill = new SkillInfo;
516
        skill->id = CAST_U32(id);
517
        skill->modifiable = Modifiable_false;
518
        skill->model = model;
519
        skill->update();
520
        skill->useButton = XML::getProperty(
521
            // TRANSLATORS: skills dialog button
522
            node, "useButton", _("Use"));
523
        skill->owner = parseOwner(XML::getProperty(
524
            node, "owner", "player"));
525
        skill->errorText = XML::getProperty(
526
            node, "errorText", name);
527
        skill->alwaysVisible = fromBool(XML::getBoolProperty(
528
            node, "alwaysVisible", false), Visible);
529
        skill->castingAction = XML::getProperty(node,
530
            "castingAction", SpriteAction::CAST);
531
        skill->castingRideAction = XML::getProperty(node,
532
            "castingRideAction", SpriteAction::CASTRIDE);
533
        skill->castingSkyAction = XML::getProperty(node,
534
            "castingSkyAction", SpriteAction::CASTSKY);
535
        skill->castingWaterAction = XML::getProperty(node,
536
            "castingWaterAction", SpriteAction::CASTWATER);
537
        skill->useTextParameter = XML::getBoolProperty(
538
            node, "useTextParameter", false);
539
        skill->x = XML::getProperty(node,
540
            "x", 0);
541
        skill->y = XML::getProperty(node,
542
            "y", 0);
543
        skill->visible = skill->alwaysVisible;
544
        model->addSkill(skill);
545
        mSkills[id] = skill;
546
    }
547
548
    loadSkillData(node, skill);
549
    return skill;
550
}
551
552
void SkillDialog::loadSkillData(XmlNodeConstPtr node,
553
                                SkillInfo *const skill)
554
{
555
    if (skill == nullptr)
556
        return;
557
    const int level = (skill->alwaysVisible == Visible_true) ?
558
        0 : XML::getProperty(node, "level", 0);
559
    SkillData *data = skill->getData(level);
560
    if (data == nullptr)
561
        data = new SkillData;
562
563
    const std::string name = XML::langProperty(node, "name",
564
        // TRANSLATORS: skills dialog. skill id
565
        strprintf(_("Skill %u"), skill->id));
566
    data->name = name;
567
    const std::string icon = XML::getProperty(node, "icon", "");
568
    if (icon.empty())
569
    {
570
        data->setIcon(paths.getStringValue("missingSkillIcon"));
571
        data->haveIcon = false;
572
    }
573
    else
574
    {
575
        data->setIcon(icon);
576
        data->haveIcon = true;
577
    }
578
    if (skill->id < SKILL_VAR_MIN_ID)
579
    {
580
        data->dispName = strprintf("%s, %u",
581
            name.c_str(),
582
            skill->id);
583
    }
584
    else
585
    {
586
        data->dispName = strprintf("%s, (%u)",
587
            name.c_str(),
588
            skill->id - SKILL_VAR_MIN_ID);
589
    }
590
    data->shortName = XML::langProperty(node,
591
        "shortName", name.substr(0, 3));
592
    data->description = XML::langProperty(
593
        node, "description", "");
594
595
    MissileInfo &missile = data->missile;
596
    missile.particle = XML::getProperty(
597
        node, "missile-particle", "");
598
    missile.z = XML::getFloatProperty(
599
        node, "missile-z", 32.0F);
600
    missile.lifeTime = XML::getProperty(
601
        node, "missile-lifetime", 500);
602
    missile.speed = XML::getFloatProperty(
603
        node, "missile-speed", 7.0F);
604
    missile.dieDistance = XML::getFloatProperty(
605
        node, "missile-diedistance", 8.0F);
606
607
    MissileInfo &castingMissile = data->castingMissile;
608
    castingMissile.particle = XML::getProperty(
609
        node, "castingMissile-particle", "");
610
    castingMissile.z = XML::getFloatProperty(
611
        node, "castingMissile-z", 32.0F);
612
    castingMissile.lifeTime = XML::getProperty(
613
        node, "castingMissile-lifetime", 500);
614
    castingMissile.speed = XML::getFloatProperty(
615
        node, "castingMissile-speed", 7.0F);
616
    castingMissile.dieDistance = XML::getFloatProperty(
617
        node, "castingMissile-diedistance", 8.0F);
618
619
    data->castingAnimation = XML::getProperty(
620
        node,
621
        "castingAnimation",
622
        paths.getStringValue("skillCastingAnimation"));
623
624
    data->soundHit.sound = XML::getProperty(
625
        node, "soundHit", "");
626
    data->soundHit.delay = XML::getProperty(
627
        node, "soundHitDelay", 0);
628
    data->soundMiss.sound = XML::getProperty(
629
        node, "soundMiss", "");
630
    data->soundMiss.delay = XML::getProperty(
631
        node, "soundMissDelay", 0);
632
    data->invokeCmd = XML::getProperty(
633
        node, "invokeCmd", "");
634
    data->updateEffectId = XML::getProperty(
635
        node, "levelUpEffectId", -1);
636
    data->removeEffectId = XML::getProperty(
637
        node, "removeEffectId", -1);
638
    data->hitEffectId = XML::getProperty(
639
        node, "hitEffectId", -1);
640
    data->missEffectId = XML::getProperty(
641
        node, "missEffectId", -1);
642
    data->castingSrcEffectId = XML::getProperty(
643
        node, "castingSrcEffectId", -1);
644
    data->castingDstEffectId = XML::getProperty(
645
        node, "castingDstEffectId", -1);
646
    data->srcEffectId = XML::getProperty(
647
        node, "srcEffectId", -1);
648
    data->dstEffectId = XML::getProperty(
649
        node, "dstEffectId", -1);
650
    data->castingGroundEffectId = XML::getProperty(
651
        node, "castingGroundEffectId", -1);
652
    data->autoTab = XML::getBoolProperty(
653
        node, "autoTab", true);
654
655
    skill->addData(level, data);
656
}
657
658
void SkillDialog::removeSkill(const int id)
659
{
660
    const SkillMap::const_iterator it = mSkills.find(id);
661
662
    if (it != mSkills.end())
663
    {
664
        SkillInfo *const info = it->second;
665
        if (info != nullptr)
666
        {
667
            info->level = 0;
668
            info->update();
669
            PlayerInfo::setSkillLevel(id, 0);
670
            if (info->alwaysVisible == Visible_false)
671
                info->visible = Visible_false;
672
        }
673
    }
674
}
675
676
bool SkillDialog::updateSkill(const int id,
677
                              const int range,
678
                              const Modifiable modifiable,
679
                              const SkillType::SkillType type,
680
                              const int sp)
681
{
682
    const SkillMap::const_iterator it = mSkills.find(id);
683
684
    if (it != mSkills.end())
685
    {
686
        SkillInfo *const info = it->second;
687
        if (info != nullptr)
688
        {
689
            info->modifiable = modifiable;
690
            info->range = range;
691
            info->type = type;
692
            info->sp = sp;
693
            info->update();
694
            if (info->tab != nullptr)
695
            {
696
                info->tab->setVisible(Visible_true);
697
                mTabs->adjustTabPositions();
698
                mTabs->setSelectedTabDefault();
699
            }
700
        }
701
        return true;
702
    }
703
    return false;
704
}
705
706
std::string SkillDialog::getDefaultSkillIcon(const SkillType::SkillType type)
707
{
708
    std::string icon;
709
    switch (type)
710
    {
711
        case SkillType::Attack:
712
            icon = paths.getStringValue("attackSkillIcon");
713
            break;
714
        case SkillType::Ground:
715
            icon = paths.getStringValue("groundSkillIcon");
716
            break;
717
        case SkillType::Self:
718
            icon = paths.getStringValue("selfSkillIcon");
719
            break;
720
        case SkillType::Unused:
721
            icon = paths.getStringValue("unusedSkillIcon");
722
            break;
723
        case SkillType::Support:
724
            icon = paths.getStringValue("supportSkillIcon");
725
            break;
726
        case SkillType::TargetTrap:
727
            icon = paths.getStringValue("trapSkillIcon");
728
            break;
729
        case SkillType::Unknown:
730
            icon = paths.getStringValue("unknownSkillIcon");
731
            break;
732
        default:
733
            break;
734
    }
735
    return icon;
736
}
737
738
void SkillDialog::addSkill(const SkillOwner::Type owner,
739
                           const int id,
740
                           const std::string &name,
741
                           const int level,
742
                           const int range,
743
                           const Modifiable modifiable,
744
                           const SkillType::SkillType type,
745
                           const int sp)
746
{
747
    if (mDefaultModel != nullptr)
748
    {
749
        SkillInfo *const skill = new SkillInfo;
750
        skill->id = CAST_U32(id);
751
        skill->type = type;
752
        skill->owner = owner;
753
        SkillData *const data = skill->data;
754
        if (name.empty())
755
        {
756
            data->name = "Unknown skill Id: " + toString(id);
757
            data->dispName = data->name;
758
        }
759
        else
760
        {
761
            data->name = name;
762
            data->dispName = strprintf("%s, %u", name.c_str(), skill->id);
763
        }
764
        data->description.clear();
765
        const std::string icon = getDefaultSkillIcon(type);
766
        if (icon.empty())
767
        {
768
            data->setIcon(paths.getStringValue("missingSkillIcon"));
769
            data->haveIcon = false;
770
        }
771
        else
772
        {
773
            data->setIcon(icon);
774
            data->haveIcon = true;
775
        }
776
        data->autoTab = settings.unknownSkillsAutoTab;
777
        data->shortName = toString(skill->id);
778
        skill->modifiable = modifiable;
779
        skill->visible = Visible_false;
780
        skill->alwaysVisible = Visible_false;
781
        skill->model = mDefaultModel;
782
        skill->level = level;
783
        // TRANSLATORS: skills dialog. skill level
784
        skill->skillLevel = strprintf(_("Lvl: %d"), level);
785
        skill->range = range;
786
        skill->sp = sp;
787
        skill->update();
788
        // TRANSLATORS: skills dialog button
789
        skill->useButton = _("Use");
790
        // TRANSLATORS: skill error message
791
        skill->errorText = strprintf(_("Failed skill: %s"), name.c_str());
792
        skill->tab = mDefaultTab;
793
        mDefaultModel->addSkill(skill);
794
        mDefaultTab->setVisible(Visible_true);
795
        mTabs->adjustTabPositions();
796
        mTabs->setSelectedTabDefault();
797
798
        mSkills[id] = skill;
799
        mDefaultModel->updateVisibilities();
800
    }
801
}
802
803
SkillInfo* SkillDialog::getSkill(const int id) const
804
{
805
    SkillMap::const_iterator it = mSkills.find(id);
806
    if (it != mSkills.end())
807
        return (*it).second;
808
    return nullptr;
809
}
810
811
SkillInfo* SkillDialog::getSkillByItem(const int itemId) const
812
{
813
    SkillMap::const_iterator it = mSkills.find(itemId - SKILL_MIN_ID);
814
    if (it != mSkills.end())
815
        return (*it).second;
816
    return nullptr;
817
}
818
819
void SkillDialog::setSkillDuration(const SkillOwner::Type owner,
820
                                   const int id,
821
                                   const int duration)
822
{
823
    SkillMap::const_iterator it = mSkills.find(id);
824
    SkillInfo *info = nullptr;
825
    if (it == mSkills.end())
826
    {
827
        addSkill(owner, id, "", 0, 0, Modifiable_false, SkillType::Unknown, 0);
828
        it = mSkills.find(id);
829
    }
830
    if (it != mSkills.end())
831
    {
832
        info = (*it).second;
833
    }
834
    if (info != nullptr)
835
    {
836
        info->duration = duration;
837
        info->durationTime = tick_time;
838
        addSkillDuration(info);
839
    }
840
}
841
842
1
void SkillDialog::widgetResized(const Event &event)
843
{
844
1
    Window::widgetResized(event);
845
846
1
    if (mTabs != nullptr)
847
1
        mTabs->adjustSize();
848
1
}
849
850
void SkillDialog::useItem(const int itemId,
851
                          const AutoTarget autoTarget,
852
                          const int level,
853
                          const std::string &data) const
854
{
855
    const std::map<int, SkillInfo*>::const_iterator
856
        it = mSkills.find(itemId - SKILL_MIN_ID);
857
    if (it == mSkills.end())
858
        return;
859
860
    const SkillInfo *const info = (*it).second;
861
    CastTypeT castType = CastType::Default;
862
    int offsetX = 0;
863
    int offsetY = 0;
864
865
    if (!data.empty())
866
    {
867
        STD_VECTOR<int> vect;
868
        splitToIntVector(vect, data, ' ');
869
        const size_t sz = vect.size();
870
        if (sz > 0)
871
            castType = static_cast<CastTypeT>(vect[0]);
872
        if (sz > 2)
873
        {
874
            offsetX = vect[1];
875
            offsetY = vect[2];
876
        }
877
    }
878
    useSkill(info,
879
        autoTarget,
880
        level,
881
        false,
882
        std::string(),
883
        castType,
884
        offsetX,
885
        offsetY);
886
}
887
888
void SkillDialog::updateTabSelection()
889
{
890
    const SkillTab *const tab = static_cast<SkillTab*>(
891
        mTabs->getSelectedTab());
892
    if (tab != nullptr)
893
    {
894
        if (const SkillInfo *const info = tab->getSelectedInfo())
895
        {
896
            mUseButton->setEnabled(info->range > 0);
897
            mIncreaseButton->setEnabled(info->id < SKILL_VAR_MIN_ID);
898
            mUseButton->setCaption(info->useButton);
899
        }
900
        else
901
        {
902
            mUseButton->setEnabled(false);
903
            // TRANSLATORS: inventory button
904
            mUseButton->setCaption(_("Use"));
905
        }
906
    }
907
}
908
909
void SkillDialog::updateQuest(const int var,
910
                              const int val1,
911
                              const int val2 A_UNUSED,
912
                              const int val3 A_UNUSED,
913
                              const int time1 A_UNUSED)
914
{
915
    const int id = var + SKILL_VAR_MIN_ID;
916
    const SkillMap::const_iterator it = mSkills.find(id);
917
918
    if (it != mSkills.end())
919
    {
920
        SkillInfo *const info = it->second;
921
        if (info != nullptr)
922
        {
923
            PlayerInfo::setSkillLevel(id, val1);
924
            info->level = val1;
925
            info->update();
926
        }
927
    }
928
}
929
930
SkillData *SkillDialog::getSkillData(const int id) const
931
{
932
    const SkillMap::const_iterator it = mSkills.find(id);
933
    if (it != mSkills.end())
934
    {
935
        SkillInfo *const info = it->second;
936
        if (info != nullptr)
937
            return info->data;
938
    }
939
    return nullptr;
940
}
941
942
SkillData *SkillDialog::getSkillDataByLevel(const int id,
943
                                            const int level) const
944
{
945
    const SkillMap::const_iterator it = mSkills.find(id);
946
    if (it != mSkills.end())
947
    {
948
        SkillInfo *const info = it->second;
949
        if (info != nullptr)
950
            return info->getData1(level);
951
    }
952
    return nullptr;
953
}
954
955
void SkillDialog::playUpdateEffect(const int id) const
956
{
957
    if (effectManager == nullptr)
958
        return;
959
    const SkillData *const data = getSkillData(id);
960
    if (data == nullptr)
961
        return;
962
    effectManager->triggerDefault(data->updateEffectId,
963
        localPlayer,
964
        paths.getIntValue("skillLevelUpEffectId"));
965
}
966
967
void SkillDialog::playRemoveEffect(const int id) const
968
{
969
    if (effectManager == nullptr)
970
        return;
971
    const SkillData *const data = getSkillData(id);
972
    if (data == nullptr)
973
        return;
974
    effectManager->triggerDefault(data->removeEffectId,
975
        localPlayer,
976
        paths.getIntValue("skillRemoveEffectId"));
977
}
978
979
void SkillDialog::playCastingDstTileEffect(const int id,
980
                                           const int level,
981
                                           const int x,
982
                                           const int y,
983
                                           const int delay) const
984
{
985
    if (effectManager == nullptr)
986
        return;
987
    SkillData *const data = getSkillDataByLevel(id, level);
988
    if (data == nullptr)
989
        return;
990
    effectManager->triggerDefault(data->castingGroundEffectId,
991
        x * 32,
992
        y * 32,
993
        cur_time + delay / 1000,  // end time in seconds
994
        paths.getIntValue("skillCastingGroundEffectId"));
995
}
996
997
void SkillDialog::useSkill(const int skillId,
998
                           const AutoTarget autoTarget,
999
                           int level,
1000
                           const bool withText,
1001
                           const std::string &text,
1002
                           CastTypeT castType,
1003
                           const int offsetX,
1004
                           const int offsetY)
1005
{
1006
    SkillInfo *const info = skillDialog->getSkill(skillId);
1007
    if (info == nullptr)
1008
        return;
1009
    if (castType == CastType::Default)
1010
        castType = info->customCastType;
1011
    useSkill(info,
1012
        autoTarget,
1013
        level,
1014
        withText,
1015
        text,
1016
        castType,
1017
        offsetX,
1018
        offsetY);
1019
}
1020
1021
void SkillDialog::useSkill(const SkillInfo *const info,
1022
                           const AutoTarget autoTarget,
1023
                           int level,
1024
                           const bool withText,
1025
                           const std::string &text,
1026
                           const CastTypeT castType,
1027
                           const int offsetX,
1028
                           const int offsetY)
1029
{
1030
    if ((info == nullptr) || (localPlayer == nullptr))
1031
        return;
1032
    if (level == 0)
1033
        level = info->level;
1034
1035
    const SkillData *data = info->getData1(level);
1036
    if (data != nullptr)
1037
    {
1038
        const std::string cmd = data->invokeCmd;
1039
        if (!cmd.empty())
1040
            SpellManager::invokeCommand(cmd, localPlayer->getTarget());
1041
    }
1042
    switch (castType)
1043
    {
1044
        default:
1045
        case CastType::Default:
1046
            useSkillDefault(info,
1047
                autoTarget,
1048
                level,
1049
                withText,
1050
                text,
1051
                offsetX,
1052
                offsetY);
1053
            break;
1054
        case CastType::Target:
1055
        {
1056
            const Being *const being = localPlayer->getTarget();
1057
            useSkillTarget(info,
1058
                autoTarget,
1059
                level,
1060
                withText,
1061
                text,
1062
                being,
1063
                offsetX,
1064
                offsetY);
1065
            break;
1066
        }
1067
        case CastType::Position:
1068
        {
1069
            int x = 0;
1070
            int y = 0;
1071
            viewport->getMouseTile(x, y);
1072
            useSkillPosition(info,
1073
                level,
1074
                withText,
1075
                text,
1076
                x,
1077
                y,
1078
                offsetX,
1079
                offsetY);
1080
            break;
1081
        }
1082
        case CastType::Self:
1083
            // +++ probably need call useSkillSelf
1084
            useSkillTarget(info,
1085
                autoTarget,
1086
                level,
1087
                withText,
1088
                text,
1089
                localPlayer,
1090
                offsetX,
1091
                offsetY);
1092
            break;
1093
    }
1094
}
1095
1096
void SkillDialog::useSkillTarget(const SkillInfo *const info,
1097
                                 const AutoTarget autoTarget,
1098
                                 int level,
1099
                                 const bool withText,
1100
                                 const std::string &text,
1101
                                 const Being *being,
1102
                                 int offsetX,
1103
                                 int offsetY)
1104
{
1105
    SkillType::SkillType type = info->type;
1106
    if ((type & SkillType::Attack) != 0)
1107
    {
1108
        if ((being == nullptr) && autoTarget == AutoTarget_true)
1109
        {
1110
            if (localPlayer != nullptr)
1111
            {
1112
                being = localPlayer->setNewTarget(ActorType::Monster,
1113
                    AllowSort_true);
1114
            }
1115
        }
1116
        if (being != nullptr)
1117
        {
1118
            skillHandler->useBeing(info->id,
1119
                level,
1120
                being->getId());
1121
        }
1122
    }
1123
    else if ((type & SkillType::Support) != 0)
1124
    {
1125
        if (being == nullptr)
1126
            being = localPlayer;
1127
        if (being != nullptr)
1128
        {
1129
            skillHandler->useBeing(info->id,
1130
                level,
1131
                being->getId());
1132
        }
1133
    }
1134
    else if ((type & SkillType::Self) != 0)
1135
    {
1136
        skillHandler->useBeing(info->id,
1137
            level,
1138
            localPlayer->getId());
1139
    }
1140
    else if ((type & SkillType::Ground) != 0)
1141
    {
1142
        if (being == nullptr)
1143
            return;
1144
        being->fixDirectionOffsets(offsetX, offsetY);
1145
        const int x = being->getTileX() + offsetX;
1146
        const int y = being->getTileY() + offsetY;
1147
        if (info->useTextParameter)
1148
        {
1149
            if (withText)
1150
            {
1151
                skillHandler->usePos(info->id,
1152
                    level,
1153
                    x, y,
1154
                    text);
1155
            }
1156
            else
1157
            {
1158
                const SkillData *data = info->getData1(level);
1159
                textSkillListener.setSkill(info->id,
1160
                    x,
1161
                    y,
1162
                    level);
1163
                TextDialog *const dialog = CREATEWIDGETR(TextDialog,
1164
                    // TRANSLATORS: text skill dialog header
1165
                    strprintf(_("Add text to skill %s"),
1166
                    data->name.c_str()),
1167
                    // TRANSLATORS: text skill dialog field
1168
                    _("Text: "),
1169
                    nullptr,
1170
                    false);
1171
                dialog->setModal(Modal_true);
1172
                textSkillListener.setDialog(dialog);
1173
                dialog->setActionEventId("ok");
1174
                dialog->addActionListener(&textSkillListener);
1175
            }
1176
        }
1177
        else
1178
        {
1179
            skillHandler->usePos(info->id,
1180
                level,
1181
                x, y);
1182
        }
1183
    }
1184
    else if ((type & SkillType::TargetTrap) != 0)
1185
    {
1186
        // for now unused
1187
    }
1188
    else if (type == SkillType::Unknown ||
1189
             type == SkillType::Unused)
1190
    {
1191
        // unknown / unused
1192
    }
1193
    else
1194
    {
1195
        reportAlways("Unsupported skill type: %d", type)
1196
    }
1197
}
1198
1199
void SkillDialog::useSkillPosition(const SkillInfo *const info,
1200
                                   int level,
1201
                                   const bool withText,
1202
                                   const std::string &text,
1203
                                   const int x,
1204
                                   const int y,
1205
                                   int offsetX,
1206
                                   int offsetY)
1207
{
1208
    SkillType::SkillType type = info->type;
1209
    if ((type & SkillType::Ground) != 0)
1210
    {
1211
        localPlayer->fixDirectionOffsets(offsetX, offsetY);
1212
        if (info->useTextParameter)
1213
        {
1214
            if (withText)
1215
            {
1216
                skillHandler->usePos(info->id,
1217
                    level,
1218
                    x + offsetX,
1219
                    y + offsetY,
1220
                    text);
1221
            }
1222
            else
1223
            {
1224
                const SkillData *data = info->getData1(level);
1225
                textSkillListener.setSkill(info->id,
1226
                    x + offsetX,
1227
                    y + offsetY,
1228
                    level);
1229
                TextDialog *const dialog = CREATEWIDGETR(TextDialog,
1230
                    // TRANSLATORS: text skill dialog header
1231
                    strprintf(_("Add text to skill %s"),
1232
                    data->name.c_str()),
1233
                    // TRANSLATORS: text skill dialog field
1234
                    _("Text: "),
1235
                    nullptr,
1236
                    false);
1237
                dialog->setModal(Modal_true);
1238
                textSkillListener.setDialog(dialog);
1239
                dialog->setActionEventId("ok");
1240
                dialog->addActionListener(&textSkillListener);
1241
            }
1242
        }
1243
        else
1244
        {
1245
            skillHandler->usePos(info->id,
1246
                level,
1247
                x + offsetX,
1248
                y + offsetY);
1249
        }
1250
    }
1251
    else if ((type & SkillType::Support) != 0)
1252
    {
1253
        // wrong type
1254
        skillHandler->useBeing(info->id,
1255
            level,
1256
            localPlayer->getId());
1257
    }
1258
    else if ((type & SkillType::Self) != 0)
1259
    {
1260
        skillHandler->useBeing(info->id,
1261
            level,
1262
            localPlayer->getId());
1263
    }
1264
    else if ((type & SkillType::Attack) != 0)
1265
    {
1266
        // do nothing
1267
        // +++ probably need select some target on x,y position?
1268
    }
1269
    else if ((type & SkillType::TargetTrap) != 0)
1270
    {
1271
        // for now unused
1272
    }
1273
    else if (type == SkillType::Unknown ||
1274
             type == SkillType::Unused)
1275
    {
1276
        // unknown / unused
1277
    }
1278
    else
1279
    {
1280
        reportAlways("Unsupported skill type: %d", type)
1281
    }
1282
}
1283
1284
void SkillDialog::useSkillDefault(const SkillInfo *const info,
1285
                                  const AutoTarget autoTarget,
1286
                                  int level,
1287
                                  const bool withText,
1288
                                  const std::string &text,
1289
                                  int offsetX,
1290
                                  int offsetY)
1291
{
1292
    SkillType::SkillType type = info->type;
1293
    if ((type & SkillType::Attack) != 0)
1294
    {
1295
        const Being *being = localPlayer->getTarget();
1296
        if ((being == nullptr) && autoTarget == AutoTarget_true)
1297
        {
1298
            being = localPlayer->setNewTarget(ActorType::Monster,
1299
                AllowSort_true);
1300
        }
1301
        if (being != nullptr)
1302
        {
1303
            skillHandler->useBeing(info->id,
1304
                level,
1305
                being->getId());
1306
        }
1307
    }
1308
    else if ((type & SkillType::Support) != 0)
1309
    {
1310
        const Being *being = localPlayer->getTarget();
1311
        if (being == nullptr)
1312
            being = localPlayer;
1313
        if (being != nullptr)
1314
        {
1315
            skillHandler->useBeing(info->id,
1316
                level,
1317
                being->getId());
1318
        }
1319
    }
1320
    else if ((type & SkillType::Self) != 0)
1321
    {
1322
        skillHandler->useBeing(info->id,
1323
            level,
1324
            localPlayer->getId());
1325
    }
1326
    else if ((type & SkillType::Ground) != 0)
1327
    {
1328
        int x = 0;
1329
        int y = 0;
1330
        viewport->getMouseTile(x, y);
1331
        localPlayer->fixDirectionOffsets(offsetX, offsetY);
1332
        x += offsetX;
1333
        y += offsetY;
1334
        if (info->useTextParameter)
1335
        {
1336
            if (withText)
1337
            {
1338
                skillHandler->usePos(info->id,
1339
                    level,
1340
                    x, y,
1341
                    text);
1342
            }
1343
            else
1344
            {
1345
                const SkillData *data = info->getData1(level);
1346
                textSkillListener.setSkill(info->id,
1347
                    x,
1348
                    y,
1349
                    level);
1350
                TextDialog *const dialog = CREATEWIDGETR(TextDialog,
1351
                    // TRANSLATORS: text skill dialog header
1352
                    strprintf(_("Add text to skill %s"),
1353
                    data->name.c_str()),
1354
                    // TRANSLATORS: text skill dialog field
1355
                    _("Text: "),
1356
                    nullptr,
1357
                    false);
1358
                dialog->setModal(Modal_true);
1359
                textSkillListener.setDialog(dialog);
1360
                dialog->setActionEventId("ok");
1361
                dialog->addActionListener(&textSkillListener);
1362
            }
1363
        }
1364
        else
1365
        {
1366
            skillHandler->usePos(info->id,
1367
                level,
1368
                x, y);
1369
        }
1370
    }
1371
    else if ((type & SkillType::TargetTrap) != 0)
1372
    {
1373
        // for now unused
1374
    }
1375
    else if (type == SkillType::Unknown ||
1376
             type == SkillType::Unused)
1377
    {
1378
        // unknown / unused
1379
    }
1380
    else
1381
    {
1382
        reportAlways("Unsupported skill type: %d", type)
1383
    }
1384
}
1385
1386
void SkillDialog::addSkillDuration(SkillInfo *const skill)
1387
{
1388
    if (skill == nullptr)
1389
        return;
1390
1391
    FOR_EACH (STD_VECTOR<SkillInfo*>::const_iterator, it, mDurations)
1392
    {
1393
        if ((*it)->id == skill->id)
1394
            return;
1395
    }
1396
    mDurations.push_back(skill);
1397
}
1398
1399
void SkillDialog::slowLogic()
1400
{
1401
    FOR_EACH_SAFE (STD_VECTOR<SkillInfo*>::iterator, it, mDurations)
1402
    {
1403
        SkillInfo *const skill = *it;
1404
        if (skill != nullptr)
1405
        {
1406
            const int time = get_elapsed_time(skill->durationTime);
1407
            if (time >= skill->duration)
1408
            {
1409
                it = mDurations.erase(it);
1410
                skill->cooldown = 0;
1411
                skill->duration = 0;
1412
                skill->durationTime = 0;
1413
                if (it == mDurations.end())
1414
                    return;
1415
                if (it != mDurations.begin())
1416
                    -- it;
1417
            }
1418
            else if (time != 0)
1419
            {
1420
                skill->cooldown = skill->duration * 100 / time;
1421
            }
1422
        }
1423
    }
1424
}
1425
1426
void SkillDialog::selectSkillLevel(const int skillId,
1427
                                   const int level)
1428
{
1429
    SkillInfo *const info = getSkill(skillId);
1430
    if (info == nullptr)
1431
        return;
1432
    if (level > info->level)
1433
        info->customSelectedLevel = info->level;
1434
    else
1435
        info->customSelectedLevel = level;
1436
    info->update();
1437
}
1438
1439
void SkillDialog::selectSkillCastType(const int skillId,
1440
                                      const CastTypeT type)
1441
{
1442
    SkillInfo *const info = getSkill(skillId);
1443
    if (info == nullptr)
1444
        return;
1445
    info->customCastType = type;
1446
    info->update();
1447
}
1448
1449
void SkillDialog::setSkillOffsetX(const int skillId,
1450
                                  const int offset)
1451
{
1452
    SkillInfo *const info = getSkill(skillId);
1453
    if (info == nullptr)
1454
        return;
1455
    info->customOffsetX = offset;
1456
    info->update();
1457
}
1458
1459
void SkillDialog::setSkillOffsetY(const int skillId,
1460
                                  const int offset)
1461
{
1462
    SkillInfo *const info = getSkill(skillId);
1463
    if (info == nullptr)
1464
        return;
1465
    info->customOffsetY = offset;
1466
    info->update();
1467

3
}