• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

TFS 1.X+ [DOUBT] Level for private message

Amora

New Member
Joined
Aug 8, 2018
Messages
2
Reaction score
0
OTX 3.0

I would like to set the minimum level for the player to be able to send Private Message.
I've already managed to set the minimum level for Chats: Help, World Chat and Adversiting. They are in Data> Channels. Then I opened the files and edited the codes.
However, I can not find the Private Message file in any way. And I would like only lvl 30+ players to be able to send a private message to other players.
 
Hi Amora,

If I recall correctly, OTX does not support this “flexibility” by default. Channels.xml normally allows you to edit restrictions for other channels (e.g. Trade). The solution would be to alter the source code to check for a level and return a default cancel message or simply return false and close the private message “channel” if they do not have the required level.

Edit: And if you do edit the source, I highly recommend offering feedback to the player in-game when they attempt to send a message. Otherwise, they may confuse this for a bug.
 
Last edited:
Hi Amora,

If I recall correctly, OTX does not support this “flexibility” by default. Channels.xml normally allows you to edit restrictions for other channels (e.g. Trade). The solution would be to alter the source code to check for a level and return a default cancel message or simply return false and close the private message “channel” if they do not have the required level.

Edit: And if you do edit the source, I highly recommend offering feedback to the player in-game when they attempt to send a message. Otherwise, they may confuse this for a bug.

Got it.
And would you be able to tell me how to edit source code for this purpose?

This is my chat.cpp
Code:
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2016  Mark Samman <[email protected]>
*
* 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
* (at your option) 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "otpch.h"

#include "chat.h"
#include "game.h"
#include "pugicast.h"
#include "scheduler.h"

extern Chat* g_chat;
extern Game g_game;

bool PrivateChatChannel::isInvited(uint32_t guid) const
{
    if (guid == getOwner()) {
        return true;
    }
    return invites.find(guid) != invites.end();
}

bool PrivateChatChannel::removeInvite(uint32_t guid)
{
    return invites.erase(guid) != 0;
}

void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer)
{
    auto result = invites.emplace(invitePlayer.getGUID(), &invitePlayer);
    if (!result.second) {
        return;
    }

    std::ostringstream ss;
    ss << player.getName() << " invites you to " << (player.getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " private chat channel.";
    invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());

    ss.str(std::string());
    ss << invitePlayer.getName() << " has been invited.";
    player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());

    for (const auto& it : users) {
        it.second->sendChannelEvent(id, invitePlayer.getName(), CHANNELEVENT_INVITE);
    }
}

void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer)
{
    if (!removeInvite(excludePlayer.getGUID())) {
        return;
    }

    removeUser(excludePlayer);

    std::ostringstream ss;
    ss << excludePlayer.getName() << " has been excluded.";
    player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str());

    excludePlayer.sendClosePrivate(id);

    for (const auto& it : users) {
        it.second->sendChannelEvent(id, excludePlayer.getName(), CHANNELEVENT_EXCLUDE);
    }
}

void PrivateChatChannel::closeChannel() const
{
    for (const auto& it : users) {
        it.second->sendClosePrivate(id);
    }
}

bool ChatChannel::addUser(Player& player)
{
    if (users.find(player.getID()) != users.end()) {
        return false;
    }

    if (!executeOnJoinEvent(player)) {
        return false;
    }

    // TODO: Move to script when guild channels can be scripted
    if (id == CHANNEL_GUILD) {
        Guild* guild = player.getGuild();
        if (guild && !guild->getMotd().empty()) {
            g_scheduler.addEvent(createSchedulerTask(150, std::bind(&Game::sendGuildMotd, &g_game, player.getID())));
        }
    }

    if (!publicChannel) {
        for (const auto& it : users) {
            it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_JOIN);
        }
    }

    users[player.getID()] = &player;
    return true;
}

bool ChatChannel::removeUser(const Player& player)
{
    auto iter = users.find(player.getID());
    if (iter == users.end()) {
        return false;
    }

    users.erase(iter);

    if (!publicChannel) {
        for (const auto& it : users) {
            it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_LEAVE);
        }
    }

    executeOnLeaveEvent(player);
    return true;
}

