GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/playerrelations.cpp Lines: 90 247 36.4 %
Date: 2017-11-29 Branches: 44 241 18.3 %

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-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 "being/playerrelations.h"
24
25
#include "actormanager.h"
26
#include "configuration.h"
27
#include "logger.h"
28
29
#include "being/localplayer.h"
30
#include "being/playerignorestrategy.h"
31
#include "being/playerrelation.h"
32
33
#include "utils/dtor.h"
34
#include "utils/foreach.h"
35
#include "utils/gettext.h"
36
37
#include "listeners/playerrelationslistener.h"
38
39
#include "debug.h"
40
41
static const unsigned int FIRST_IGNORE_EMOTE = 14;
42
43
typedef std::map<std::string, PlayerRelation *> PlayerRelations;
44
typedef PlayerRelations::const_iterator PlayerRelationsCIter;
45
typedef std::list<PlayerRelationsListener *> PlayerRelationListeners;
46
typedef PlayerRelationListeners::const_iterator PlayerRelationListenersCIter;
47
48
static const char *const PLAYER_IGNORE_STRATEGY_NOP = "nop";
49
static const char *const PLAYER_IGNORE_STRATEGY_EMOTE0 = "emote0";
50
static const char *const DEFAULT_IGNORE_STRATEGY =
51
    PLAYER_IGNORE_STRATEGY_EMOTE0;
52
53
static const char *const NAME = "name";
54
static const char *const RELATION = "relation";
55
56
static const unsigned int IGNORE_EMOTE_TIME = 100;
57
58
namespace
59
{
60
    class SortPlayersFunctor final
61
    {
62
        public:
63
            SortPlayersFunctor()
64
            { }
65
66
            A_DEFAULT_COPY(SortPlayersFunctor)
67
68
            bool operator() (const std::string &str1,
69
                             const std::string &str2) const
70
            {
71
                std::string s1 = str1;
72
                std::string s2 = str2;
73
                toLower(s1);
74
                toLower(s2);
75
                if (s1 == s2)
76
                    return str1 < str2;
77
                return s1 < s2;
78
            }
79
2
    } playersRelSorter;
80
81
    // (De)serialisation class
82
2
    class PlayerConfSerialiser final :
83
        public ConfigurationListManager<std::pair<std::string,
84
            PlayerRelation *>, std::map<std::string, PlayerRelation *> *>
85
    {
86
        public:
87
            PlayerConfSerialiser()
88
4
            { }
89
90
            A_DELETE_COPY(PlayerConfSerialiser)
91
92
            ConfigurationObject *writeConfigItem(
93
                const std::pair<std::string, PlayerRelation *> &value,
94
                ConfigurationObject *const cobj) const override final
95
            {
96
                if (cobj == nullptr ||
97
                    value.second == nullptr)
98
                {
99
                    return nullptr;
100
                }
101
                cobj->setValue(NAME, value.first);
102
                cobj->setValue(RELATION, toString(
103
                    CAST_S32(value.second->mRelation)));
104
105
                return cobj;
106
            }
107
108
            std::map<std::string, PlayerRelation *> *
109
            readConfigItem(const ConfigurationObject *const cobj,
110
                           std::map<std::string, PlayerRelation *>
111
                           *const container) const override final
112
            {
113
                if (cobj == nullptr ||
114
                    container == nullptr)
115
                {
116
                    return container;
117
                }
118
                const std::string name = cobj->getValue(NAME, "");
119
                if (name.empty())
120
                    return container;
121
122
                if ((*container)[name] == nullptr)
123
                {
124
                    const int v = cobj->getValueInt(RELATION,
125
                        CAST_S32(Relation::NEUTRAL));
126
127
                    (*container)[name] = new PlayerRelation(
128
                        static_cast<RelationT>(v));
129
                }
130
                // otherwise ignore the duplicate entry
131
132
                return container;
133
            }
134
    };
135
}  // namespace
136
137
2
static PlayerConfSerialiser player_conf_serialiser;  // stateless singleton
138
139
const unsigned int PlayerRelation::RELATION_PERMISSIONS[RELATIONS_NR] =
140
{
141
    /* NEUTRAL */     0,  // we always fall back to the defaults anyway
142
    /* FRIEND  */     EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE,
143
    /* DISREGARDED*/  EMOTE | SPEECH_FLOAT,
144
    /* IGNORED */     0,
145
    /* ERASED */      INVISIBLE,
146
    /* BLACKLISTED */ SPEECH_LOG | WHISPER,
147
    /* ENEMY2 */      EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE
148
};
149
150
2
PlayerRelationsManager::PlayerRelationsManager() :
151
    mPersistIgnores(false),
