roh/conf.old/area/
roh/config/code/python/
roh/config/game/area/
roh/config/game/signs/
roh/help/dmhelp/
roh/help/help/
roh/log/
roh/log/staff/
roh/monsters/ocean/
roh/objects/misc/
roh/objects/ocean/
roh/player/
roh/rooms/area/1/
roh/rooms/misc/
roh/rooms/ocean/
roh/src-2.47e/
/*
 * communication.cpp
 *	 Functions used for communication on the mud
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * Permission to use, modify and distribute is granted via the
 *  Creative Commons - Attribution - Non Commercial - Share Alike 3.0 License
 *    http://creativecommons.org/licenses/by-nc-sa/3.0/
 *
 * 	Copyright (C) 2007-2012 Jason Mitchell, Randi Mitchell
 * 	   Contributions by Tim Callahan, Jonathan Hseu
 *  Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman
 *
 */
#include "mud.h"
#include "commands.h"
#include "communication.h"
#include "move.h"
#include "clans.h"
#include "guilds.h"

//*********************************************************************
//						confusionChar
//*********************************************************************

char confusionChar() {
	int n;
	switch(mrand(0,10)) {
	case 0:
		// random upper case
		return(mrand(65, 90));
	case 1:
	case 2:
		// random character
		n = mrand(0,57);
		// weight all the characters in the different ranges evenly
		if(n <= 31) {
			// mrand(91, 121);		31 chars
			return(n + 91);
		} else if(n <= 38) {
			// mrand(58, 64);		7 chars
			return(n - 31 + 58);
		} else if(n <= 54) {
			// mrand(32, 47);		16 chars
			return(n - 31 - 7 + 32);
		} else {
			// mrand(123, 125);		3 chars
			return(n - 31 - 7 - 16 + 123);
		}
	case 3:
		// random number
		return(mrand(48, 57));
		break;
	default:
		break;
	}
	// random lower case
	return(mrand(97, 122));
}

//*********************************************************************
//						confusionText
//*********************************************************************
// make the player say gibberish instead of actual words

bstring confusionText(Creature* speaker, bstring text) {
	if(	(!speaker->isEffected("confusion") && !speaker->isEffected("drunkenness")) ||
		speaker->isStaff()
	)
		return(text);

	bool scramble = mrand(0,1);

	for(int i=0; i<text.getLength(); i++) {
		if(text.getAt(i) == ' ') {
			scramble = !mrand(0,3);
			// sometimes scramble spaces, too
			if(!mrand(0,8))
				text.setAt(i, confusionChar());
		} else {

			if(scramble || !mrand(0,8))
				text.setAt(i, confusionChar());

		}
	}
	return(text);
}


//*********************************************************************
//						getFullstrText
//*********************************************************************
// general function that gets a cmnd->fullstr and returns the portion
// of the text that will get sent to other player(s)
// skip = 1 when we only have 1 command to skip
//    Ex: say hello all
// skip = 2 when we have 2 commands to skip
//    Ex: tell bob hello there

bstring getFullstrTextTrun(bstring str, int skip, char toSkip, bool colorEscape) {
	return(getFullstrText(str, skip, toSkip, colorEscape, true));
}

bstring getFullstrText(bstring str, int skip, char toSkip, bool colorEscape, bool truncate) {
	int i=0, n = str.length() - 1;

	// first of all, trim both directions
	while(i <= n && str.at(i) == toSkip)
		i++;
	while(n >= 0 && str.at(n) == toSkip)
		n--;

	while(skip > 0) {
		// skip a set of spaces
		for(; i < (int)str.length()-1; i++)
			if(str.at(i) == toSkip && str.at(i+1) != toSkip)
				break;
		i++;
		skip--;
	}


	// these numbers can't overlap
	if(i > n)
		str = "";
	else {
		str = str.substr(0, n+1);
		str = str.substr(i);

		if(truncate) {
			// skip a set of spaces
			for(i=0; i < (int)str.length()-1; i++) {
				if(str.at(i) == toSkip && str.at(i+1) != toSkip) {
					i--;
					break;
				}
			}
			i++;

			// these numbers can't overlap
			str = str.substr(0, i);
		}
	}

	if(colorEscape)
		str = escapeColor(str);
	return(str);
}

//*********************************************************************
//						communicateWith
//*********************************************************************
// This is the base function for communication with a target player.
// The commands tell, whisper, reply, and sign are all routed through here.

