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