152
    mDefaultPermissions(PlayerRelation::DEFAULT),
153
    mIgnoreStrategy(nullptr),
154
    mRelations(),
155
    mListeners(),
156
8
    mIgnoreStrategies()
157
{
158
}
159
160
10
PlayerRelationsManager::~PlayerRelationsManager()
161
{
162
4
    delete_all(mIgnoreStrategies);
163
164
10
    FOR_EACH (PlayerRelationsCIter, it, mRelations)
165
        delete it->second;
166
4
    mRelations.clear();
167
2
}
168
169
void PlayerRelationsManager::clear()
170
{
171
    StringVect *const names = getPlayers();
172
    FOR_EACHP (StringVectCIter, it, names)
173
        removePlayer(*it);
174
    delete names;
175
}
176
177
static const char *const PERSIST_IGNORE_LIST = "persistent-player-list";
178
static const char *const PLAYER_IGNORE_STRATEGY = "player-ignore-strategy";
179
static const char *const DEFAULT_PERMISSIONS = "default-player-permissions";
180
181
int PlayerRelationsManager::getPlayerIgnoreStrategyIndex(
182
    const std::string &name)
183
{
184
    const STD_VECTOR<PlayerIgnoreStrategy *> *const strategies
185
        = getPlayerIgnoreStrategies();
186
187
    if (strategies == nullptr)
188
        return -1;
189
190
    const size_t sz = strategies->size();
191
    for (size_t i = 0; i < sz; i++)
192
    {
193
        if ((*strategies)[i]->mShortName == name)
194
            return CAST_S32(i);
195
    }
196
197
    return -1;
198
}
199
200
void PlayerRelationsManager::load()
201
{
202
    Configuration *const cfg = &serverConfig;
203
    clear();
204
205
    mPersistIgnores = (cfg->getValue(PERSIST_IGNORE_LIST, 1) != 0);
206
    mDefaultPermissions = CAST_S32(cfg->getValue(DEFAULT_PERMISSIONS,
207
                                           mDefaultPermissions));
208
209
    const std::string ignore_strategy_name = cfg->getValue(
210
        PLAYER_IGNORE_STRATEGY, DEFAULT_IGNORE_STRATEGY);
211
    const int ignore_strategy_index = getPlayerIgnoreStrategyIndex(
212
        ignore_strategy_name);
213
214
    if (ignore_strategy_index >= 0)
215
    {
216
        setPlayerIgnoreStrategy((*getPlayerIgnoreStrategies())
217
                                [ignore_strategy_index]);
218
    }
219
220
    cfg->getList<std::pair<std::string, PlayerRelation *>,
221
                   std::map<std::string, PlayerRelation *> *>
222
        ("player",  &(mRelations), &player_conf_serialiser);
223
}
224
225
226
void PlayerRelationsManager::init()
227
{
228
    load();
229
230
    if (!mPersistIgnores)
231
    {
232
        clear();  // Yes, we still keep them around in the config file
233
                  // until the next update.
234
    }
235
236
    FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
237
        (*it)->updateAll();
238
}
239
240
384
void PlayerRelationsManager::store() const
241
{
242
    serverConfig.setList<std::map<std::string,
243
        PlayerRelation *>::const_iterator,
244
        std::pair<std::string, PlayerRelation *>,
245
        std::map<std::string, PlayerRelation *> *>
246

2304
        ("player", mRelations.begin(), mRelations.end(),
247
        &player_conf_serialiser);
248
249

1536
    serverConfig.setValue(DEFAULT_PERMISSIONS, mDefaultPermissions);
250
1920
    serverConfig.setValue(PERSIST_IGNORE_LIST, mPersistIgnores);
251

1536
    serverConfig.setValue(PLAYER_IGNORE_STRATEGY,
252

1536
        mIgnoreStrategy != nullptr ? mIgnoreStrategy->mShortName :
253
        DEFAULT_IGNORE_STRATEGY);
254
255
384
    serverConfig.write();
256
384
}
257
258
void PlayerRelationsManager::signalUpdate(const std::string &name)
259
{
260
    FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
261
        (*it)->updatedPlayer(name);
262
263
    if (actorManager != nullptr)
264
    {
265
        Being *const being = actorManager->findBeingByName(
266
            name, ActorType::Player);
267
268
        if (being != nullptr &&
269
            being->getType() == ActorType::Player)
270
        {
271
            being->updateColors();
272
        }
273
    }
274
}
275
276
unsigned int PlayerRelationsManager::checkPermissionSilently(
277
    const std::string &player_name, const unsigned int flags) const