int communicateWith(Player* player, cmd* cmnd) {
	bstring text = "";
	Player	*target=0;
	int		i=0, found=0;
	char	ooc_str[10];

	// reuse text
	text = cmnd->myCommand->getName();

	// get us a channel to use!
	commPtr chan = NULL;
	while(commList[i].name != NULL) {
		if(!strcmp(text.c_str(), commList[i].name)) {
			chan = &commList[i];
			break;
		}
		i++;
	}
	if(!chan) {
		player->print("Unknown channel.\n");
		return(0);
	}

	strcpy(ooc_str, (chan->ooc ? "[ooc]: " : ""));


	// extra checks for reply and sign
	if(chan->type == COM_REPLY) {
		strcpy(cmnd->str[1], player->getLastCommunicate().c_str());
		if(!strlen(cmnd->str[1])) {
			player->print("You have nobody to reply to right now.\n");
			return(0);
		}
	} else if(chan->type == COM_SIGN) {
		if(player->getRace() != DARKELF && !player->isStaff()) {
			player->print("Only dark elves may communicate by signing.\n");
			return(0);
		}
	}


	// the basic communication checks all commands must obey
	player->clearFlag(P_AFK);

	if(!player->ableToDoCommand())
		return(0);

	if(cmnd->num < chan->skip) {
		player->print("%s to whom?\n", com_text_u[chan->type]);
		return(0);
	}

	if(!player->flagIsSet(P_SECURITY_CHECK_OK)) {
		player->print("You may not do that yet.\n");
		return(0);
	}

	// silence!
	if(chan->type != COM_SIGN) {
		if(player->flagIsSet(P_DM_SILENCED)) {
			player->printColor("^CYou are unable to move your fingers.\n");
			return(0);
		}
		if(!player->canSpeak()) {
			player->printColor("^yYou cannot speak.\n");
			return(0);
		}
	} else {
		if(player->flagIsSet(P_DM_SILENCED)) {
			player->printColor("^yYou are unable to move your lips.\n");
			return(0);
		}
	}


	// run the player table
	cmnd->str[1][0] = up(cmnd->str[1][0]);
	Player* ply;
	for(std::pair<bstring, Player*> p : gServer->players) {
		ply = p.second;

		if(!ply->isConnected())
			continue;
		// these two need vicinity
		if(chan->type == COM_SIGN || chan->type == COM_WHISPER)
			if(!player->inSameRoom(ply))
				continue;
		if(!player->canSee(ply))
			continue;
		if(ply->getName() == cmnd->str[1]) {
			target = ply;
			break;
		}
		if(!strncmp(ply->getCName(), cmnd->str[1], strlen(cmnd->str[1]))) {
			target = ply;
			found++;
		}
	}

	if(found > 1) {
		player->print("More than one person with that name.\n");
		return(0);
	}

	if(!target || !player->canSee(target)) {
		player->print("%s to whom?\n", com_text_u[chan->type]);
		return(0);
	}



	// higher level communication checks
	if(player == target && !player->flagIsSet(P_CAN_FLASH_SELF) && !player->isStaff()) {
		player->print("Talking to yourself is a sign of insanity.\n");
		return(0);
	}

	if(player->getClass() == BUILDER && !target->isStaff()) {
		player->print("You may only communicate with staff!\n");
		return(0);
	}

	// polite ignore message for watchers
	if(!player->isCt() && target->flagIsSet(P_NO_TELLS)) {
		if(target->isPublicWatcher())
			player->print("%s is busy at the moment. Please mudmail %s.\n", target->getCName(), target->himHer());
		else
			player->print("%s is ignoring everyone.\n", target->getCName());
		return(0);
	}


	// staff can always message AFK people
	if(	target->flagIsSet(P_AFK) &&
		!player->checkStaff("%s is currently afk.\n", target->getCName()))
		return(0);

	if(chan->type != COM_SIGN && target->isEffected("deafness") && !player->isStaff()) {
		player->print("%s is deaf and cannot hear anything.\n", target->getCName());
		return(0);
	}


	if(	target->isIgnoring(player->getName()) &&
		!target->checkStaff("%s is ignoring you.\n", target->getCName())
	)
		return(0);

	if(chan->type == COM_SIGN || chan->type == COM_WHISPER)
		player->unhide();

	if(chan->type == COM_SIGN) {
		if(!target->isStaff()) {
			if(target->getRace() != DARKELF && target->getClass() < BUILDER) {
				player->print("%s doesn't understand dark elven sign language.\n", target->upHeShe());
				return(0);
			}
			if(target->isEffected("blindness")) {
				player->print("%M is blind! %s can't see what you're signing.\n", target, target->upHeShe());
				return(0);
			}
			if(player->isInvisible() && !target->isEffected("detect-invisible")) {
				player->print("You are invisible! %s cannot see what you are signing.\n", target->getCName());
				return(0);
			}
		}
	} else {
		if(	!target->languageIsKnown(LUNKNOWN+player->current_language) &&
			!player->isStaff() &&
			!target->isEffected("comprehend-languages")
		) {
			player->print("%s doesn't speak your current language.\n", target->upHeShe());
			return(0);
		}
	}

	// spam check
	if(player->checkForSpam())
		return(0);


	// reuse text
	text = getFullstrText(cmnd->fullstr, chan->skip, ' ', true);
	if(text == "") {
		player->print("%s what?\n", com_text_u[chan->type]);
		return(0);
	}
	text = confusionText(player, text);

	player->printColor("^cYou %s \"%s%s\" to %N.\n",
		com_text[chan->type],
		ooc_str, text.c_str(), target);


	if(chan->type == COM_WHISPER)
		broadcast(player->getSock(), target->getSock(), player->getParent(), "%M whispers something to %N.", player, target);


	if(!target->isGagging(player->getName())) {
		target->printColor("%s%s%M %s%s, \"%s%s\".\n",
			target->customColorize("*CC:TELL*").c_str(),
			chan->type == COM_WHISPER || chan->type == COM_SIGN ? "" : "### ",
			player,
			chan->type == COM_WHISPER || chan->type == COM_SIGN ? com_text[chan->type] : "just flashed",
			chan->type == COM_WHISPER || chan->type == COM_SIGN ? " to you" : "",
			ooc_str, text.c_str());
	} else {
		if(!player->isDm() && !target->isDm())
			broadcast(watchingEaves, "^E--- %s TRIED sending to %s, \"%s\".",
				player->getCName(), target->getCName(), text.c_str());
	}


	// sign doesnt use reply anyway
	if(chan->type != COM_SIGN) {

		if(!target->isStaff() &&
			( ( player->isEffected("incognito") && !player->inSameRoom(target) ) ||
				( player->isInvisible() && !target->isEffected("detect-invisible") ) ||
				( player->isEffected("mist") && !target->isEffected("true-sight") ) ||
				player->flagIsSet(P_DM_INVIS) ||
		    	target->flagIsSet(P_UNCONSCIOUS) ||
		    	target->isBraindead() ||
		    	target->flagIsSet(P_LINKDEAD)
		    )
		) {
			player->print("%s will be unable to reply.", target->upHeShe());
			if(target->flagIsSet(P_LINKDEAD))
				player->print(" %s is linkdead.", target->upHeShe());
			player->print("\n");

			if(player->flagIsSet(P_ALIASING))
				player->print("Sent from: %s.\n", player->getAlias()->getCName());
		} else if(player->flagIsSet(P_IGNORE_ALL)) {
			player->print("You are ignoring all, as such they will be unable to respond.\n");
		}

	} else {

	    for(Player* ply : player->getRoomParent()->players) {
			if(	ply != player && ply != target && !ply->isEffected("blindness"))
			{
				if(ply->getRace() == DARKELF || ply->isStaff())
					ply->print("%M signed, \"%s\" to %N.\n", player, text.c_str(), target);
				else
					ply->print("%M signed something in dark elven to %N.\n", player, target);
			}
		}

	}

	if(player->isDm() || target->isDm())
		return(0);

	broadcast(watchingEaves, "^E--- %s %s to %s, \"%s%s\".", player->getCName(),
		com_text[chan->type], target->getCName(), ooc_str, text.c_str());

	player->bug("%s %s, \"%s%s\" to %s.\n", player->getCName(),
		com_text[chan->type], ooc_str, text.c_str(), target->getCName());
	target->bug("%s %s, \"%s%s\" to %s.\n", player->getCName(),
		com_text[chan->type], ooc_str, text.c_str(), target->getCName());

	if(chan->type != COM_SIGN)
		target->setLastCommunicate(player->getName());
	return(0);
}


