/* * * Automation.cpp: The Ruined Place Chat Bot. * */ #include #include #include #include #include #include #include #include #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 guildMembers; std::deque whispers; std::deque information; std::set onlineList; std::set inviteList; std::map 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::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::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::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::iterator it; it=guildMOTD.find(findByName(nick).guildName); if (it != guildMOTD.end()) { guildMOTD.erase(it); guildMOTD.insert(std::pair(findByName(nick).guildName, args)); } else { guildMOTD.insert(std::pair(findByName(nick).guildName, args)); } whisper(nick, colorText("MOTD Set:" + args), 1); } else if (type == "!removemotd" && accessLevel(nick) >= 5) { std::map::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 , !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 , !disband, !setaccess " "(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 , !setaccess " "(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 , (the comma is important)" " and !removeguild ."), 1); whisper(nick, colorText( " To send a message to all guilds use !global , " "!listallonline, !joinguild (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::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::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::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::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::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 &onlinePlayers) { debugMsg("Update Online List."); onlineList = onlinePlayers; std::set::iterator it; std::set::iterator it2; std::set 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::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::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::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::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::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::iterator it; it=guildMOTD.find(guild); if (it != guildMOTD.end()) return it->second; else return ""; }