GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/skilldialog.cpp Lines: 49 675 7.3 %
Date: 2017-11-29 Branches: 30 793 3.8 %

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-2017  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "gui/windows/skilldialog.h"
24
25
#include "configuration.h"
26
#include "effectmanager.h"
27
#include "spellmanager.h"
28
29
#include "being/localplayer.h"
30
#include "being/playerinfo.h"
31
32
#include "const/resources/spriteaction.h"
33
34
#include "enums/resources/skill/skillsettype.h"
35
36
#include "gui/shortcut/itemshortcut.h"
37
38
#include "gui/windows/setupwindow.h"
39
#include "gui/windows/shortcutwindow.h"
40
41
#include "gui/widgets/button.h"
42
#include "gui/widgets/createwidget.h"
43
#include "gui/widgets/label.h"
44
#include "gui/widgets/scrollarea.h"
45
#include "gui/widgets/tabbedarea.h"
46
47
#include "gui/widgets/tabs/skilltab.h"
48
49
#include "gui/windows/textdialog.h"
50
51
#include "listeners/textskilllistener.h"
52
53
#include "net/playerhandler.h"
54
#include "net/skillhandler.h"
55
56
#include "utils/checkutils.h"
57
#include "utils/dtor.h"
58
#include "utils/gettext.h"
59
#include "utils/timer.h"
60
61
#include "resources/beingcommon.h"
62
63
#include "debug.h"
64
65
SkillDialog *skillDialog = nullptr;
66
67
namespace
68
{
69
2
    TextSkillListener textSkillListener;
70
}  // namespace
71
72
static SkillOwner::Type parseOwner(const std::string &str)
73
{
74
    if (str == "player")
75
        return SkillOwner::Player;
76
    else if (str == "mercenary")
77
        return SkillOwner::Mercenary;
78
    else if (str == "homunculus")
79
        return SkillOwner::Homunculus;
80
    return SkillOwner::Player;
81
}
82
83
2
SkillDialog::SkillDialog() :
84
    // TRANSLATORS: skills dialog name
85
2
    Window(_("Skills"), Modal_false, nullptr, "skills.xml"),
86
    ActionListener(),
87
    mSkills(),
88
    mDurations(),
89

2
    mTabs(CREATEWIDGETR(TabbedArea, this)),
90
    mDeleteTabs(),
91

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

4
    mUseButton(new Button(this, _("Use"), "use", this)),
94
    // TRANSLATORS: skills dialog button
95

4
    mIncreaseButton(new Button(this, _("Up"), "inc", this)),
96
    mDefaultModel(nullptr),
97




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

6
}