//*********************************************************************
//						commTarget
//*********************************************************************
// function that does the actual printing of message for below

void commTarget(Creature* player, Player* target, int type, bool ooc, int lang, bstring text, bstring speak, char *ooc_str, bool anon) {
	std::ostringstream out;

	if(!target || target->flagIsSet(P_UNCONSCIOUS))
		return;
	if(target->isEffected("deafness") && !player->isStaff())
		return;

	if(type != COM_GT)
		speak += "s";

	if(type == COM_EMOTE) {
		out << player->getCrtStr(target, CAP) << " " << text << "\n";
		target->bug("%s emoted: %s.\n", player->getCName(), text.c_str());

	} else if( ooc ||
		target->languageIsKnown(LUNKNOWN+lang) ||
		target->isStaff() ||
		target->isEffected("comprehend-languages"))
	{

		if(type == COM_GT)
			out << target->customColorize("*CC:GROUP*").c_str() << "### ";

		if(ooc || lang == LCOMMON) {
			if(anon) out << "Someone";
			else	 out << player->getCrtStr(target, CAP);
			out << " " << speak;
		} else {
			if(anon) out << "Someone";
			else	 out << player->getCrtStr(target, CAP);

			out << " " << speak << " in " << get_language_adj(lang);
		}
		out << ", \"";

//		if(!ooc && target->flagIsSet(P_LANGUAGE_COLORS))
//			ANSI(fd, get_lang_color(lang));
		out << ooc_str << text;
		out << "\".\n";

		target->bug("%s %s in %s, \"%s%s.\"\n", player->getCName(), com_text[type],
			get_language_adj(lang), ooc_str, text.c_str());

	} else {
		if(anon) out << "Someone";
		else	 out << player->getCrtStr(target, CAP);

		out << " " << speak << " something in " << get_language_adj(lang) << ".\n";
		target->bug("%s %s something in %s.\n", player->getCName(), speak.c_str(), get_language_adj(lang));
	}
	*target << ColorOn << out.str() << ColorOff;
//	target->printColor("%s", out.str().c_str());
}

//*********************************************************************
//						communicate
//*********************************************************************
// This function allows the player to say something to all the other
// people in the room (or nearby rooms).

int pCommunicate(Player* player, cmd* cmnd) {
	return(communicate(player, cmnd));
}

