GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/playerrelations.cpp Lines: 92 247 37.2 %
Date: 2021-03-17 Branches: 37 229 16.2 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2008-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "being/playerrelations.h"
25
26
#include "actormanager.h"
27
#include "configuration.h"
28
#include "logger.h"
29
30
#include "being/localplayer.h"
31
#include "being/playerignorestrategy.h"
32
#include "being/playerrelation.h"
33
34
#include "utils/dtor.h"
35
#include "utils/foreach.h"
36
#include "utils/gettext.h"
37
38
#include "listeners/playerrelationslistener.h"
39
40
#include "debug.h"
41
42
static const unsigned int FIRST_IGNORE_EMOTE = 14;
43
44
typedef std::map<std::string, PlayerRelation *> PlayerRelations;
45
typedef PlayerRelations::const_iterator PlayerRelationsCIter;
46
typedef std::list<PlayerRelationsListener *> PlayerRelationListeners;
47
typedef PlayerRelationListeners::const_iterator PlayerRelationListenersCIter;
48
49
static const char *const PLAYER_IGNORE_STRATEGY_NOP = "nop";
50
static const char *const PLAYER_IGNORE_STRATEGY_EMOTE0 = "emote0";
51
static const char *const DEFAULT_IGNORE_STRATEGY =
52
    PLAYER_IGNORE_STRATEGY_EMOTE0;
53
54
static const char *const NAME = "name";
55
static const char *const RELATION = "relation";
56
57
static const unsigned int IGNORE_EMOTE_TIME = 100;
58
59
namespace
60
{
61
    class SortPlayersFunctor final
62
    {
63
        public:
64
            SortPlayersFunctor()
65
            { }
66
67
            A_DEFAULT_COPY(SortPlayersFunctor)
68
69
            bool operator() (const std::string &str1,
70
                             const std::string &str2) const
71
            {
72
                std::string s1 = str1;
73
                std::string s2 = str2;
74
                toLower(s1);
75
                toLower(s2);
76
                if (s1 == s2)
77
                    return str1 < str2;
78
                return s1 < s2;
79
            }
80
1
    } playersRelSorter;
81
82
    // (De)serialisation class
83
1
    class PlayerConfSerialiser final :
84
        public ConfigurationListManager<std::pair<std::string,
85
            PlayerRelation *>, std::map<std::string, PlayerRelation *> *>
86
    {
87
        public:
88
            PlayerConfSerialiser()
89
2
            { }
90
91
            A_DELETE_COPY(PlayerConfSerialiser)
92
93
            ConfigurationObject *writeConfigItem(
94
                const std::pair<std::string, PlayerRelation *> &value,
95
                ConfigurationObject *const cobj) const override final
96
            {
97
                if (cobj == nullptr ||
98
                    value.second == nullptr)
99
                {
100
                    return nullptr;
101
                }
102
                cobj->setValue(NAME, value.first);
103
                cobj->setValue(RELATION, toString(
104
                    CAST_S32(value.second->mRelation)));
105
106
                return cobj;
107
            }
108
109
            std::map<std::string, PlayerRelation *> *
110
            readConfigItem(const ConfigurationObject *const cobj,
111
                           std::map<std::string, PlayerRelation *>
112
                           *const container) const override final
113
            {
114
                if (cobj == nullptr ||
115
                    container == nullptr)
116
                {
117
                    return container;
118
                }
119
                const std::string name = cobj->getValue(NAME, "");
120
                if (name.empty())
121
                    return container;
122
123
                if ((*container)[name] == nullptr)
124
                {
125
                    const int v = cobj->getValueInt(RELATION,
126
                        CAST_S32(Relation::NEUTRAL));
127
128
                    (*container)[name] = new PlayerRelation(
129
                        static_cast<RelationT>(v));
130
                }
131
                // otherwise ignore the duplicate entry
132
133
                return container;
134
            }
135
    };
136
}  // namespace
137
138
1
static PlayerConfSerialiser player_conf_serialiser;  // stateless singleton
139
140
const unsigned int PlayerRelation::RELATION_PERMISSIONS[RELATIONS_NR] =
141
{
142
    /* NEUTRAL */     0,  // we always fall back to the defaults anyway
143
    /* FRIEND  */     EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE,
144
    /* DISREGARDED*/  EMOTE | SPEECH_FLOAT,
145
    /* IGNORED */     0,
146
    /* ERASED */      INVISIBLE,
147
    /* BLACKLISTED */ SPEECH_LOG | WHISPER,
148
    /* ENEMY2 */      EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE
149
};
150
151
1
PlayerRelationsManager::PlayerRelationsManager() :
152
    mPersistIgnores(false),
