/*
*
* Automation.cpp: The Ruined Place Chat Bot.
*
*/
#include <iostream>
#include <string>
#include <set>
#include <deque>
#include <sstream>
#include <fstream>
#include <map>
#include <cstdlib>
#include "automation.h"
#include "game.h"
#include "main.h"
#include "utils/stringutils.h"
#include "utils/specialfolder.h"
#include "net/nethandler.h"
static Automation *automationHandler = NULL;
std::set<member> guildMembers;
std::deque<whispermsg> whispers;
std::deque<whispermsg> information;
std::set<std::string> onlineList;
std::set<memberinvite> inviteList;
std::map<std::string, std::string> guildMOTD;
bool automation = true;
bool repeatfix = false;
bool load = true;
bool hideOnLoad = true; // Hide all online/offline notifications on startup - creates a lot of spam!
int message_timer = 0;
Automation::Automation()
{
automationHandler = this;
}
Automation::~Automation()
{
delete automationHandler;
}
Automation *Automation::getAutomationHandler()
{
return automationHandler;
}
/* Commands/chat recieved through whisper. */
void Automation::commandHandler(std::string message, std::string nick)
{
std::string::size_type pos = message.find(' ');
std::string type(message, 0, pos);
std::string args(message, pos == std::string::npos ? message.size() : pos + 1);
/*start of code that deals with invitations - could do with a rewrite*/
std::set<memberinvite>::iterator it3;
bool invited = false;
memberinvite invitedMember;
for (it3=inviteList.begin(); it3 != inviteList.end(); it3++)
{
if (it3->name == nick)
{
invited = true;
invitedMember = *it3;
}
}
if (invited)
{
if (removeColors(type) == "yes")
{
inviteList.erase(invitedMember);
addMember(nick, invitedMember.guildName, 0);
addMessage(nick+ " has joined the Guild.",
invitedMember.guildName, "Info", 1);
return;
}
else if (removeColors(type) == "no")
{
inviteList.erase(invitedMember);
return;
}
}
/* end */
if (!automation || !inGuild(nick))
{
whisper(nick, colorText("You are currently not in a guild."
" For more information or to discuss the possibility"
" of adding you own guild please contact Jero."), 1);
return;
}
if (type[0] == '!')
{
debugMsg("("+findByName(nick).guildName+") Command: "+
nick+": "+type +" "+args);
//note incorrect commands will be logged twice.
}
if (accessLevel(args) != -1)
{
member tmp = findByName(nick);
if (tmp.status == 0)
{
guildMembers.erase(tmp);
tmp.status = 1;
addMessage(tmp.name+ " is now Online.", tmp.guildName, "Info", 1);
guildMembers.insert(tmp);
whisper(tmp.name, colorText("Welcome to the "+tmp.guildName+"! ("+
toString(countOnline(tmp.guildName)) +" Members are Online)"), 1);
}
}
if (type == "!invite" && accessLevel(nick) >= 5) /* This command is only available to admin*/
{
if (!args.empty() && accessLevel(args) != -1)
{
if (inGuild(args))
{
whisper(nick, colorText("The user is in another guild."), 1);
}
else
{
if (onlineList.find(args) != onlineList.end())
{
memberinvite tmp= {args, findByName(nick).guildName};
inviteList.insert(tmp);
whisper(args, colorText("You have been invited to the "+
findByName(nick).guildName+ " guild chat. If you "
"would like to accept this invitation"
" please reply \"yes\" and if not then \"no\" ." ), 1);
}
else
{
whisper(nick, colorText("The user is offline or hidden."), 1);
}
}
}
}
else if (type == "!test" && accessLevel(nick) == 20) /* This command is only available to admin*/
{
addMessage("A test will be running shortly.",
findByName(nick).guildName, "Info", 0);
testMsg();
}
else if (type == "!disband" && accessLevel(nick) >= 10) /* This command is only available to guild masters*/
{
std::string guild = findByName(nick).guildName;
debugMsg(guild + " disbanded.");
addMessage("The "+guild +" guild has been disbanded.","global", "Info", 1);
std::set<member>::iterator it2;
for (it2=guildMembers.begin(); it2 != guildMembers.end(); it2++)
{
if (it2->guildName == guild)
guildMembers.erase(it2);
}
saveMembers();
}
else if (type == "!removeguild" && accessLevel(nick) == 20) /* This command is only available to admin*/
{
if (!args.empty())
{
debugMsg(args + "removed.");
std::set<member>::iterator it2;
for (it2=guildMembers.begin(); it2 != guildMembers.end(); it2++)
{
if (it2->guildName == args)
guildMembers.erase(it2);
}
addMessage("The "+args +" guild has been removed.","global", "Info", 1);
saveMembers();
}
}
else if (type == "!addguild" && accessLevel(nick) == 20)
{
if (!args.empty())
{
std::string::size_type pos2 = args.find(',');
std::string name = args.substr(0, pos2);
std::string guildName = args.substr(pos2+1, args.size());
addMember(name, guildName, 10);
addMessage("The "+guildName+" guild has been created.","global", "Info", 1);
}
}
else if (type == "!info")
{
std::stringstream message;
message << "Player name: "+nick+", Access Level: "<< accessLevel(nick)
<< ", Guild:" << findByName(nick).guildName
<< ", No. Of Online Players: " <<
countOnline(findByName(nick).guildName);
whisper(nick, colorText(message.str()), 1);
std::string MOTD = getMOTD(findByName(nick).guildName);
if (MOTD != "")
whisper(nick, colorText("Guild MOTD: "+MOTD), 1);
}
else if (type == "!ping") /* A simple response to check if the bot is active. */
{
whisper(nick, colorText("Pong."), 0);
}
else if (type == "!remove")
{
if (!args.empty())
{
if (hasPermission(nick, args, 0) && inGuild(args))
{
removeMember(args);
whisper(args, "You have been removed from the Guild", 0);
addMessage(args+ " has been removed from the Guild.",
findByName(nick).guildName, "Info", 1);
saveMembers();
}
}
}
else if (type == "!setmotd" && accessLevel(nick) >= 5)
{
std::map<std::string, std::string>::iterator it;
it=guildMOTD.find(findByName(nick).guildName);
if (it != guildMOTD.end())
{
guildMOTD.erase(it);
guildMOTD.insert(std::pair<std::string, std::string>(findByName(nick).guildName, args));
}
else
{
guildMOTD.insert(std::pair<std::string, std::string>(findByName(nick).guildName, args));
}
whisper(nick, colorText("MOTD Set:" + args), 1);
}
else if (type == "!removemotd" && accessLevel(nick) >= 5)
{
std::map<std::string, std::string>::iterator it;
it=guildMOTD.find(findByName(nick).guildName);
if (it != guildMOTD.end())
{
guildMOTD.erase(it);
whisper(nick, colorText("The MOTD has been Removed."), 1);
}
else
{
whisper(nick, colorText("No MOTD set."), 1);
}
}
else if (type == "!setaccess") /* This command is only available to admin*/
{
if (!args.empty())
{
std::string::size_type pos = args.find(' ');
std::string level(args, 0, pos);
std::string name(args, pos == std::string::npos ? args.size() : pos + 1);
int access = toInt(level);
if (hasPermission(nick, name, access) && inGuild(name)
&& !name.empty() && !level.empty())
{
member update = findByName(name);
guildMembers.erase(update);
update.accesslevel = access;
guildMembers.insert(update);
whisper(nick, colorText("Player name:" + name +
", Access Level: "+level), 1);
}
saveMembers();
}
}
else if (type == "!leave")
{
std::string guild = findByName(nick).guildName;
whisper(nick, "You have left the Guild", 0);
removeMember(nick);
addMessage(nick+ " has left the Guild.", guild, "Info", 1);
saveMembers();
}
else if (type == "!help")
{
whisper(nick, colorText("Guild ChatBot. The "
"commands are: !invite <name>, !leave, !listonline, !listmembers, !info. "
"If you would like to hide information messages use !hideinfo, "
"and to make them visible again use !showinfo."), 1);
if (accessLevel(nick) >= 5)
{
whisper(nick, colorText(
"You are also able to add a guild MOTD using !setmotd, this can be removed using !removemotd."), 1);
}
if (accessLevel(nick) >= 10)
{
whisper(nick, colorText(
"You also have access to the guild master commands these are"
" !remove <name>, !disband, !setaccess <access level> <name>"
"(the access level must be less than or equal to your own)."), 1);
}
else if (accessLevel(nick) <= 10 && accessLevel(nick) > 0)
{
whisper(nick, colorText(
"You also have access to the guild moderator commands these are"
" !remove <name>, !setaccess <access level> <name>"
"(the access level must be less than or equal to your own)."), 1);
}
if (accessLevel(nick) == 20)
{
whisper(nick, colorText(
"You also have access to the admin commands these are !test,"
" !addguild <name>,<guild name> (the comma is important)"
" and !removeguild <name>."), 1);
whisper(nick, colorText(
" To send a message to all guilds use !global <message>, "
"!listallonline, !joinguild <guild name> (lets you join another guilds chat)"), 1);
}
}
else if (type == "!hideinfo")
{
whisper(nick, colorText("Information messages hidden."), 1);
member tmp = findByName(nick);
guildMembers.erase(tmp);
tmp.info = 0;
guildMembers.insert(tmp);
}
else if (type == "!online")
{
whisper(nick, colorText("You are online."), 1);
member tmp = findByName(nick);
guildMembers.erase(tmp);
tmp.status = 1;
guildMembers.insert(tmp);
}
else if (type == "!offline")
{
whisper(nick, colorText("You have gone offline."), 1);
member tmp = findByName(nick);
guildMembers.erase(tmp);
tmp.status = 0;
guildMembers.insert(tmp);
}
else if (type == "!joinguild" && accessLevel(nick) == 20)
{
whisper(nick, colorText("You've joined "+args+"."), 1);
member tmp = findByName(nick);
guildMembers.erase(tmp);
tmp.guildName = args;
guildMembers.insert(tmp);
}
else if (type == "!showinfo")
{
whisper(nick, colorText("Information messages vissible."), 1);
member tmp = findByName(nick);
guildMembers.erase(tmp);
tmp.info = 1;
guildMembers.insert(tmp);
}
else if (type == "!global" && accessLevel(nick) == 20) /* This command is only available to admin*/
{
if (!args.empty())
{
addMessage(args, "global", "Info", 0);
}
}
else if (type == "!listonline")
{
std::set<member>::iterator it;
std::string message;
std::stringstream list;
list << "List of online Guild Members: ";
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
{
if (list.str().length() + it->name.length() > 250 && it->status == 1
&& it->guildName == findByName(nick).guildName)
{
message = list.str();
message[message.length()-2] = '.';
whisper(nick,colorText(message), 1);
list.str("");
list << it->name + ", ";
}
else if (it->status == 1 && it->guildName == findByName(nick).guildName)
{
list << it->name + ", ";
}
}
message = list.str();
message[message.length()-2] = '.';
whisper(nick,colorText(message), 1);
}
else if (type == "!getonlineinfo")
{
std::set<member>::iterator it;
std::string message;
std::stringstream list;
list << "OL";
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
{
int status = it->status;
if (it->accesslevel >= 10)
status += 2;
if (list.str().length() + it->name.length()+2 > 400
&& it->guildName == findByName(nick).guildName)
{
list << "#";
message = list.str();
whisper(nick,colorText(message), 1);
list.str("");
list << "oL#";
list << it->name;
list << status;
}
else if (it->guildName == findByName(nick).guildName)
{
list << "#";
list << it->name;
list << status;
}
}
message = list.str();
whisper(nick,colorText(message), 1);
}
else if (type == "!listallonline" && accessLevel(nick) == 20)
{
std::set<member>::iterator it;
std::string message;
std::stringstream list;
list << "List of online Players: ";
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
{
if (list.str().length() + it->name.length() > 250 && it->status == 1)
{
message = list.str();
message[message.length()-2] = '.';
whisper(nick,colorText(message), 1);
list.str("");
list << it->name + ", ";
}
else if (it->status == 1)
{
list << it->name + ", ";
}
}
message = list.str();
message[message.length()-2] = '.';
whisper(nick,colorText(message), 1);
}
else if (type == "!listmembers")
{
std::set<member>::iterator it;
std::string message;
std::stringstream list;
list << "List of Guild Members: ";
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
{
if (list.str().length() + it->name.length() > 250
&& it->guildName == findByName(nick).guildName)
{
message = list.str();
message[message.length()-2] = '.';
whisper(nick,colorText(message), 1);
list.str("");
list << it->name + ", ";
}
else if (it->guildName == findByName(nick).guildName)
{
list << it->name + ", ";
}
}
message = list.str();
message[message.length()-2] = '.';
whisper(nick,colorText(message), 1);
}
else
{
std::size_t found;
found=message.find("*AFK*");
if (found == std::string::npos)
addMessage(removeColors(message), findByName(nick).guildName, nick, 0);
}
}
/* hooks directly into the main game logic. */
void Automation::logic()
{
if (message_timer == 0)
message_timer = cur_time;
if (get_elapsed_time(message_timer) > 10 && hideOnLoad)
{
hideOnLoad = false;
debugMsg("Online/Offline messages unhidden.");
}
if (!whispers.empty())// with SDL_Delay(10) this should send around 100 messages per second.
{
NetHandler::getNetInstance()->privateMessage(whispers.front().name,
whispers.front().message);
whispers.pop_front();
}
else if (!information.empty())
{
NetHandler::getNetInstance()->privateMessage(information.front().name,
information.front().message);
information.pop_front();
}
}
void Automation::addMember(std::string name, std::string guildName, int access)
{
member addme = {name, guildName, 0, access, 1};
guildMembers.insert(addme);
if (!load)
{
debugMsg("Member Added: "+ name+", Access Level: "+
toString(access) + " Guild Name: " + guildName);
saveMembers();
}
}
void Automation::removeMember(std::string name)
{
debugMsg("Member Removed: "+ name);
guildMembers.erase(findByName(name));
saveMembers();
}
/*Checks to see whether a player is on the guildbot.*/
bool Automation::inGuild(std::string name)
{
std::set<member>::iterator it;
for (it=guildMembers.begin() ; it != guildMembers.end(); it++)
if (it->name == name)
return true;
return false;
}
/* Updates member online status, this uses 4144's online list.*/
void Automation::updateOnline(std::set<std::string> &onlinePlayers)
{
debugMsg("Update Online List.");
onlineList = onlinePlayers;
std::set<member>::iterator it;
std::set<member>::iterator it2;
std::set<member> updateStatus; //used for applying changes
//figure out status changes
for (it=guildMembers.begin(); it != guildMembers.end(); it++ )
{
if (it->accesslevel < 0)
continue;
member old = *it;
if (onlinePlayers.find(it->name) != onlinePlayers.end() && it->status == 0)
{
old.status = 1;
updateStatus.insert(old);
}
else if (onlinePlayers.find(it->name) == onlinePlayers.end() && it->status == 1)
{
old.status = 0;
updateStatus.insert(old);
}
}
//apply status changes
for (it2=updateStatus.begin(); it2 != updateStatus.end(); it2++)
{
member tmp = *it2;
if (it2->status == 1)
{
guildMembers.erase(findByName(it2->name));
/*if (hideOnLoad != true)*/
addMessage(tmp.name+ " is now Online.", tmp.guildName, "Info", 1);
guildMembers.insert(tmp);
whisper(tmp.name, colorText("Welcome to the "+tmp.guildName+"! ("+
toString(countOnline(tmp.guildName)) +" Members are Online)"), 1);
std::string MOTD = getMOTD(tmp.guildName);
if (MOTD != "")
whisper(tmp.name, colorText(MOTD), 1);
}
else if (it2->status == 0)
{
guildMembers.erase(findByName(it2->name));
guildMembers.insert(tmp);
addMessage(tmp.name+ " is now Offline.", tmp.guildName, "Info", 1);
}
}
}
/* Sends messages to all those in the specified guild. */
void Automation::addMessage(std::string msg, std::string guildName,
std::string sender, int p)
{
std::set<member>::iterator it;
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
{
if (sender == "Info" && it->info == 0)
continue;
if (it->name != sender && it->status == 1 && (it->guildName == guildName
|| guildName == "global"))
{
if (sender == "Info")
whisper(it->name,"##0"+msg, p);
else
whisper(it->name, colorText(sender) +": "+"##0"+msg, p);
}
}
}
void Automation::debugMsg(std::string msg)
{
std::string message = removeColors("GuildBot: "+msg);
//logger->log(message.c_str());
}
/* Adds the whispers to the required queue*/
void Automation::whisper(std::string name, std::string msg, int priority)
{
whispermsg newmsg = {name, msg};
if (priority == 0)
whispers.push_back(newmsg);
else
information.push_back(newmsg);
}
void Automation::showOptions()
{
debugMsg("List of Guild Members:");
std::set<member>::iterator it;
for ( it=guildMembers.begin() ; it != guildMembers.end(); it++ )
{
std::stringstream status;
status << " Status: " << it->status;
status << " Access Level: " << it->accesslevel;
status << " Guild: " << it->guildName;
debugMsg("Character name: "+ it->name + status.str());
}
}
/* Saves the member file */
void Automation::saveMembers()
{
mLocalDataDir += ".";
#if defined WIN32
mLocalDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA);
mLocalDataDir += "/ManaGuild";
std::string namelist = mLocalDataDir + "/guildmembers.txt";
#else
std::string namelist = "guildmembers.txt";
#endif
std::ofstream guildMemberFile(namelist.c_str());
if (guildMemberFile.is_open())
{
debugMsg("Save Guild Member File.");
std::set<member>::iterator it;
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
guildMemberFile << it->name.c_str() << ","
<< it->accesslevel << ","
<< it->guildName << std::endl;
guildMemberFile.close();
}
else
{
debugMsg("Guild Member File save failed.");
}
}
/* Loads the member file */
void Automation::loadMembers()
{
mLocalDataDir += ".";
#if defined WIN32
mLocalDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA);
mLocalDataDir += "/ManaGuild";
std::string namelist = mLocalDataDir + "/guildmembers.txt";
#else
std::string namelist = "guildmembers.txt";
#endif
std::string line;
std::ifstream guildMemberFile(namelist.c_str());
if (guildMemberFile.is_open())
{
debugMsg("Load Guild Member File.");
while (!guildMemberFile.eof() )
{
getline (guildMemberFile,line);
if (!line.empty())
{
/* Messy pretend tokenizer*/
std::string::size_type pos = line.find(',');
std::string::size_type pos2 = line.find(',', pos+1);
std::string guildName = line.substr(pos2+1, line.size());
std::string name = line.substr(0, pos);
int access = toInt(line.substr(pos+1, pos2));
addMember(name, guildName, access);
}
}
guildMemberFile.close();
}
else
{
debugMsg("Guild Member File open failed. If this is your "
"first time runing the chat bot please ignore this message.");
}
load = false;
}
/* Color parts of the text to avoid bans from chat spam, also looks pretty. */
std::string Automation::colorText(std::string text)
{
if (repeatfix)
text = "##3"+text;
else
text = "##2"+text;
repeatfix = !repeatfix;
return text;
}
/* Returns the number of members online in a specified guild. */
int Automation::countOnline(std::string guildName)
{
int count = 0;
std::set<member>::iterator it;
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
if (it->status == 1 && (it->guildName == guildName || guildName == "global"))
count++;
return count;
}
void Automation::clearMembers()
{
debugMsg("Clear Members.");
guildMembers.clear();
}
int Automation::accessLevel(std::string name)
{
int accesslevel = findByName(name).accesslevel;
return accesslevel;
}
/* Checks to see whether the user has the required permissions,
* for either changing the someones accesslevel or removing another player. */
bool Automation::hasPermission(std::string user,
std::string player, int changeLevel)
{
// level 5 players should not be able to remove others.
if (accessLevel(user) != 50 && accessLevel(player) < accessLevel(user)
&& changeLevel <= accessLevel(user) && accessLevel(user) > 5 &&
findByName(user).guildName == findByName(player).guildName)
{
return true;
}
else
{
whisper(user, colorText("You do not have the correct "
"access level, for this operation."), 1);
return false;
}
return false;
}
/* Finds and a member by name. */
member Automation::findByName(std::string name)
{
member findme;
std::set<member>::iterator it;
for (it=guildMembers.begin(); it != guildMembers.end(); it++)
if (it->name == name)
findme = *it;
return findme;
}
/* Outputs a test message for testing purposes.*/
void Automation::testMsg()
{
for (int i=0;i < 20; i++){
std::string message = "testing " + toString(i);
addMessage(message,"global", "Info", 0);
}
}
std::string Automation::getMOTD(std::string guild)
{
std::map<std::string, std::string>::iterator it;
it=guildMOTD.find(guild);
if (it != guildMOTD.end())
return it->second;
else
return "";
}