int communicate(Creature* creature, cmd* cmnd) {
	const Player* player=0;
	bstring text = "", name = "";
	Creature *owner=0;
	Player*	pTarget=0;
	BaseRoom* room=0;

	int		i=0, lang;
	char	speak[35], ooc_str[10];
	if(creature->isPet())
		owner = creature->getMaster();
	else
		owner = creature;
	player = owner->getAsConstPlayer();

	// reuse text
	text = cmnd->myCommand->getName();

	// get us a channel to use!
	sayPtr chan = NULL;
	while(sayList[i].name != NULL) {
		if(!strcmp(text.c_str(), sayList[i].name)) {
			chan = &sayList[i];
			break;
		}
		i++;
	}
	if(!chan) {
		creature->print("Unknown channel.\n");
		return(0);
	}

	strcpy(ooc_str, (chan->ooc ? "[ooc]: " : ""));
	owner->clearFlag(P_AFK);

	if(!creature->ableToDoCommand())
		return(0);

	if(!creature->canSpeak() || (owner && owner != creature && !owner->canSpeak())) {
		creature->printColor("^yYour lips move but no sound comes forth.\n");
		return(0);
	}
	// reuse text
	text = getFullstrText(cmnd->fullstr, 1, ' ', true);
	if(text == "") {
		creature->print("%s what?\n", com_text_u[chan->type]);
		return(0);
	}
	text = confusionText(owner, text);

	lang = owner->current_language;

	if(chan->ooc)
		strcpy(speak, "say");
	else if(chan->type == COM_RECITE)
		strcpy(speak, "recite");
	else if(chan->type == COM_GT)
		strcpy(speak, "group mentioned");
	else if(chan->type == COM_YELL) {
		strcpy(speak, "yell");
		text += "!";
	} else
		strcpy(speak, get_language_verb(lang));


	if(chan->type == COM_GT) {
	    // Group Tell is being handled slightly differently as it now uses a std::list instead of ctags
	    // Adapt the other communication methods to use this once they've been moved to a std::list as well

	    Group* group = creature->getGroup();
	    if(group) {
	        for(Creature* crt : group->members) {
	            pTarget = crt->getAsPlayer();
	            if(!pTarget) continue;
	            // GT prints to the player!
	            if(pTarget == creature && chan->type != COM_GT)
	                continue;

	            if(pTarget->isGagging(creature->isPet() ? creature->getMaster()->getCName() : creature->getCName()))
	                continue;

	            if(pTarget->getGroupStatus() < GROUP_MEMBER) continue;

	            commTarget(creature, pTarget, chan->type, chan->ooc, lang, text, speak, ooc_str, false);
	        }
	    } else {
	        creature->print("You are not in a group.\n");
	        return(0);
	    }

	} else {
		creature->unhide();

        if(chan->type == COM_EMOTE) {

            creature->printColor("You emote: %s.\n", text.c_str());
            player->bug("%s emoted: %s.\n", creature->getCName(), text.c_str());

        } else {
            char intro[2046];
            if(chan->ooc || lang == LCOMMON)
                sprintf(intro, "You %s,", speak);
            else
                sprintf(intro, "You %s in %s,", speak, get_language_adj(lang));

            creature->printColor("%s%s \"%s%s\".\n^x", ((!chan->ooc && creature->flagIsSet(P_LANGUAGE_COLORS)) ? get_lang_color(lang) : ""),
            		intro, ooc_str, text.c_str());
            player->bug("%s %s in %s, \"%s%s.\"\n", creature->getCName(), com_text[chan->type],
                get_language_adj(lang), ooc_str, text.c_str());

        }

        for(Player* ply : creature->getRoomParent()->players) {
            if(chan->shout)
                ply->wake("Loud noises disturb your sleep.", true);

            // GT prints to the player!
            if(ply == creature && chan->type != COM_GT)
                continue;

            if(ply->isGagging(creature->isPet() ? creature->getMaster()->getName() : creature->getName()))
                continue;

            commTarget(creature, ply, chan->type, chan->ooc, lang, text, speak, ooc_str, false);
        }

        if(chan->shout) {
            // Because of multiple exits leading to the same room, we will keep
            // track of who has heard us shout
            std::list<Socket*> listeners;
            std::list<Socket*>::iterator it;
            bool	heard = false;

            // This allows a player to yell something that will be heard
            // not only in their room, but also in all rooms adjacent to them. In
            // the adjacent rooms, however, people will not know who yelled.


            for(Exit* exit : creature->getRoomParent()->exits) {
                room = 0;
                i=0;
                PlayerSet::iterator pIt, pEnd;
                // don't shout through closed doors
                if(!exit->flagIsSet(X_CLOSED))
                    Move::getRoom(0, exit, &room, true);

                // the same-room checks aren't run in getRoom because
                // we don't send a creature.
                if(room) {
                    if(creature->getParent() && room == creature->getParent())
                        continue;
                    pIt = room->players.begin();
                    pEnd = room->players.end();
                } else
                    continue;

                while(pIt != pEnd) {
                    pTarget = (*pIt++);

                    if(!pTarget)
                        continue;

                    pTarget->wake("Loud noises disturb your sleep.", true);
                    if(pTarget->isGagging(creature->getName()))
                        continue;

                    // have they already heard us yell?
                    heard = false;

                    for(it = listeners.begin() ; it != listeners.end() ; it++) {
                        if((*it) == pTarget->getSock()) {
                            heard = true;
                            break;
                        }
                    }

                    if(!heard) {
                        listeners.push_back(pTarget->getSock());
                        commTarget(creature, pTarget, chan->type, chan->ooc, lang, text, speak, ooc_str, true);
                    }
                }
            }
            listeners.clear();
        }

        if(chan->passphrase) {

           	for(Exit* exit : creature->getRoomParent()->exits) {
                // got the phrase right?
                if(exit->getPassPhrase() != "" && exit->getPassPhrase() == text) {
                    // right language?
                    if(!exit->getPassLanguage() || lang == exit->getPassLanguage()) {
                        // even needs to be open?
                        if(exit->flagIsSet(X_LOCKED)) {
                            broadcast(NULL, creature->getRoomParent(), "The %s opens!", exit->getCName());
                            exit->clearFlag(X_LOCKED);
                            exit->clearFlag(X_CLOSED);

                            if(exit->getOpen() != "") {
                                if(exit->flagIsSet(X_ONOPEN_PLAYER)) {
                                    creature->print("%s.\n", exit->getOpen().c_str());
                                } else {
                                    broadcast(0, creature->getRoomParent(), exit->getOpen().c_str());
                                }
                            }

                        }
                    }
                }
            }
        }

	}
	// DMs still broadcast to eaves on GT
	if(creature->isDm() && chan->type != COM_GT)
		return(0);


	if(creature->isPet()) {
		name = creature->getMaster()->getName() + "'s " + creature->getName();
	} else
		name = creature->getName();

	if(chan->type == COM_EMOTE)
		broadcast(watchingSuperEaves, "^E--- %s %s.", name.c_str(), text.c_str());
	else
		broadcast(watchingSuperEaves, "^E--- %s %s, \"%s%s\" in %s.", name.c_str(), com_text[chan->type],
			ooc_str, text.c_str(), get_language_adj(lang));

	Player* pPlayer = creature->getAsPlayer();
	// spam check
	if(pPlayer)
		pPlayer->checkForSpam();

	return(0);
}