153
    mDefaultPermissions(PlayerRelation::DEFAULT),
154
    mIgnoreStrategy(nullptr),
155
    mRelations(),
156
    mListeners(),
157
4
    mIgnoreStrategies()
158
{
159
}
160
161
5
PlayerRelationsManager::~PlayerRelationsManager()
162
{
163
2
    delete_all(mIgnoreStrategies);
164
165
5
    FOR_EACH (PlayerRelationsCIter, it, mRelations)
166
        delete it->second;
167
2
    mRelations.clear();
168
1
}
169
170
void PlayerRelationsManager::clear()
171
{
172
    StringVect *const names = getPlayers();
173
    FOR_EACHP (StringVectCIter, it, names)
174
        removePlayer(*it);
175
    delete names;
176
}
177
178
static const char *const PERSIST_IGNORE_LIST = "persistent-player-list";
179
static const char *const PLAYER_IGNORE_STRATEGY = "player-ignore-strategy";
180
static const char *const DEFAULT_PERMISSIONS = "default-player-permissions";
181
182
int PlayerRelationsManager::getPlayerIgnoreStrategyIndex(
183
    const std::string &name)
184
{
185
    const STD_VECTOR<PlayerIgnoreStrategy *> *const strategies
186
        = getPlayerIgnoreStrategies();
187
188
    if (strategies == nullptr)
189
        return -1;
190
191
    const size_t sz = strategies->size();
192
    for (size_t i = 0; i < sz; i++)
193
    {
194
        if ((*strategies)[i]->mShortName == name)
195
            return CAST_S32(i);
196
    }
197
198
    return -1;
199
}
200
201
void PlayerRelationsManager::load()
202
{
203
    Configuration *const cfg = &serverConfig;
204
    clear();
205
206
    mPersistIgnores = (cfg->getValue(PERSIST_IGNORE_LIST, 1) != 0);
207
    mDefaultPermissions = CAST_S32(cfg->getValue(DEFAULT_PERMISSIONS,
208
                                           mDefaultPermissions));
209
210
    const std::string ignore_strategy_name = cfg->getValue(
211
        PLAYER_IGNORE_STRATEGY, DEFAULT_IGNORE_STRATEGY);
212
    const int ignore_strategy_index = getPlayerIgnoreStrategyIndex(
213
        ignore_strategy_name);
214
215
    if (ignore_strategy_index >= 0)
216
    {
217
        setPlayerIgnoreStrategy((*getPlayerIgnoreStrategies())
218
                                [ignore_strategy_index]);
219
    }
220
221
    cfg->getList<std::pair<std::string, PlayerRelation *>,
222
                   std::map<std::string, PlayerRelation *> *>
223
        ("player",  &(mRelations), &player_conf_serialiser);
224
}
225
226
227
void PlayerRelationsManager::init()
228
{
229
    load();
230
231
    if (!mPersistIgnores)
232
    {
233
        clear();  // Yes, we still keep them around in the config file
234
                  // until the next update.
235
    }
236
237
    FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
238
        (*it)->updateAll();
239
}
240
241
204
void PlayerRelationsManager::store() const
242
{
243
    serverConfig.setList<std::map<std::string,
244
        PlayerRelation *>::const_iterator,
245
        std::pair<std::string, PlayerRelation *>,
246
        std::map<std::string, PlayerRelation *> *>
247
1224
        ("player", mRelations.begin(), mRelations.end(),
248
204
        &player_conf_serialiser);
249
250
816
    serverConfig.setValue(DEFAULT_PERMISSIONS, mDefaultPermissions);
251
1020
    serverConfig.setValue(PERSIST_IGNORE_LIST, mPersistIgnores);
252
816
    serverConfig.setValue(PLAYER_IGNORE_STRATEGY,
253
816
        mIgnoreStrategy != nullptr ? mIgnoreStrategy->mShortName :
254
204
        DEFAULT_IGNORE_STRATEGY);
255
256
204
    serverConfig.write();
257
204
}
258
259
void PlayerRelationsManager::signalUpdate(const std::string &name)
260
{
261
    FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
262
        (*it)->updatedPlayer(name);
263
264
    if (actorManager != nullptr)
265
    {
266
        Being *const being = actorManager->findBeingByName(
267
            name, ActorType::Player);
268
269
        if (being != nullptr &&
270
            being->getType() == ActorType::Player)
271
        {
272
            being->updateColors();
273
        }
274
    }
275
}
276
277
unsigned int PlayerRelationsManager::checkPermissionSilently(
278
    const std::string &player_name, const unsigned int flags) const
