/* * The ManaPlus Client * Copyright (C) 2008-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2019 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 . */ #include "being/playerrelations.h" #include "actormanager.h" #include "configuration.h" #include "logger.h" #include "being/localplayer.h" #include "being/playerignorestrategy.h" #include "being/playerrelation.h" #include "utils/dtor.h" #include "utils/foreach.h" #include "utils/gettext.h" #include "listeners/playerrelationslistener.h" #include "debug.h" static const unsigned int FIRST_IGNORE_EMOTE = 14; typedef std::map PlayerRelations; typedef PlayerRelations::const_iterator PlayerRelationsCIter; typedef std::list PlayerRelationListeners; typedef PlayerRelationListeners::const_iterator PlayerRelationListenersCIter; 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; namespace { class SortPlayersFunctor final { public: SortPlayersFunctor() { } A_DEFAULT_COPY(SortPlayersFunctor) 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::map *> { public: PlayerConfSerialiser() { } A_DELETE_COPY(PlayerConfSerialiser) ConfigurationObject *writeConfigItem( const std::pair &value, ConfigurationObject *const cobj) const override final { if (cobj == nullptr || value.second == nullptr) { return nullptr; } cobj->setValue(NAME, value.first); cobj->setValue(RELATION, toString( CAST_S32(value.second->mRelation))); return cobj; } std::map * readConfigItem(const ConfigurationObject *const cobj, std::map *const container) const override final { if (cobj == nullptr || container == nullptr) { return container; } const std::string name = cobj->getValue(NAME, ""); if (name.empty()) return container; if ((*container)[name] == nullptr) { const int v = cobj->getValueInt(RELATION, CAST_S32(Relation::NEUTRAL)); (*container)[name] = new PlayerRelation( static_cast(v)); } // otherwise ignore the duplicate entry return container; } }; } // namespace 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 }; 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 *const strategies = getPlayerIgnoreStrategies(); if (strategies == nullptr) return -1; const size_t sz = strategies->size(); for (size_t i = 0; i < sz; i++) { if ((*strategies)[i]->mShortName == name) return CAST_S32(i); } return -1; } void PlayerRelationsManager::load() { Configuration *const cfg = &serverConfig; clear(); mPersistIgnores = (cfg->getValue(PERSIST_IGNORE_LIST, 1) != 0); mDefaultPermissions = CAST_S32(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::map *> ("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::const_iterator, std::pair, std::map *> ("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 != nullptr ? mIgnoreStrategy->mShortName : DEFAULT_IGNORE_STRATEGY); serverConfig.write(); } void PlayerRelationsManager::signalUpdate(const std::string &name) { FOR_EACH (PlayerRelationListenersCIter, it, mListeners) (*it)->updatedPlayer(name); if (actorManager != nullptr) { Being *const being = actorManager->findBeingByName( name, ActorType::Player); if (being != nullptr && being->getType() == ActorType::Player) { being->updateColors(); } } } unsigned int PlayerRelationsManager::checkPermissionSilently( const std::string &player_name, const unsigned int flags) const { const std::map::const_iterator it = mRelations.find(player_name); if (it == mRelations.end()) { return mDefaultPermissions & flags; } const PlayerRelation *const r = (*it).second; unsigned int permissions = PlayerRelation::RELATION_PERMISSIONS[ CAST_S32(r->mRelation)]; switch (r->mRelation) { case Relation::NEUTRAL: permissions = mDefaultPermissions; break; case Relation::FRIEND: permissions |= mDefaultPermissions; // widen break; case Relation::DISREGARDED: case Relation::IGNORED: case Relation::ERASED: case Relation::BLACKLISTED: case Relation::ENEMY2: default: permissions &= mDefaultPermissions; // narrow break; } return permissions & flags; } bool PlayerRelationsManager::hasPermission(const Being *const being, const unsigned int flags) const { if (being == nullptr) return false; if (being->getType() == ActorType::Player) { return static_cast(hasPermission( being->getName(), flags)) == flags; } return true; } bool PlayerRelationsManager::hasPermission(const std::string &name, const unsigned int flags) const { if (actorManager == nullptr) return false; const unsigned int rejections = flags & ~checkPermissionSilently(name, flags); const bool permitted = (rejections == 0); if (!permitted) { // execute `ignore' strategy, if possible if (mIgnoreStrategy != nullptr) { Being *const b = actorManager->findBeingByName( name, ActorType::Player); if ((b != nullptr) && b->getType() == ActorType::Player) mIgnoreStrategy->ignore(b, rejections); } } return permitted; } void PlayerRelationsManager::setRelation(const std::string &player_name, const RelationT relation) { if (localPlayer == nullptr || (relation != Relation::NEUTRAL && localPlayer->getName() == player_name)) { return; } PlayerRelation *const r = mRelations[player_name]; if (r == nullptr) mRelations[player_name] = new PlayerRelation(relation); else r->mRelation = relation; signalUpdate(player_name); store(); } StringVect *PlayerRelationsManager::getPlayers() const { StringVect *const retval = new StringVect; FOR_EACH (PlayerRelationsCIter, it, mRelations) { if (it->second != nullptr) retval->push_back(it->first); } std::sort(retval->begin(), retval->end(), playersRelSorter); return retval; } StringVect *PlayerRelationsManager::getPlayersByRelation( const RelationT rel) const { StringVect *const retval = new StringVect; FOR_EACH (PlayerRelationsCIter, it, mRelations) { if ((it->second != nullptr) && 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); } RelationT PlayerRelationsManager::getRelation( const std::string &name) const { const std::map::const_iterator it = mRelations.find(name); if (it != mRelations.end()) return (*it).second->mRelation; return Relation::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) const { if (name.empty()) return; const RelationT relation = getRelation(name); if (relation == Relation::IGNORED || relation == Relation::DISREGARDED || relation == Relation::BLACKLISTED || relation == Relation::ERASED) { return; } playerRelations.setRelation(name, Relation::BLACKLISTED); } bool PlayerRelationsManager::checkBadRelation(const std::string &name) const { if (name.empty()) return true; const RelationT relation = getRelation(name); if (relation == Relation::IGNORED || relation == Relation::DISREGARDED || relation == Relation::BLACKLISTED || relation == Relation::ERASED || relation == Relation::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; } A_DELETE_COPY(PIS_nothing) void ignore(Being *const being A_UNUSED, const unsigned int flags A_UNUSED) const override final { } }; class PIS_dotdotdot final : public PlayerIgnoreStrategy { public: PIS_dotdotdot() : PlayerIgnoreStrategy() { // TRANSLATORS: ignore/unignore action mDescription = _("Print '...'"); mShortName = "dotdotdot"; } A_DELETE_COPY(PIS_dotdotdot) void ignore(Being *const being, const unsigned int flags A_UNUSED) const override final { if (being == nullptr) 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"; } A_DELETE_COPY(PIS_blinkname) void ignore(Being *const being, const unsigned int flags A_UNUSED) const override final { if (being == nullptr) 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; } A_DELETE_COPY(PIS_emote) void ignore(Being *const being, const unsigned int flags A_UNUSED) const override final { if (being == nullptr) return; being->setEmote(mEmotion, IGNORE_EMOTE_TIME); } uint8_t mEmotion; }; STD_VECTOR * 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::const_iterator it = mRelations.find(name); if (it != mRelations.end()) return true; return checkName(name); } bool PlayerRelationsManager::isGoodName(Being *const being) const { if (being == nullptr) 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::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 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 playerRelations;