bstring mxpTag(bstring str) {
    return( bstring(MXP_BEG) + str + bstring(MXP_END));
}
//*********************************************************************
//						channel
//**********************************************************************
// This function is used as a base for all global communication channels

int channel(Player* player, cmd* cmnd) {
	bstring text = "", chanStr = "", extra = "";
	int		i=0, check=0, skip=1;
	const Guild* guild=0;

	player->clearFlag(P_AFK);
	chanStr = cmnd->myCommand->getName();
	i = strlen(cmnd->str[1]);

	// these require special attention - we most provide an
	// override for the check below
	if(chanStr == "dmcls" || chanStr == "dmclass") {

		chanStr = "classsend";
		skip = 2;

		for(check=1; check<CLASS_COUNT-1; check++)
			if(!strncasecmp(get_class_string(check), cmnd->str[1], i))
				break;

		// these checks are overkill, but it never hurts to be safe:
		// this will force them to use their own class
		if(check == DUNGEONMASTER && !player->isDm())
			check = 0;
		if(check == CARETAKER && !player->isCt())
			check = 0;

	} else if(chanStr == "dmrace") {

		chanStr = "racesend";
		skip = 2;

		for(check=1; check<gConfig->raceCount()-1; check++)
			if(!strncasecmp(gConfig->getRace(check)->getName().c_str(), cmnd->str[1], i))
				break;

	} else if(chanStr == "dmclan") {

		chanStr = "clansend";
		skip = 2;

		std::map<int, Clan*>::iterator it;
		Clan *clan=0;

		for(it = gConfig->clans.begin() ; it != gConfig->clans.end() ; it++) {
			clan = (*it).second;
			if(!strncasecmp(cmnd->str[1], clan->getName().c_str(), i)) {
				check = clan->getId();
				break;
			}
		}

	} if(chanStr == "dmguild") {

		skip = 2;
		guild = gConfig->getGuild(player, cmnd->str[1]);

		if(!guild)
			return(0);
	}



	text = getFullstrText(cmnd->fullstr, skip);
	if(text == "") {
		player->print("Send what?\n");
		return(0);
	}
	text = confusionText(player, text);


	// this isnt a channel in the list
	if(chanStr == "dmguild") {
		doGuildSend(guild, player, text);
		return(0);
	}


	// get us a channel to use!
	channelPtr chan = NULL;
	i = 0;
	while(channelList[i].channelName != NULL) {
		if(	!strcmp(chanStr.c_str(), channelList[i].channelName) &&
			(!channelList[i].canSee || channelList[i].canSee(player))
		) {
			chan = &channelList[i];
			break;
		}
		i++;
	}

	if(!chan)
		return(cmdNoExist(player, cmnd));


	// get us that extra text we'll be attaching to the front of the message
	if(chan->type == COM_CLASS) {

		if(!check)
			check = player->getClass();
		extra = "(";
		extra += get_class_string(check);
		if(player->getDeity() && gConfig->classes[get_class_string(check)]->needsDeity()) {
			extra += ":";
			extra += gConfig->getDeity(player->getDeity())->getName();
		}
		extra += ") ";

	} else if(chan->type == COM_RACE) {

		if(!check)
			check = player->getDisplayRace();
		extra = "(";
		extra += gConfig->getRace(check)->getName().c_str();
		extra += ") ";

	} else if(chan->type == COM_CLAN) {

		if(!player->getClan() && !player->getDeity() && !check) {
			player->print("You do not belong to a clan or religion.\n");
			return(0);
		}

		extra = "(";
		if(!check) {
			if(player->getDeity()) {
				check = player->getDeityClan();
				extra += gConfig->getDeity(player->getDeity())->getName();
			} else {
				check = player->getClan();
				extra += gConfig->getClan(player->getClan())->getName();
			}
		} else
			extra += gConfig->getClan(check)->getName();
		extra += ") ";

	}


	// it is up to the canUse function to print out the error message
	if(chan->canUse && !chan->canUse(player))
		//oldPrint(fd, "You are not authorized to use that channel.\n");
		return(0);
	if(chan->maxLevel != -1 && player->getLevel() > chan->maxLevel && !player->isWatcher()) {
		player->print("You are too high of a level to use that channel.\n");
		return(0);
	}
	if(chan->minLevel != -1 && player->getLevel() < chan->minLevel && !player->isWatcher()) {
		player->print("You must be level %d to use the %s channel.\n", chan->minLevel, chan->channelName);
		return(0);
	}



	if(player->flagIsSet(P_GLOBAL_GAG) && !player->isStaff()) {
		// global gag - don't let them know they're gagged!
		if(extra != "")
			player->printColor("%s%s", player->customColorize(chan->color).c_str(), extra.c_str());

		player->printColor(player->customColorize(chan->color + chan->displayFmt).c_str(), player, text.c_str());
	} else {

		std::ostringstream eaves;
		eaves << "--- " << extra << player->getName() << " ";
		if(chan->type == COM_CLASS)
			eaves << "class";
		else if(chan->type == COM_RACE)
			eaves << "race";
		else if(chan->type == COM_CLAN)
			eaves << "clan";
		eaves << " sent, \"" << text << "\".\n";

		bstring etxt = eaves.str();
		etxt = escapeColor(etxt);
		text = escapeColor(text);

		// more complicated checks go here
		Player* ply=0;
		Socket* sock=0;
		for(std::pair<bstring, Player*> p : gServer->players) {
			ply = p.second;

			sock = ply->getSock();
			if(!sock->isConnected())
				continue;

			// no gagging staff!
			if(player && ply->isGagging(player->getName()) && !player->isCt())
				continue;
			// deaf people can always hear staff and themselves
			if(ply->isEffected("deafness") && !player->isStaff() && ply != player)
				continue;

			// must satisfy all the basic canHear rules to hear this channel
			if(	(	(!chan->canHear || chan->canHear(sock)) &&
					(!chan->flag || ply->flagIsSet(chan->flag)) &&
					(!chan->not_flag || !ply->flagIsSet(chan->not_flag)) )
				&& ( // they must also satisfy any special conditions here
					(chan->type != COM_CLASS || ply->getClass() == check) &&
					(chan->type != COM_RACE || ply->getDisplayRace() == check) &&
					(chan->type != COM_CLAN || (ply->getDeity() ? ply->getDeityClan() : ply->getClan()) == check) ) )
			{
				if(extra != "")
				    *ply << ColorOn << ply->customColorize(chan->color) << extra << ColorOff;

				bstring toPrint = chan->displayFmt;
				bstring icName = player->getCrtStr(ply, ply->displayFlags() | CAP);
				bstring oocName = player->getName();
				bstring prompt = "";

				if(ply->getSock()->getTermType().toUpper().find("MUDLET") != bstring::npos)
					prompt = " PROMPT";

				if(ply->canSee(player)) {
				    icName = mxpTag(bstring("player name='") + player->getName() + "'" + prompt ) + icName + mxpTag("/player");
				    oocName = mxpTag(bstring("player name='") + player->getName()  + "'" + prompt) + oocName + mxpTag("/player");
				}

				toPrint.Replace("*IC-NAME*", icName.c_str());
				toPrint.Replace("*OOC-NAME*", oocName.c_str());
				//std::cout << "ToPrint: " << toPrint << std::endl;

				if(ply->isStaff() || (player->current_language && ply->isEffected("comprehend-languages"))
				        || ply->languageIsKnown(player->current_language))
				{
				    // Listern speaks this language
				    toPrint.Replace("*TEXT*", text.c_str());
				} else {
				    // Listern doesn't speak this language
				    toPrint.Replace("*TEXT*", "<something incomprehensible>");
				}


				if(player->current_language != LCOMMON)
				    toPrint += bstring(" in ") + get_language_adj(player->current_language) + ".";

				*ply << ColorOn << ply->customColorize(chan->color) << toPrint << "\n" << ColorOff;
			}


			// even if they fail the check, it might still show up on eaves
			if(	chan->eaves &&
				watchingEaves(sock) &&
				!(chan->type == COM_CLASS && check == DUNGEONMASTER)
			) {
				ply->printColor("^E%s", etxt.c_str());
			}
		}
	}

	return(0);
}