279
{
280
    const std::map<std::string, PlayerRelation *>::const_iterator
281
        it = mRelations.find(player_name);
282
    if (it == mRelations.end())
283
    {
284
        return mDefaultPermissions & flags;
285
    }
286
287
    const PlayerRelation *const r = (*it).second;
288
    unsigned int permissions = PlayerRelation::RELATION_PERMISSIONS[
289
        CAST_S32(r->mRelation)];
290
291
    switch (r->mRelation)
292
    {
293
        case Relation::NEUTRAL:
294
            permissions = mDefaultPermissions;
295
            break;
296
297
        case Relation::FRIEND:
298
            permissions |= mDefaultPermissions;  // widen
299
            break;
300
301
        case Relation::DISREGARDED:
302
        case Relation::IGNORED:
303
        case Relation::ERASED:
304
        case Relation::BLACKLISTED:
305
        case Relation::ENEMY2:
306
        default:
307
            permissions &= mDefaultPermissions;  // narrow
308
            break;
309
    }
310
311
    return permissions & flags;
312
}
313
314
bool PlayerRelationsManager::hasPermission(const Being *const being,
315
                                           const unsigned int flags) const
316
{
317
    if (being == nullptr)
318
        return false;
319
320
    if (being->getType() == ActorType::Player)
321
    {
322
        return static_cast<unsigned int>(hasPermission(
323
            being->getName(), flags)) == flags;
324
    }
325
    return true;
326
}
327
328
bool PlayerRelationsManager::hasPermission(const std::string &name,
329
                                           const unsigned int flags) const
330
{
331
    if (actorManager == nullptr)
332
        return false;
333
334
    const unsigned int rejections = flags
335
        & ~checkPermissionSilently(name, flags);
336
    const bool permitted = (rejections == 0);
337
338
    if (!permitted)
339
    {
340
        // execute `ignore' strategy, if possible
341
        if (mIgnoreStrategy != nullptr)
342
        {
343
            Being *const b = actorManager->findBeingByName(
344
                name, ActorType::Player);
345
346
            if ((b != nullptr) && b->getType() == ActorType::Player)
347
                mIgnoreStrategy->ignore(b, rejections);
348
        }
349
    }
350
351
    return permitted;
352
}
353
354
void PlayerRelationsManager::setRelation(const std::string &player_name,
355
                                         const RelationT relation)
356
{
357
    if (localPlayer == nullptr ||
358
        (relation != Relation::NEUTRAL &&
359
        localPlayer->getName() == player_name))
360
    {
361
        return;
362
    }
363
364
    PlayerRelation *const r = mRelations[player_name];
365
    if (r == nullptr)
366
        mRelations[player_name] = new PlayerRelation(relation);
367
    else
368
        r->mRelation = relation;
369
370
    signalUpdate(player_name);
371
    store();
372
}
373
374
2
StringVect *PlayerRelationsManager::getPlayers() const
375
{
376
4
    StringVect *const retval = new StringVect;
377
378
6
    FOR_EACH (PlayerRelationsCIter, it, mRelations)
379
    {
380
        if (it->second != nullptr)
381
            retval->push_back(it->first);
382
    }
383
384
4
    std::sort(retval->begin(), retval->end(), playersRelSorter);
385
386
2
    return retval;
387
}
388
389
StringVect *PlayerRelationsManager::getPlayersByRelation(
390
    const RelationT rel) const