278
{
279
    const std::map<std::string, PlayerRelation *>::const_iterator
280
        it = mRelations.find(player_name);
281
    if (it == mRelations.end())
282
    {
283
        return mDefaultPermissions & flags;
284
    }
285
286
    const PlayerRelation *const r = (*it).second;
287
    unsigned int permissions = PlayerRelation::RELATION_PERMISSIONS[
288
        CAST_S32(r->mRelation)];
289
290
    switch (r->mRelation)
291
    {
292
        case Relation::NEUTRAL:
293
            permissions = mDefaultPermissions;
294
            break;
295
296
        case Relation::FRIEND:
297
            permissions |= mDefaultPermissions;  // widen
298
            break;
299
300
        case Relation::DISREGARDED:
301
        case Relation::IGNORED:
302
        case Relation::ERASED:
303
        case Relation::BLACKLISTED:
304
        case Relation::ENEMY2:
305
        default:
306
            permissions &= mDefaultPermissions;  // narrow
307
            break;
308
    }
309
310
    return permissions & flags;
311
}
312
313
bool PlayerRelationsManager::hasPermission(const Being *const being,
314
                                           const unsigned int flags) const
315
{
316
    if (being == nullptr)
317
        return false;
318
319
    if (being->getType() == ActorType::Player)
320
    {
321
        return static_cast<unsigned int>(hasPermission(
322
            being->getName(), flags)) == flags;
323
    }
324
    return true;
325
}
326
327
bool PlayerRelationsManager::hasPermission(const std::string &name,
328
                                           const unsigned int flags) const
329
{
330
    if (actorManager == nullptr)
331
        return false;
332
333
    const unsigned int rejections = flags
334
        & ~checkPermissionSilently(name, flags);
335
    const bool permitted = (rejections == 0);
336
337
    if (!permitted)
338
    {
339
        // execute `ignore' strategy, if possible
340
        if (mIgnoreStrategy != nullptr)
341
        {
342
            Being *const b = actorManager->findBeingByName(
343
                name, ActorType::Player);
344
345
            if ((b != nullptr) && b->getType() == ActorType::Player)
346
                mIgnoreStrategy->ignore(b, rejections);
347
        }
348
    }
349
350
    return permitted;
351
}
352
353
void PlayerRelationsManager::setRelation(const std::string &player_name,
354
                                         const RelationT relation)
355
{
356
    if (localPlayer == nullptr ||
357
        (relation != Relation::NEUTRAL &&
358
        localPlayer->getName() == player_name))
359
    {
360
        return;
361
    }
362
363
    PlayerRelation *const r = mRelations[player_name];
364
    if (r == nullptr)
365
        mRelations[player_name] = new PlayerRelation(relation);
366
    else
367
        r->mRelation = relation;
368
369
    signalUpdate(player_name);
370
    store();
371
}
372
373
4
StringVect *PlayerRelationsManager::getPlayers() const
374
{
375
8
    StringVect *const retval = new StringVect;
376
377
12
    FOR_EACH (PlayerRelationsCIter, it, mRelations)
378
    {
379
        if (it->second != nullptr)
380
            retval->push_back(it->first);
381
    }
382
383
8
    std::sort(retval->begin(), retval->end(), playersRelSorter);
384
385
4
    return retval;
386
}
387
388
StringVect *PlayerRelationsManager::getPlayersByRelation(
389
    const RelationT rel) const
390
{
391
    StringVect *const retval = new StringVect;
392
393
    FOR_EACH (PlayerRelationsCIter, it, mRelations)
394
    {
395
        if ((it->second != nullptr) &&
396
            it->second->mRelation == rel)
397
        {
398
            retval->push_back(it->first);
399
        }
400
    }
401
402
    std::sort(retval->begin(), retval->end(), playersRelSorter);
403
404
    return retval;
405
}
406
407
void PlayerRelationsManager::removePlayer(const std::string &name)
408
{
409
    delete mRelations[name];
410
    mRelations.erase(name);
411
    signalUpdate(name);
412
}
413
414
415
980
RelationT PlayerRelationsManager::getRelation(
416
    const std::string &name) const
417
{
418
    const std::map<std::string, PlayerRelation *>::const_iterator
419
1960
        it = mRelations.find(name);
420

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

18
        mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE,
582
                                    // TRANSLATORS: ignore strategi
583
2
                                    _("Floating '...' bubble"),
584

2
                                    PLAYER_IGNORE_STRATEGY_EMOTE0));
585

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

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

60
    const std::string check = config.getStringValue("unsecureChars");
638
24
    const std::string lastChar = name.substr(size - 1, 1);
639
640

48
    if (name.substr(0, 1) == " " ||
641
24
        lastChar == " " ||
642

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

6
PlayerRelationsManager playerRelations;