void ChatChannel::sendToAll(const std::string& message, SpeakClasses type) const
{
    for (const auto& it : users) {
        it.second->sendChannelMessage("", message, type, id);
    }
}

bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text)
{
    if (users.find(fromPlayer.getID()) == users.end()) {
        return false;
    }

    for (const auto& it : users) {
        it.second->sendToChannel(&fromPlayer, type, text, id);
    }
    return true;
}

bool ChatChannel::executeCanJoinEvent(const Player& player)
{
    if (canJoinEvent == -1) {
        return true;
    }

    //canJoin(player)
    LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
    if (!scriptInterface->reserveScriptEnv()) {
        std::cout << "[Error - CanJoinChannelEvent::execute] Call stack overflow" << std::endl;
        return false;
    }

    ScriptEnvironment* env = scriptInterface->getScriptEnv();
    env->setScriptId(canJoinEvent, scriptInterface);

    lua_State* L = scriptInterface->getLuaState();

    scriptInterface->pushFunction(canJoinEvent);
    LuaScriptInterface::pushUserdata(L, &player);
    LuaScriptInterface::setMetatable(L, -1, "Player");

    return scriptInterface->callFunction(1);
}

bool ChatChannel::executeOnJoinEvent(const Player& player)
{
    if (onJoinEvent == -1) {
        return true;
    }

    //onJoin(player)
    LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
    if (!scriptInterface->reserveScriptEnv()) {
        std::cout << "[Error - OnJoinChannelEvent::execute] Call stack overflow" << std::endl;
        return false;
    }

    ScriptEnvironment* env = scriptInterface->getScriptEnv();
    env->setScriptId(onJoinEvent, scriptInterface);

    lua_State* L = scriptInterface->getLuaState();

    scriptInterface->pushFunction(onJoinEvent);
    LuaScriptInterface::pushUserdata(L, &player);
    LuaScriptInterface::setMetatable(L, -1, "Player");

    return scriptInterface->callFunction(1);
}

bool ChatChannel::executeOnLeaveEvent(const Player& player)
{
    if (onLeaveEvent == -1) {
        return true;
    }

    //onLeave(player)
    LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
    if (!scriptInterface->reserveScriptEnv()) {
        std::cout << "[Error - OnLeaveChannelEvent::execute] Call stack overflow" << std::endl;
        return false;
    }

    ScriptEnvironment* env = scriptInterface->getScriptEnv();
    env->setScriptId(onLeaveEvent, scriptInterface);

    lua_State* L = scriptInterface->getLuaState();

    scriptInterface->pushFunction(onLeaveEvent);
    LuaScriptInterface::pushUserdata(L, &player);
    LuaScriptInterface::setMetatable(L, -1, "Player");

    return scriptInterface->callFunction(1);
}

bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message)
{
    if (onSpeakEvent == -1) {
        return true;
    }

    //onSpeak(player, type, message)
    LuaScriptInterface* scriptInterface = g_chat->getScriptInterface();
    if (!scriptInterface->reserveScriptEnv()) {
        std::cout << "[Error - OnSpeakChannelEvent::execute] Call stack overflow" << std::endl;
        return false;
    }

    ScriptEnvironment* env = scriptInterface->getScriptEnv();
    env->setScriptId(onSpeakEvent, scriptInterface);

    lua_State* L = scriptInterface->getLuaState();

    scriptInterface->pushFunction(onSpeakEvent);
    LuaScriptInterface::pushUserdata(L, &player);
    LuaScriptInterface::setMetatable(L, -1, "Player");

    lua_pushnumber(L, type);
    LuaScriptInterface::pushString(L, message);

    bool result = false;
    int size0 = lua_gettop(L);
    int ret = scriptInterface->protectedCall(L, 3, 1);
    if (ret != 0) {
        LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L));
    } else if (lua_gettop(L) > 0) {
        if (lua_isboolean(L, -1)) {
            result = LuaScriptInterface::getBoolean(L, -1);
        } else if (lua_isnumber(L, -1)) {
            result = true;
            type = LuaScriptInterface::getNumber<SpeakClasses>(L, -1);
        }
        lua_pop(L, 1);
    }

    if ((lua_gettop(L) + 4) != size0) {
        LuaScriptInterface::reportError(nullptr, "Stack size changed!");
    }
    scriptInterface->resetScriptEnv();
    return result;
}

