GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/skilldialog.cpp Lines: 49 688 7.1 %
Date: 2018-07-14 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-2018  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
1
    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
1
SkillDialog::SkillDialog() :
84
    // TRANSLATORS: skills dialog name
85
1
    Window(_("Skills"), Modal_false, nullptr, "skills.xml"),
86
    ActionListener(),
87
    mSkills(),
88
    mDurations(),
89


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

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

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

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



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

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

3
}