391
{
392
    StringVect *const retval = new StringVect;
393
394
    FOR_EACH (PlayerRelationsCIter, it, mRelations)
395
    {
396
        if ((it->second != nullptr) &&
397
            it->second->mRelation == rel)
398
        {
399
            retval->push_back(it->first);
400
        }
401
    }
402
403
    std::sort(retval->begin(), retval->end(), playersRelSorter);
404
405
    return retval;
406
}
407
408
void PlayerRelationsManager::removePlayer(const std::string &name)
409
{
410
    delete mRelations[name];
411
    mRelations.erase(name);
412
    signalUpdate(name);
413
}
414
415
416
497
RelationT PlayerRelationsManager::getRelation(
417
    const std::string &name) const
418
{
419
    const std::map<std::string, PlayerRelation *>::const_iterator
420
994
        it = mRelations.find(name);
421

994
    if (it != mRelations.end())
422
        return (*it).second->mRelation;
423
424
    return Relation::NEUTRAL;
425
}
426
427
////////////////////////////////////////
428
// defaults
429
430
4
unsigned int PlayerRelationsManager::getDefault() const
431
{
432
4
    return mDefaultPermissions;
433
}
434
435
void PlayerRelationsManager::setDefault(const unsigned int permissions)
436
{
437
    mDefaultPermissions = permissions;
438
439
    store();
440
    signalUpdate("");
441
}
442
443
void PlayerRelationsManager::ignoreTrade(const std::string &name) const
444
{
445
    if (name.empty())
446
        return;
447
448
    const RelationT relation = getRelation(name);
449
450
    if (relation == Relation::IGNORED ||
451
        relation == Relation::DISREGARDED ||
452
        relation == Relation::BLACKLISTED ||
453
        relation == Relation::ERASED)
454
    {
455
        return;
456
    }
457
    playerRelations.setRelation(name, Relation::BLACKLISTED);
458
}
459
460
bool PlayerRelationsManager::checkBadRelation(const std::string &name) const
461
{
462
    if (name.empty())
463
        return true;
464
465
    const RelationT relation = getRelation(name);
466
467
    if (relation == Relation::IGNORED ||
468
        relation == Relation::DISREGARDED ||
469
        relation == Relation::BLACKLISTED ||
470
        relation == Relation::ERASED ||
471
        relation == Relation::ENEMY2)
472
    {
473
        return true;
474
    }
475
    return false;
476
}
477
478
////////////////////////////////////////
479
// ignore strategies
480
481
482
1
class PIS_nothing final : public PlayerIgnoreStrategy
483
{
484
    public:
485
1
        PIS_nothing() :
486
2
            PlayerIgnoreStrategy()
487
        {
488
            // TRANSLATORS: ignore/unignore action
489
2
            mDescription = _("Completely ignore");
490
2
            mShortName = PLAYER_IGNORE_STRATEGY_NOP;
491
1
        }
492
493
        A_DELETE_COPY(PIS_nothing)
494
495
        void ignore(Being *const being A_UNUSED,
496
                    const unsigned int flags A_UNUSED) const override final
497
        {
498
        }
499
};
500
501
1
class PIS_dotdotdot final : public PlayerIgnoreStrategy
502
{
503
    public:
504
1
        PIS_dotdotdot() :
505
2
            PlayerIgnoreStrategy()
506
        {
507
            // TRANSLATORS: ignore/unignore action
508
2
            mDescription = _("Print '...'");
509
2
            mShortName = "dotdotdot";
510
1
        }
511
512
        A_DELETE_COPY(PIS_dotdotdot)
513
514
        void ignore(Being *const being,
515
                    const unsigned int flags A_UNUSED) const override final
516
        {
517
            if (being == nullptr)
518
                return;
519
520
            logger->log("ignoring: " + being->getName());
521
            being->setSpeech("...");
522
        }
523
};
524
525
526
1
class PIS_blinkname final : public PlayerIgnoreStrategy
527
{
528
    public:
529
1
        PIS_blinkname() :
530
2
            PlayerIgnoreStrategy()
531
        {
532
            // TRANSLATORS: ignore/unignore action
533
2
            mDescription = _("Blink name");
534
2
            mShortName = "blinkname";
535
1
        }
536
537
        A_DELETE_COPY(PIS_blinkname)
538
539
        void ignore(Being *const being,
540
                    const unsigned int flags A_UNUSED) const override final
541
        {
542
            if (being == nullptr)
543
                return;
544
545
            logger->log("ignoring: " + being->getName());
546
            being->flashName(200);
547
        }
548
};
549
550
2
class PIS_emote final : public PlayerIgnoreStrategy
551
{
552
    public:
553
2
        PIS_emote(const uint8_t emote_nr,
554
                  const std::string &description,
555
2
                  const std::string &shortname) :
556
            PlayerIgnoreStrategy(),
557
4
            mEmotion(emote_nr)
558
        {
559
4
            mDescription = description;
560
4
            mShortName = shortname;
561
2
        }
562
563
        A_DELETE_COPY(PIS_emote)
564
565
        void ignore(Being *const being,
566
                    const unsigned int flags A_UNUSED) const override final
567
        {
568
            if (being == nullptr)
569
                return;
570
571
            being->setEmote(mEmotion, IGNORE_EMOTE_TIME);
572
        }
573
        uint8_t mEmotion;
574
};
575
576
STD_VECTOR<PlayerIgnoreStrategy *> *
577
28
PlayerRelationsManager::getPlayerIgnoreStrategies()
578
{
579
56
    if (mIgnoreStrategies.empty())
580
    {
581
        // not initialised yet?
582
9
        mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE,
583
                                    // TRANSLATORS: ignore strategi
584
1
                                    _("Floating '...' bubble"),
585

2
                                    PLAYER_IGNORE_STRATEGY_EMOTE0));