Chat::Chat():
    scriptInterface("Chat Interface"),
    dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel")
{
    scriptInterface.initState();
}

bool Chat::load()
{
    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file("data/chatchannels/chatchannels.xml");
    if (!result) {
        printXMLError("Error - Chat::load", "data/chatchannels/chatchannels.xml", result);
        return false;
    }

    std::forward_list<uint16_t> removedChannels;
    for (auto& channelEntry : normalChannels) {
        ChatChannel& channel = channelEntry.second;
        channel.onSpeakEvent = -1;
        channel.canJoinEvent = -1;
        channel.onJoinEvent = -1;
        channel.onLeaveEvent = -1;
        removedChannels.push_front(channelEntry.first);
    }

    for (auto channelNode : doc.child("channels").children()) {
        ChatChannel channel(pugi::cast<uint16_t>(channelNode.attribute("id").value()), channelNode.attribute("name").as_string());
        channel.publicChannel = channelNode.attribute("public").as_bool();

        pugi::xml_attribute scriptAttribute = channelNode.attribute("script");
        if (scriptAttribute) {
            if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) {
                channel.onSpeakEvent = scriptInterface.getEvent("onSpeak");
                channel.canJoinEvent = scriptInterface.getEvent("canJoin");
                channel.onJoinEvent = scriptInterface.getEvent("onJoin");
                channel.onLeaveEvent = scriptInterface.getEvent("onLeave");
            } else {
                std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl;
            }
        }

        removedChannels.remove(channel.id);
        normalChannels[channel.id] = channel;
    }

    for (uint16_t channelId : removedChannels) {
        normalChannels.erase(channelId);
    }
    return true;
}

ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId)
{
    if (getChannel(player, channelId)) {
        return nullptr;
    }

    switch (channelId) {
        case CHANNEL_GUILD: {
            Guild* guild = player.getGuild();
            if (guild) {
                auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName())));
                return &ret.first->second;
            }
            break;
        }

        case CHANNEL_PARTY: {
            Party* party = player.getParty();
            if (party) {
                auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party")));
                return &ret.first->second;
            }
            break;
        }

        case CHANNEL_PRIVATE: {
            //only 1 private channel for each premium player
            if (!player.isPremium() || getPrivateChannel(player)) {
                return nullptr;
            }

            //find a free private channel slot
            for (uint16_t i = 100; i < 10000; ++i) {
                auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel")));
                if (ret.second) { //second is a bool that indicates that a new channel has been placed in the map
                    auto& newChannel = (*ret.first).second;
                    newChannel.setOwner(player.getGUID());
                    return &newChannel;
                }
            }
            break;
        }

        default:
            break;
    }
    return nullptr;
}

bool Chat::deleteChannel(const Player& player, uint16_t channelId)
{
    switch (channelId) {
        case CHANNEL_GUILD: {
            Guild* guild = player.getGuild();
            if (!guild) {
                return false;
            }

            auto it = guildChannels.find(guild->getId());
            if (it == guildChannels.end()) {
                return false;
            }

            guildChannels.erase(it);
            break;
        }

        case CHANNEL_PARTY: {
            Party* party = player.getParty();
            if (!party) {
                return false;
            }

            auto it = partyChannels.find(party);
            if (it == partyChannels.end()) {
                return false;
            }

            partyChannels.erase(it);
            break;
        }

        default: {
            auto it = privateChannels.find(channelId);
            if (it == privateChannels.end()) {
                return false;
            }

            it->second.closeChannel();

            privateChannels.erase(it);
            break;
        }
    }
    return true;
}

ChatChannel* Chat::addUserToChannel(Player& player, uint16_t channelId)
{
    ChatChannel* channel = getChannel(player, channelId);
    if (channel && channel->addUser(player)) {
        return channel;
    }
    return nullptr;
}