//*********************************************************************
//						cmdSpeak
//*********************************************************************

int cmdSpeak(Player* player, cmd* cmnd) {
	int		lang=0;

	if(!player->ableToDoCommand())
		return(0);

	if(cmnd->num < 2 ) {
		player->print("Speak what?\n");
		return(0);
	}

	lowercize(cmnd->str[1],0);

	switch (cmnd->str[1][0]) {

	case 'a':
		switch (cmnd->str[1][1]) {
		case 'b':
			lang = LABYSSAL;
			break;
		case 'l':
			lang = 0;
			break;
		case 'r':
			lang = LARCANIC;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 'b':
		lang = LBARBARIAN;
		break;

	case 'c':
		switch (cmnd->str[1][1]) {
		case 'a':
			lang = LINFERNAL;
			break;
		case 'e':
			lang = LCELESTIAL;
			break;
		case 'o':
			lang = LCOMMON;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 'd':
		switch (cmnd->str[1][1]) {
		case 'a':
			lang = LDARKELVEN;
			break;
		case 'r':
			lang = LDRUIDIC;
			break;
		case 'w':
			lang = LDWARVEN;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 'e':
		lang = LELVEN;
		break;
	case 'g':
		switch (cmnd->str[1][1]) {
		case 'i':
			lang = LGIANTKIN;
			break;
		case 'n':
			lang = LGNOMISH;
			break;
		case 'o':
			lang = LGOBLINOID;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 'h':
		switch (cmnd->str[1][1]) {
		case 'u':
			lang = LCOMMON;
			break;
		default:
			switch (cmnd->str[1][4]) {
			case 'e':
				player->print("Half elves have no language of their own.\n");
				player->print("They speak the elven or common tongues.\n");
				return(0);
				break;
			case 'o':
				player->print("Half orcs have no language of their own.\n");
				player->print("They speak the orcish or common tongues.\n");
				return(0);
				break;
			case 'l':
				lang = LHALFLING;
				break;
			case 'g':
				lang = LGIANTKIN;
				break;
			default:
				player->print("You do not know that language.\n");
				return(0);
				break;
			}
			break;
		}
		break;
	case 'i':
		lang = LINFERNAL;
		break;
	case 'k':
		switch (cmnd->str[1][1]) {
		case 'a':
			lang = LKATARAN;
			break;
		case 'o':
			lang = LKOBOLD;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 'm':
		lang = LMINOTAUR;
		break;
	case 'o':
		switch (cmnd->str[1][1]) {
		case 'g':
			lang = LOGRISH;
			break;
		case 'r':
			lang = LORCISH;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 's':
		switch (cmnd->str[1][1]) {
		case 'e':
			lang = LCELESTIAL;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 't':
		switch (cmnd->str[1][1]) {
		case 'i':
			lang = LTIEFLING;
			break;
		case 'r':
			lang = LTROLL;
			break;
		case 'h':
			lang = LTHIEFCANT;
			break;
		default:
			player->print("You do not know that language.\n");
			return(0);
			break;
		}
		break;
	case 'w':
		lang = LWOLFEN;
		break;
	default:
		player->print("You do not know that language.\n");
		return(0);
		break;
	}


	if(lang == player->current_language) {
		player->print("You're already speaking %s!\n", get_language_adj(lang));
		return(0);
	}

	if(!player->isEffected("tongues") && !player->languageIsKnown(LUNKNOWN+lang) && !player->isStaff()) {
		player->print("You do not know how to speak %s.\n", get_language_adj(lang));
		return(0);
	} else {
		player->print("You will now speak in %s.\n", get_language_adj(lang));
		player->current_language = lang;
	}

	return(0);
}



//*********************************************************************
//						cmdLanguages
//*********************************************************************

int cmdLanguages(Player* player, cmd* cmnd) {
	char    str[2048];
	//	char	lang[LANGUAGE_COUNT][32];
	int     i, j=0;

	player->print("Currently speaking: %s.\n", get_language_adj(player->current_language));
	player->print("Languages known:");

	strcpy(str," ");
	for(i = 0; i < LANGUAGE_COUNT ; i++) {
		if(player->languageIsKnown(LUNKNOWN + i)) {
			j++;
			strcat(str, get_language_adj(i));
			strcat(str, ", ");
		}
	}

	if(!j)
		strcat(str, "None.");
	else {
		str[strlen(str) - 2] = '.';
		str[strlen(str) - 1] = 0;
	}

	player->print("%s\n", str);

	if(player->isEffected("tongues"))
		player->printColor("^yYou have the ability to speak any language.\n");

	if(player->isEffected("comprehend-languages"))
		player->printColor("^yYou have the ability to comprehend any language.\n");

	return(0);
}


//*********************************************************************
//						printForeignTongueMsg
//*********************************************************************

void printForeignTongueMsg(const BaseRoom *inRoom, Creature *talker) {
	int			lang=0;

	if(!talker || !inRoom)
		return;

	lang = talker->current_language;
	if(!lang)
		return;

	for(Player* ply : inRoom->players) {
		if(ply->languageIsKnown(lang) || ply->isEffected("comprehend-languages") || ply->isStaff()) {
			continue;
		}
		ply->print("%M says something in %s.\n", talker, get_language_adj(lang));
	}
}

//*********************************************************************
//						sayTo
//*********************************************************************

void Monster::sayTo(const Player* player, const bstring& message) {
	short language = player->current_language;

	broadcast_rom_LangWc(language, player->getSock(), player->currentLocation,
		"%M says to %N, \"%s\"^x", this, player, message.c_str());
	printForeignTongueMsg(player->getConstRoomParent(), this);

	player->printColor("%s%M says to you, \"%s\"\n^x",get_lang_color(language), this, message.c_str());
}


//*********************************************************************
//						canCommunicate
//*********************************************************************

bool canCommunicate(Player* player) {
	if(player->getClass() == BUILDER) {
		player->print("You are not allowed to broadcast.\n");
		return(false);
	}

	// Non staff checks on global communication
	if(!player->isStaff()) {
		if(!player->ableToDoCommand())
			return(false);
		if(player->flagIsSet(P_CANT_BROADCAST)) {
			player->print("Due to abuse, you no longer have that privilage.\n");
			return(false);
		}
		if(player->inJail()) {
			player->print("People in jail do not have that privilage.\n");
			return(false);
		}

		if(player->flagIsSet(P_OUTLAW)) {
			player->print("You're an outlaw, you don't have that privilage.\n");
			return(false);
		}
		if(!player->canSpeak()) {
			player->printColor("^yYour voice is too weak to do that.\n");
			return(false);
		}
		// spam check
		if(player->checkForSpam())
			return(false);

	}
	return(true);
}



//*********************************************************************
//						bstring list functions
//*********************************************************************

int listWrapper(Player* player, cmd* cmnd, const char* gerund, const char* noun, bstring (Player::*show)(void) const, bool (Player::*is)(bstring name) const, void (Player::*del)(bstring name), void (Player::*add)(bstring name), void (Player::*clear)(void)) {
	Player	*target=0;
	bool online=true;

	player->clearFlag(P_AFK);

	if(!player->ableToDoCommand())
		return(0);

	if(cmnd->num == 1) {
		player->print("You are %s: %s\n", gerund, (player->*show)().c_str());
		return(0);
	}

	cmnd->str[1][0] = up(cmnd->str[1][0]);

	if((player->*is)(cmnd->str[1])) {
		(player->*del)(cmnd->str[1]);
		player->print("%s removed from your %s list.\n", cmnd->str[1], noun);
		return(0);
	}

	if(cmnd->num == 2) {
		if(!strcmp(cmnd->str[1], "clear")) {
			(player->*clear)();
			player->print("Cleared.\n");
			return(0);
		}
	}

	target = gServer->findPlayer(cmnd->str[1]);

	if(!target) {
		loadPlayer(cmnd->str[1], &target);
		online = false;
	}

	if(!target || player == target) {
		player->print("That player does not exist.\n");
	} else if(target->isStaff() && target->getClass() > player->getClass()) {
		player->print("You cannot %s that player.\n", noun);
		if(!online)
			free_crt(target);
	} else {

		if(!strcmp(noun, "watch")) {
			if(	!player->inSameGroup(target) &&
				!player->isCt() &&
				!(player->isWatcher() && target->getLevel() <= 4)
			) {
				player->print("%M is not a member of your group.\n", target);
				if(!online)
					free_crt(target);
				return(0);
			}
		}

		(player->*add)(cmnd->str[1]);
		player->print("%s added to your %s list.\n", target->getCName(), noun);
		if(!online)
			free_crt(target);
	}

	return(0);
}

int cmdIgnore(Player* player, cmd* cmnd) {
	return(listWrapper(player, cmnd, "ignoring", "ignore",
		&Player::showIgnoring,
		&Player::isIgnoring,
		&Player::delIgnoring,
		&Player::addIgnoring,
		&Player::clearIgnoring
	));
}
int cmdGag(Player* player, cmd* cmnd) {
	return(listWrapper(player, cmnd, "gaging", "gag",
		&Player::showGagging,
		&Player::isGagging,
		&Player::delGagging,
		&Player::addGagging,
		&Player::clearGagging
	));
}
int cmdRefuse(Player* player, cmd* cmnd) {
	return(listWrapper(player, cmnd, "refusing", "refuse",
		&Player::showRefusing,
		&Player::isRefusing,
		&Player::delRefusing,
		&Player::addRefusing,
		&Player::clearRefusing
	));
}
int cmdWatch(Player* player, cmd* cmnd) {
	return(listWrapper(player, cmnd, "watching", "watch",
		&Player::showWatching,
		&Player::isWatching,
		&Player::delWatching,
		&Player::addWatching,
		&Player::clearWatching
	));
}

//*********************************************************************
//						dmGag
//*********************************************************************

int dmGag(Player* player, cmd* cmnd) {
	Player	*target;

	if(!player->isStaff() && !player->isWatcher())
		return(cmdNoExist(player, cmnd));
	if(!player->isWatcher())
		return(cmdNoAuth(player));

	if(cmnd->num < 2) {
		player->print("\nGlobally gag whom?\n");
		return(PROMPT);
	}

	lowercize(cmnd->str[1], 1);
	target = gServer->findPlayer(cmnd->str[1]);

	if(!target) {
		player->print("%s is not on.\n", cmnd->str[1]);
		return(0);
	}

	if(target->flagIsSet(P_GLOBAL_GAG)) {
		player->print("%s is no longer globally gagged.\n", target->getCName());
		broadcast(isWatcher, "^C*** %s is no longer globally gagged.", target->getCName());
		broadcast(isDm, "^g*** %s turned off %s's global gag.", player->getCName(), target->getCName());
		target->clearFlag(P_GLOBAL_GAG);
		return(0);
	}

	if(target->isCt()) {
		target->printColor("^R%s tried to gag you!\n", player->getCName());
		player->print("You can't globally gag a DM or CT.\n");
		return(0);
	}

	logn("log.gag", "%s was globally gagged by %s.\n", target->getCName(), player->getCName());
	target->setFlag(P_GLOBAL_GAG);

	broadcast(isWatcher, "^C*** %s has been globally gagged.", target->getCName());
	broadcast(isDm, "^g*** %s globally gagged %s.", player->getCName(), target->getCName());
	return(0);
}