586
9
        mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE + 1,
587
                                    // TRANSLATORS: ignore strategi
588
1
                                    _("Floating bubble"),
589

2
                                    "emote1"));
590
2
        mIgnoreStrategies.push_back(new PIS_nothing);
591
2
        mIgnoreStrategies.push_back(new PIS_dotdotdot);
592
2
        mIgnoreStrategies.push_back(new PIS_blinkname);
593
    }
594
28
    return &mIgnoreStrategies;
595
}
596
597
bool PlayerRelationsManager::isGoodName(const std::string &name) const
598
{
599
    const size_t size = name.size();
600
601
    if (size < 3)
602
        return true;
603
604
    const std::map<std::string, PlayerRelation *>::const_iterator
605
        it = mRelations.find(name);
606
    if (it != mRelations.end())
607
        return true;
608
609
    return checkName(name);
610
}
611
612
7
bool PlayerRelationsManager::isGoodName(Being *const being) const
613
{
614
7
    if (being == nullptr)
615
        return false;
616
7
    if (being->getGoodStatus() != -1)
617
1
        return being->getGoodStatus() == 1;
618
619
6
    const std::string &name = being->getName();
620
6
    const size_t size = name.size();
621
622
6
    if (size < 3)
623
        return true;
624
625
    const std::map<std::string, PlayerRelation *>::const_iterator
626
12
        it = mRelations.find(name);
627
12
    if (it != mRelations.end())
628
        return true;
629
630
6
    const bool status = checkName(name);
631
12
    being->setGoodStatus(status ? 1 : 0);
632
6
    return status;
633
}
634
635
6
bool PlayerRelationsManager::checkName(const std::string &name)
636
{
637
6
    const size_t size = name.size();
638
30
    const std::string check = config.getStringValue("unsecureChars");
639
12
    const std::string lastChar = name.substr(size - 1, 1);
640
641


24
    if (name.substr(0, 1) == " " ||
642
12
        lastChar == " " ||
643

24
        lastChar == "." ||
644
6
        name.find("  ") != std::string::npos)
645
    {
646
        return false;
647
    }
648
6
    else if (check.empty())
649
    {
650
        return true;
651
    }
652
6
    else if (name.find_first_of(check) != std::string::npos)
653
    {
654
        return false;
655
    }
656
    else
657
    {
658
6
        return true;
659
    }
660
}
661
662

3
PlayerRelationsManager playerRelations;