summaryrefslogtreecommitdiff
path: root/src/being/playerrelations.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/being/playerrelations.cpp')
-rw-r--r--src/being/playerrelations.cpp630
1 files changed, 630 insertions, 0 deletions
diff --git a/src/being/playerrelations.cpp b/src/being/playerrelations.cpp
new file mode 100644
index 000000000..cdd9ca4bc
--- /dev/null
+++ b/src/being/playerrelations.cpp
@@ -0,0 +1,630 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/playerrelations.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+
+#include "being/localplayer.h"
+
+#include "render/graphics.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include <algorithm>
+
+#include "debug.h"
+
+static const char *const PLAYER_IGNORE_STRATEGY_NOP = "nop";
+static const char *const PLAYER_IGNORE_STRATEGY_EMOTE0 = "emote0";
+static const char *const DEFAULT_IGNORE_STRATEGY
+ = PLAYER_IGNORE_STRATEGY_EMOTE0;
+
+static const char *const NAME = "name";
+static const char *const RELATION = "relation";
+
+static const unsigned int IGNORE_EMOTE_TIME = 100;
+
+typedef std::map<std::string, PlayerRelation *> PlayerRelations;
+typedef PlayerRelations::const_iterator PlayerRelationsCIter;
+typedef std::list<PlayerRelationsListener *> PlayerRelationListeners;
+typedef PlayerRelationListeners::const_iterator PlayerRelationListenersCIter;
+
+class SortPlayersFunctor final
+{
+ public:
+ bool operator() (const std::string &str1,
+ const std::string &str2) const
+ {
+ std::string s1 = str1;
+ std::string s2 = str2;
+ toLower(s1);
+ toLower(s2);
+ if (s1 == s2)
+ return str1 < str2;
+ return s1 < s2;
+ }
+} playersRelSorter;
+
+// (De)serialisation class
+class PlayerConfSerialiser final :
+ public ConfigurationListManager<std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+{
+public:
+ virtual ConfigurationObject *writeConfigItem(
+ const std::pair<std::string, PlayerRelation *> &value,
+ ConfigurationObject *const cobj) const override
+ {
+ if (!cobj || !value.second)
+ return nullptr;
+ cobj->setValue(NAME, value.first);
+ cobj->setValue(RELATION, toString(
+ static_cast<int>(value.second->mRelation)));
+
+ return cobj;
+ }
+
+ virtual std::map<std::string, PlayerRelation *> *
+ readConfigItem(const ConfigurationObject *const cobj,
+ std::map<std::string, PlayerRelation *>
+ *const container) const override
+ {
+ if (!cobj)
+ return container;
+ const std::string name = cobj->getValue(NAME, "");
+ if (name.empty())
+ return container;
+
+ if (!(*container)[name])
+ {
+ const int v = cobj->getValueInt(RELATION,
+ static_cast<int>(PlayerRelation::NEUTRAL));
+
+ (*container)[name] = new PlayerRelation(
+ static_cast<PlayerRelation::Relation>(v));
+ }
+ // otherwise ignore the duplicate entry
+
+ return container;
+ }
+};
+
+static PlayerConfSerialiser player_conf_serialiser; // stateless singleton
+
+const unsigned int PlayerRelation::RELATION_PERMISSIONS[RELATIONS_NR] =
+{
+ /* NEUTRAL */ 0, // we always fall back to the defaults anyway
+ /* FRIEND */ EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE,
+ /* DISREGARDED*/ EMOTE | SPEECH_FLOAT,
+ /* IGNORED */ 0,
+ /* ERASED */ INVISIBLE,
+ /* BLACKLISTED */ SPEECH_LOG | WHISPER,
+ /* ENEMY2 */ EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE
+};
+
+PlayerRelation::PlayerRelation(const Relation relation) :
+ mRelation(relation)
+{
+}
+
+PlayerRelationsManager::PlayerRelationsManager() :
+ mPersistIgnores(false),
+ mDefaultPermissions(PlayerRelation::DEFAULT),
+ mIgnoreStrategy(nullptr),
+ mRelations(),
+ mListeners(),
+ mIgnoreStrategies()
+{
+}
+
+PlayerRelationsManager::~PlayerRelationsManager()
+{
+ delete_all(mIgnoreStrategies);
+
+ FOR_EACH (PlayerRelationsCIter, it, mRelations)
+ delete it->second;
+ mRelations.clear();
+}
+
+void PlayerRelationsManager::clear()
+{
+ StringVect *const names = getPlayers();
+ FOR_EACHP (StringVectCIter, it, names)
+ removePlayer(*it);
+ delete names;
+}
+
+static const char *const PERSIST_IGNORE_LIST = "persistent-player-list";
+static const char *const PLAYER_IGNORE_STRATEGY = "player-ignore-strategy";
+static const char *const DEFAULT_PERMISSIONS = "default-player-permissions";
+
+int PlayerRelationsManager::getPlayerIgnoreStrategyIndex(
+ const std::string &name)
+{
+ const std::vector<PlayerIgnoreStrategy *> *const strategies
+ = getPlayerIgnoreStrategies();
+
+ if (!strategies)
+ return -1;
+
+ const size_t sz = strategies->size();
+ for (size_t i = 0; i < sz; i++)
+ {
+ if ((*strategies)[i]->mShortName == name)
+ return i;
+ }
+
+ return -1;
+}
+
+void PlayerRelationsManager::load(const bool oldConfig)
+{
+ Configuration *cfg;
+ if (oldConfig)
+ cfg = &config;
+ else
+ cfg = &serverConfig;
+ clear();
+
+ mPersistIgnores = cfg->getValue(PERSIST_IGNORE_LIST, 1);
+ mDefaultPermissions = static_cast<int>(cfg->getValue(DEFAULT_PERMISSIONS,
+ mDefaultPermissions));
+
+ const std::string ignore_strategy_name = cfg->getValue(
+ PLAYER_IGNORE_STRATEGY, DEFAULT_IGNORE_STRATEGY);
+ const int ignore_strategy_index = getPlayerIgnoreStrategyIndex(
+ ignore_strategy_name);
+
+ if (ignore_strategy_index >= 0)
+ {
+ setPlayerIgnoreStrategy((*getPlayerIgnoreStrategies())
+ [ignore_strategy_index]);
+ }
+
+ cfg->getList<std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+ ("player", &(mRelations), &player_conf_serialiser);
+}
+
+
+void PlayerRelationsManager::init()
+{
+ load();
+
+ if (!mPersistIgnores)
+ {
+ clear(); // Yes, we still keep them around in the config file
+ // until the next update.
+ }
+
+ FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
+ (*it)->updateAll();
+}
+
+void PlayerRelationsManager::store() const
+{
+ serverConfig.setList<std::map<std::string,
+ PlayerRelation *>::const_iterator,
+ std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+ ("player", mRelations.begin(), mRelations.end(),
+ &player_conf_serialiser);
+
+ serverConfig.setValue(DEFAULT_PERMISSIONS, mDefaultPermissions);
+ serverConfig.setValue(PERSIST_IGNORE_LIST, mPersistIgnores);
+ serverConfig.setValue(PLAYER_IGNORE_STRATEGY,
+ mIgnoreStrategy ? mIgnoreStrategy->mShortName
+ : DEFAULT_IGNORE_STRATEGY);
+
+ serverConfig.write();
+}
+
+void PlayerRelationsManager::signalUpdate(const std::string &name)
+{
+ FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
+ (*it)->updatedPlayer(name);
+
+ if (actorSpriteManager)
+ {
+ Being *const being = actorSpriteManager->findBeingByName(
+ name, Being::PLAYER);
+
+ if (being && being->getType() == Being::PLAYER)
+ being->updateColors();
+ }
+}
+
+unsigned int PlayerRelationsManager::checkPermissionSilently(
+ const std::string &player_name, const unsigned int flags) const
+{
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(player_name);
+ if (it == mRelations.end())
+ {
+ return mDefaultPermissions & flags;
+ }
+ else
+ {
+ const PlayerRelation *const r = (*it).second;
+ unsigned int permissions =
+ PlayerRelation::RELATION_PERMISSIONS[r->mRelation];
+
+ switch (r->mRelation)
+ {
+ case PlayerRelation::NEUTRAL:
+ permissions = mDefaultPermissions;
+ break;
+
+ case PlayerRelation::FRIEND:
+ permissions |= mDefaultPermissions; // widen
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ case PlayerRelation::IGNORED:
+ case PlayerRelation::ERASED:
+ case PlayerRelation::BLACKLISTED:
+ case PlayerRelation::ENEMY2:
+ default:
+ permissions &= mDefaultPermissions; // narrow
+ }
+
+ return permissions & flags;
+ }
+}
+
+bool PlayerRelationsManager::hasPermission(const Being *const being,
+ const unsigned int flags) const
+{
+ if (!being)
+ return false;
+
+ if (being->getType() == ActorSprite::PLAYER)
+ return hasPermission(being->getName(), flags) == flags;
+ return true;
+}
+
+bool PlayerRelationsManager::hasPermission(const std::string &name,
+ const unsigned int flags) const
+{
+ if (!actorSpriteManager)
+ return false;
+
+ const unsigned int rejections = flags
+ & ~checkPermissionSilently(name, flags);
+ const bool permitted = (rejections == 0);
+
+ if (!permitted)
+ {
+ // execute `ignore' strategy, if possible
+ if (mIgnoreStrategy)
+ {
+ Being *const b = actorSpriteManager->findBeingByName(
+ name, ActorSprite::PLAYER);
+
+ if (b && b->getType() == ActorSprite::PLAYER)
+ mIgnoreStrategy->ignore(b, rejections);
+ }
+ }
+
+ return permitted;
+}
+
+void PlayerRelationsManager::setRelation(const std::string &player_name,
+ const PlayerRelation::Relation
+ relation)
+{
+ if (!player_node || (relation != PlayerRelation::NEUTRAL
+ && player_node->getName() == player_name))
+ {
+ return;
+ }
+
+ PlayerRelation *const r = mRelations[player_name];
+ if (!r)
+ mRelations[player_name] = new PlayerRelation(relation);
+ else
+ r->mRelation = relation;
+
+ signalUpdate(player_name);
+}
+
+StringVect *PlayerRelationsManager::getPlayers() const
+{
+ StringVect *const retval = new StringVect();
+
+ FOR_EACH (PlayerRelationsCIter, it, mRelations)
+ {
+ if (it->second)
+ retval->push_back(it->first);
+ }
+
+ std::sort(retval->begin(), retval->end(), playersRelSorter);
+
+ return retval;
+}
+
+StringVect *PlayerRelationsManager::getPlayersByRelation(
+ const PlayerRelation::Relation rel) const
+{
+ StringVect *const retval = new StringVect();
+
+ FOR_EACH (PlayerRelationsCIter, it, mRelations)
+ {
+ if (it->second && it->second->mRelation == rel)
+ retval->push_back(it->first);
+ }
+
+ std::sort(retval->begin(), retval->end(), playersRelSorter);
+
+ return retval;
+}
+
+void PlayerRelationsManager::removePlayer(const std::string &name)
+{
+ delete mRelations[name];
+ mRelations.erase(name);
+ signalUpdate(name);
+}
+
+
+PlayerRelation::Relation PlayerRelationsManager::getRelation(
+ const std::string &name) const
+{
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(name);
+ if (it != mRelations.end())
+ return (*it).second->mRelation;
+
+ return PlayerRelation::NEUTRAL;
+}
+
+////////////////////////////////////////
+// defaults
+
+unsigned int PlayerRelationsManager::getDefault() const
+{
+ return mDefaultPermissions;
+}
+
+void PlayerRelationsManager::setDefault(const unsigned int permissions)
+{
+ mDefaultPermissions = permissions;
+
+ store();
+ signalUpdate("");
+}
+
+void PlayerRelationsManager::ignoreTrade(const std::string &name)
+{
+ if (name.empty())
+ return;
+
+ const PlayerRelation::Relation relation = getRelation(name);
+
+ if (relation == PlayerRelation::IGNORED
+ || relation == PlayerRelation::DISREGARDED
+ || relation == PlayerRelation::BLACKLISTED
+ || relation == PlayerRelation::ERASED)
+ {
+ return;
+ }
+ else
+ {
+ player_relations.setRelation(name, PlayerRelation::BLACKLISTED);
+ }
+}
+
+bool PlayerRelationsManager::checkBadRelation(const std::string &name) const
+{
+ if (name.empty())
+ return true;
+
+ const PlayerRelation::Relation relation = getRelation(name);
+
+ if (relation == PlayerRelation::IGNORED
+ || relation == PlayerRelation::DISREGARDED
+ || relation == PlayerRelation::BLACKLISTED
+ || relation == PlayerRelation::ERASED
+ || relation == PlayerRelation::ENEMY2)
+ {
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////
+// ignore strategies
+
+
+class PIS_nothing final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_nothing() :
+ PlayerIgnoreStrategy()
+ {
+ // TRANSLATORS: ignore/unignore action
+ mDescription = _("Completely ignore");
+ mShortName = PLAYER_IGNORE_STRATEGY_NOP;
+ }
+
+ virtual void ignore(Being *const being A_UNUSED,
+ const unsigned int flags A_UNUSED) const override
+ {
+ }
+};
+
+class PIS_dotdotdot final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_dotdotdot() :
+ PlayerIgnoreStrategy()
+ {
+ // TRANSLATORS: ignore/unignore action
+ mDescription = _("Print '...'");
+ mShortName = "dotdotdot";
+ }
+
+ virtual void ignore(Being *const being,
+ const unsigned int flags A_UNUSED) const override
+ {
+ if (!being)
+ return;
+
+ logger->log("ignoring: " + being->getName());
+ being->setSpeech("...");
+ }
+};
+
+
+class PIS_blinkname final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_blinkname() :
+ PlayerIgnoreStrategy()
+ {
+ // TRANSLATORS: ignore/unignore action
+ mDescription = _("Blink name");
+ mShortName = "blinkname";
+ }
+
+ virtual void ignore(Being *const being,
+ const unsigned int flags A_UNUSED) const override
+ {
+ if (!being)
+ return;
+
+ logger->log("ignoring: " + being->getName());
+ being->flashName(200);
+ }
+};
+
+class PIS_emote final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_emote(const uint8_t emote_nr, const std::string &description,
+ const std::string &shortname) :
+ PlayerIgnoreStrategy(),
+ mEmotion(emote_nr)
+ {
+ mDescription = description;
+ mShortName = shortname;
+ }
+
+ virtual void ignore(Being *const being,
+ const unsigned int flags A_UNUSED) const override
+ {
+ if (!being)
+ return;
+
+ being->setEmote(mEmotion, IGNORE_EMOTE_TIME);
+ }
+ uint8_t mEmotion;
+};
+
+std::vector<PlayerIgnoreStrategy *> *
+PlayerRelationsManager::getPlayerIgnoreStrategies()
+{
+ if (mIgnoreStrategies.empty())
+ {
+ // not initialised yet?
+ mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE,
+ // TRANSLATORS: ignore strategi
+ _("Floating '...' bubble"),
+ PLAYER_IGNORE_STRATEGY_EMOTE0));
+ mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE + 1,
+ // TRANSLATORS: ignore strategi
+ _("Floating bubble"),
+ "emote1"));
+ mIgnoreStrategies.push_back(new PIS_nothing);
+ mIgnoreStrategies.push_back(new PIS_dotdotdot);
+ mIgnoreStrategies.push_back(new PIS_blinkname);
+ }
+ return &mIgnoreStrategies;
+}
+
+bool PlayerRelationsManager::isGoodName(const std::string &name) const
+{
+ const size_t size = name.size();
+
+ if (size < 3)
+ return true;
+
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(name);
+ if (it != mRelations.end())
+ return true;
+
+ return checkName(name);
+}
+
+bool PlayerRelationsManager::isGoodName(Being *const being) const
+{
+ if (!being)
+ return false;
+ if (being->getGoodStatus() != -1)
+ return (being->getGoodStatus() == 1);
+
+ const std::string name = being->getName();
+ const size_t size = name.size();
+
+ if (size < 3)
+ return true;
+
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(name);
+ if (it != mRelations.end())
+ return true;
+
+ const bool status = checkName(name);
+ being->setGoodStatus(status ? 1 : 0);
+ return status;
+}
+
+bool PlayerRelationsManager::checkName(const std::string &name) const
+{
+ const size_t size = name.size();
+ const std::string check = config.getStringValue("unsecureChars");
+ const std::string lastChar = name.substr(size - 1, 1);
+
+ if (name.substr(0, 1) == " " || lastChar == " " || lastChar == "."
+ || name.find(" ") != std::string::npos)
+ {
+ return false;
+ }
+ else if (check.empty())
+ {
+ return true;
+ }
+ else if (name.find_first_of(check) != std::string::npos)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+PlayerRelationsManager player_relations;