bool Chat::removeUserFromChannel(const Player& player, uint16_t channelId)
{
    ChatChannel* channel = getChannel(player, channelId);
    if (!channel || !channel->removeUser(player)) {
        return false;
    }

    if (channel->getOwner() == player.getGUID()) {
        deleteChannel(player, channelId);
    }
    return true;
}

void Chat::removeUserFromAllChannels(const Player& player)
{
    for (auto& it : normalChannels) {
        it.second.removeUser(player);
    }

    for (auto& it : partyChannels) {
        it.second.removeUser(player);
    }

    for (auto& it : guildChannels) {
        it.second.removeUser(player);
    }

    auto it = privateChannels.begin();
    while (it != privateChannels.end()) {
        PrivateChatChannel* channel = &it->second;
        channel->removeInvite(player.getGUID());
        channel->removeUser(player);
        if (channel->getOwner() == player.getGUID()) {
            channel->closeChannel();
            it = privateChannels.erase(it);
        } else {
            ++it;
        }
    }
}

bool Chat::talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId)
{
    ChatChannel* channel = getChannel(player, channelId);
    if (!channel) {
        return false;
    }

    if (channelId == CHANNEL_GUILD) {
        const GuildRank* rank = player.getGuildRank();
        if (rank && rank->level > 1) {
            type = TALKTYPE_CHANNEL_O;
        } else if (type != TALKTYPE_CHANNEL_Y) {
            type = TALKTYPE_CHANNEL_Y;
        }
    } else if (type != TALKTYPE_CHANNEL_Y && (channelId == CHANNEL_PRIVATE || channelId == CHANNEL_PARTY)) {
        type = TALKTYPE_CHANNEL_Y;
    }

    if (!channel->executeOnSpeakEvent(player, type, text)) {
        return false;
    }

    return channel->talk(player, type, text);
}

ChannelList Chat::getChannelList(const Player& player)
{
    ChannelList list;
    if (player.getGuild()) {
        ChatChannel* channel = getChannel(player, CHANNEL_GUILD);
        if (channel) {
            list.push_back(channel);
        } else {
            channel = createChannel(player, CHANNEL_GUILD);
            if (channel) {
                list.push_back(channel);
            }
        }
    }

    if (player.getParty()) {
        ChatChannel* channel = getChannel(player, CHANNEL_PARTY);
        if (channel) {
            list.push_back(channel);
        } else {
            channel = createChannel(player, CHANNEL_PARTY);
            if (channel) {
                list.push_back(channel);
            }
        }
    }

    for (const auto& it : normalChannels) {
        ChatChannel* channel = getChannel(player, it.first);
        if (channel) {
            list.push_back(channel);
        }
    }

    bool hasPrivate = false;
    for (auto& it : privateChannels) {
        if (PrivateChatChannel* channel = &it.second) {
            uint32_t guid = player.getGUID();
            if (channel->isInvited(guid)) {
                list.push_back(channel);
            }

            if (channel->getOwner() == guid) {
                hasPrivate = true;
            }
        }
    }

    if (!hasPrivate && player.isPremium()) {
        list.push_front(&dummyPrivate);
    }
    return list;
}

ChatChannel* Chat::getChannel(const Player& player, uint16_t channelId)
{
    switch (channelId) {
        case CHANNEL_GUILD: {
            Guild* guild = player.getGuild();
            if (guild) {
                auto it = guildChannels.find(guild->getId());
                if (it != guildChannels.end()) {
                    return &it->second;
                }
            }
            break;
        }

        case CHANNEL_PARTY: {
            Party* party = player.getParty();
            if (party) {
                auto it = partyChannels.find(party);
                if (it != partyChannels.end()) {
                    return &it->second;
                }
            }
            break;
        }

        default: {
            auto it = normalChannels.find(channelId);
            if (it != normalChannels.end()) {
                ChatChannel& channel = it->second;
                if (!channel.executeCanJoinEvent(player)) {
                    return nullptr;
                }
                return &channel;
            } else {
                auto it2 = privateChannels.find(channelId);
                if (it2 != privateChannels.end() && it2->second.isInvited(player.getGUID())) {
                    return &it2->second;
                }
            }
            break;
        }
    }
    return nullptr;
}

