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:
SortPlayersFunctor()64             SortPlayersFunctor()
65             { }
66 
A_DEFAULT_COPY(SortPlayersFunctor)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     } playersRelSorter;
81 
82     // (De)serialisation class
83     class PlayerConfSerialiser final :
84         public ConfigurationListManager<std::pair<std::string,
85             PlayerRelation *>, std::map<std::string, PlayerRelation *> *>
86     {
87         public:
PlayerConfSerialiser()88             PlayerConfSerialiser()
89             { }
90 
A_DELETE_COPY(PlayerConfSerialiser)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 *> *
readConfigItem(const ConfigurationObject * const cobj,std::map<std::string,PlayerRelation * > * const container) const110             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 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 
PlayerRelationsManager()151 PlayerRelationsManager::PlayerRelationsManager() :
152     mPersistIgnores(false),
153     mDefaultPermissions(PlayerRelation::DEFAULT),
154     mIgnoreStrategy(nullptr),
155     mRelations(),
156     mListeners(),
157     mIgnoreStrategies()
158 {
159 }
160 
~PlayerRelationsManager()161 PlayerRelationsManager::~PlayerRelationsManager()
162 {
163     delete_all(mIgnoreStrategies);
164 
165     FOR_EACH (PlayerRelationsCIter, it, mRelations)
166         delete it->second;
167     mRelations.clear();
168 }
169 
clear()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 
getPlayerIgnoreStrategyIndex(const std::string & name)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 
load()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 
init()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 
store() const241 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         ("player", mRelations.begin(), mRelations.end(),
248         &player_conf_serialiser);
249 
250     serverConfig.setValue(DEFAULT_PERMISSIONS, mDefaultPermissions);
251     serverConfig.setValue(PERSIST_IGNORE_LIST, mPersistIgnores);
252     serverConfig.setValue(PLAYER_IGNORE_STRATEGY,
253         mIgnoreStrategy != nullptr ? mIgnoreStrategy->mShortName :
254         DEFAULT_IGNORE_STRATEGY);
255 
256     serverConfig.write();
257 }
258 
signalUpdate(const std::string & name)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 
checkPermissionSilently(const std::string & player_name,const unsigned int flags) const277 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 
hasPermission(const Being * const being,const unsigned int flags) const314 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 
hasPermission(const std::string & name,const unsigned int flags) const328 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 
setRelation(const std::string & player_name,const RelationT relation)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 
getPlayers() const374 StringVect *PlayerRelationsManager::getPlayers() const
375 {
376     StringVect *const retval = new StringVect;
377 
378     FOR_EACH (PlayerRelationsCIter, it, mRelations)
379     {
380         if (it->second != nullptr)
381             retval->push_back(it->first);
382     }
383 
384     std::sort(retval->begin(), retval->end(), playersRelSorter);
385 
386     return retval;
387 }
388 
getPlayersByRelation(const RelationT rel) const389 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 
removePlayer(const std::string & name)408 void PlayerRelationsManager::removePlayer(const std::string &name)
409 {
410     delete mRelations[name];
411     mRelations.erase(name);
412     signalUpdate(name);
413 }
414 
415 
getRelation(const std::string & name) const416 RelationT PlayerRelationsManager::getRelation(
417     const std::string &name) const
418 {
419     const std::map<std::string, PlayerRelation *>::const_iterator
420         it = mRelations.find(name);
421     if (it != mRelations.end())
422         return (*it).second->mRelation;
423 
424     return Relation::NEUTRAL;
425 }
426 
427 ////////////////////////////////////////
428 // defaults
429 
getDefault() const430 unsigned int PlayerRelationsManager::getDefault() const
431 {
432     return mDefaultPermissions;
433 }
434 
setDefault(const unsigned int permissions)435 void PlayerRelationsManager::setDefault(const unsigned int permissions)
436 {
437     mDefaultPermissions = permissions;
438 
439     store();
440     signalUpdate("");
441 }
442 
ignoreTrade(const std::string & name) const443 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 
checkBadRelation(const std::string & name) const460 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 class PIS_nothing final : public PlayerIgnoreStrategy
483 {
484     public:
PIS_nothing()485         PIS_nothing() :
486             PlayerIgnoreStrategy()
487         {
488             // TRANSLATORS: ignore/unignore action
489             mDescription = _("Completely ignore");
490             mShortName = PLAYER_IGNORE_STRATEGY_NOP;
491         }
492 
A_DELETE_COPY(PIS_nothing)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 class PIS_dotdotdot final : public PlayerIgnoreStrategy
502 {
503     public:
PIS_dotdotdot()504         PIS_dotdotdot() :
505             PlayerIgnoreStrategy()
506         {
507             // TRANSLATORS: ignore/unignore action
508             mDescription = _("Print '...'");
509             mShortName = "dotdotdot";
510         }
511 
A_DELETE_COPY(PIS_dotdotdot)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 class PIS_blinkname final : public PlayerIgnoreStrategy
527 {
528     public:
PIS_blinkname()529         PIS_blinkname() :
530             PlayerIgnoreStrategy()
531         {
532             // TRANSLATORS: ignore/unignore action
533             mDescription = _("Blink name");
534             mShortName = "blinkname";
535         }
536 
A_DELETE_COPY(PIS_blinkname)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 class PIS_emote final : public PlayerIgnoreStrategy
551 {
552     public:
PIS_emote(const uint8_t emote_nr,const std::string & description,const std::string & shortname)553         PIS_emote(const uint8_t emote_nr,
554                   const std::string &description,
555                   const std::string &shortname) :
556             PlayerIgnoreStrategy(),
557             mEmotion(emote_nr)
558         {
559             mDescription = description;
560             mShortName = shortname;
561         }
562 
A_DELETE_COPY(PIS_emote)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 *> *
getPlayerIgnoreStrategies()577 PlayerRelationsManager::getPlayerIgnoreStrategies()
578 {
579     if (mIgnoreStrategies.empty())
580     {
581         // not initialised yet?
582         mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE,
583                                     // TRANSLATORS: ignore strategi
584                                     _("Floating '...' bubble"),
585                                     PLAYER_IGNORE_STRATEGY_EMOTE0));
586         mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE + 1,
587                                     // TRANSLATORS: ignore strategi
588                                     _("Floating bubble"),
589                                     "emote1"));
590         mIgnoreStrategies.push_back(new PIS_nothing);
591         mIgnoreStrategies.push_back(new PIS_dotdotdot);
592         mIgnoreStrategies.push_back(new PIS_blinkname);
593     }
594     return &mIgnoreStrategies;
595 }
596 
isGoodName(const std::string & name) const597 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 
isGoodName(Being * const being) const612 bool PlayerRelationsManager::isGoodName(Being *const being) const
613 {
614     if (being == nullptr)
615         return false;
616     if (being->getGoodStatus() != -1)
617         return being->getGoodStatus() == 1;
618 
619     const std::string &name = being->getName();
620     const size_t size = name.size();
621 
622     if (size < 3)
623         return true;
624 
625     const std::map<std::string, PlayerRelation *>::const_iterator
626         it = mRelations.find(name);
627     if (it != mRelations.end())
628         return true;
629 
630     const bool status = checkName(name);
631     being->setGoodStatus(status ? 1 : 0);
632     return status;
633 }
634 
checkName(const std::string & name)635 bool PlayerRelationsManager::checkName(const std::string &name)
636 {
637     const size_t size = name.size();
638     const std::string check = config.getStringValue("unsecureChars");
639     const std::string lastChar = name.substr(size - 1, 1);
640 
641     if (name.substr(0, 1) == " " ||
642         lastChar == " " ||
643         lastChar == "." ||
644         name.find("  ") != std::string::npos)
645     {
646         return false;
647     }
648     else if (check.empty())
649     {
650         return true;
651     }
652     else if (name.find_first_of(check) != std::string::npos)
653     {
654         return false;
655     }
656     else
657     {
658         return true;
659     }
660 }
661 
662 PlayerRelationsManager playerRelations;
663