ChatChannel* Chat::getGuildChannelById(uint32_t guildId)
{
    auto it = guildChannels.find(guildId);
    if (it == guildChannels.end()) {
        return nullptr;
    }
    return &it->second;
}

ChatChannel* Chat::getChannelById(uint16_t channelId)
{
    auto it = normalChannels.find(channelId);
    if (it == normalChannels.end()) {
        return nullptr;
    }
    return &it->second;
}

PrivateChatChannel* Chat::getPrivateChannel(const Player& player)
{
    for (auto& it : privateChannels) {
        if (it.second.getOwner() == player.getGUID()) {
            return &it.second;
        }
    }
    return nullptr;
}

This is my chat.h
Code:
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2016  Mark Samman <[email protected]>
*
* 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
* (at your option) 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628
#define FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628

#include "const.h"
#include "luascript.h"

class Party;
class Player;

typedef std::map<uint32_t, Player*> UsersMap;
typedef std::map<uint32_t, const Player*> InvitedMap;

class ChatChannel
{
    public:
        ChatChannel() = default;
        ChatChannel(uint16_t channelId, std::string channelName):
            name(std::move(channelName)),
            id(channelId) {}

        virtual ~ChatChannel() = default;

        bool addUser(Player& player);
        bool removeUser(const Player& player);

        bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text);
        void sendToAll(const std::string& message, SpeakClasses type) const;

        const std::string& getName() const {
            return name;
        }
        uint16_t getId() const {
            return id;
        }
        const UsersMap& getUsers() const {
            return users;
        }
        virtual const InvitedMap* getInvitedUsers() const {
            return nullptr;
        }

        virtual uint32_t getOwner() const {
            return 0;
        }

        bool isPublicChannel() const { return publicChannel; }

        bool executeOnJoinEvent(const Player& player);
        bool executeCanJoinEvent(const Player& player);
        bool executeOnLeaveEvent(const Player& player);
        bool executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message);

    protected:
        UsersMap users;

        std::string name;

        int32_t canJoinEvent = -1;
        int32_t onJoinEvent = -1;
        int32_t onLeaveEvent = -1;
        int32_t onSpeakEvent = -1;

        uint16_t id;
        bool publicChannel = false;

    friend class Chat;
};

class PrivateChatChannel final : public ChatChannel
{
    public:
        PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {}

        uint32_t getOwner() const final {
            return owner;
        }
        void setOwner(uint32_t owner) {
            this->owner = owner;
        }

        bool isInvited(uint32_t guid) const;

        void invitePlayer(const Player& player, Player& invitePlayer);
        void excludePlayer(const Player& player, Player& excludePlayer);

        bool removeInvite(uint32_t guid);

        void closeChannel() const;

        const InvitedMap* getInvitedUsers() const final {
            return &invites;
        }

    protected:
        InvitedMap invites;
        uint32_t owner = 0;
};

typedef std::list<ChatChannel*> ChannelList;

class Chat
{
    public:
        Chat();

        // non-copyable
        Chat(const Chat&) = delete;
        Chat& operator=(const Chat&) = delete;

        bool load();

        ChatChannel* createChannel(const Player& player, uint16_t channelId);
        bool deleteChannel(const Player& player, uint16_t channelId);

        ChatChannel* addUserToChannel(Player& player, uint16_t channelId);
        bool removeUserFromChannel(const Player& player, uint16_t channelId);
        void removeUserFromAllChannels(const Player& player);

        bool talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId);

        ChannelList getChannelList(const Player& player);

        ChatChannel* getChannel(const Player& player, uint16_t channelId);
        ChatChannel* getChannelById(uint16_t channelId);
        ChatChannel* getGuildChannelById(uint32_t guildId);
        PrivateChatChannel* getPrivateChannel(const Player& player);

        LuaScriptInterface* getScriptInterface() {
            return &scriptInterface;
        }

    private:
        std::map<uint16_t, ChatChannel> normalChannels;
        std::map<uint16_t, PrivateChatChannel> privateChannels;
        std::map<Party*, ChatChannel> partyChannels;
        std::map<uint32_t, ChatChannel> guildChannels;

        LuaScriptInterface scriptInterface;

        PrivateChatChannel dummyPrivate;
};

#endif
 
Back
Top