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/
/*
 * steal.cpp
 *   Routines all involved with stealing by both monsters and players
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * 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 "unique.h"

//*********************************************************************
//						steal_gold
//*********************************************************************

void steal_gold(Player* player, Creature* creature) {
	int chance=0, amt=0;
	if(!player)
		return;
	if(!creature)
		return;
	if(!player->knowsSkill("steal")) {
		player->print("You'd surely be caught if you tried stealing something.\n");
		return;
	}
	if(	player->getSecondClass() == THIEF &&
		(player->getClass() == FIGHTER || player->getClass() == MAGE) )
	{
		player->print("Only pure thieves and thief/mages are able attempt to steal gold.\n");
		return;
	}

	if(player->getClass() != THIEF && !player->isCt()) {
		player->print("Only thieves are skilled enough to steal gold.\n");
		return;
	}


	if(creature->isPlayer() && creature->isStaff() && !player->isDm())
	{
		player->print("Stealing from an immortal is not a good thing for your health.\n");
		creature->print("%M tried to steal something from you.\n", player);
		return;
	}

	if(creature->isPlayer() && !player->isCt() && (player->getLevel() > creature->getLevel() + 6))
	{
		player->print("%M is probably poor. Go look for a better mark.\n", creature);
		return;
	}

	int level = (int)player->getSkillLevel("steal");


	if(creature->isMonster()) {
		if(creature->flagIsSet(M_UNKILLABLE) && !player->checkStaff("%s don't have anything you want.\n", creature->upHeShe()))
			return;
		if( creature->getAsMonster()->isEnemy(player) &&
			!player->checkStaff("Not while %s's attacking you.\n", creature->heShe()))
			return;

	} else {
		if(	player->getRoomParent()->isPkSafe() &&
			!player->isCt() &&
			!creature->flagIsSet(P_OUTLAW))
		{
			player->print("No stealing allowed in this room.\n");
			return;
		}


		if(!player->flagIsSet(P_PLEDGED) || !creature->flagIsSet(P_PLEDGED)) {
			if(!player->flagIsSet(P_CHAOTIC) && !player->isCt() && !creature->flagIsSet(P_OUTLAW)) {
				player->print("Sorry, you're lawful.\n");
				return;
			}
			if(	!creature->flagIsSet(P_CHAOTIC) &&
				!creature->flagIsSet(P_OUTLAW) &&
				!player->isCt())
			{
				player->print("Sorry, that player is lawful.\n");
				return;
			}
		}

		if(!player->isCt() && player->getLevel() > 10 && creature->getLevel() < 4 && creature->isPlayer() && !creature->flagIsSet(P_OUTLAW)) {
			player->print("Steal from someone lower level.\n");
			return;
		}

	}
	if(player->isBlind()) {
		player->printColor("^CHow do you do that? You're blind!\n");
		return;
	}

	if(	creature->isEffected("mist") && !
		!player->checkStaff("You cannot steal from a misted creature.\n") )
		return;


	chance = 5*(level - creature->getLevel()) + bonus((int)player->dexterity.getCur())*5 + bonus(player->getLuck() / 4)*5 + mrand(-15, 15);

	if(player->flagIsSet(P_HIDDEN))
		chance += 10;
	if((creature->isEffected("blindness")) && creature->isMonster())
		chance+=5;
	if(creature->pFlagIsSet(P_OUTLAW))
		chance += 10;
	chance = MIN(chance, 60);

	if(creature->getLevel() > level + 2)
		chance = 0;

	if(player->isDm())
		chance = 101;

	player->statistics.attemptSteal();
	player->interruptDelayedActions();

	if(mrand(1,100) < chance) {
		int formula;
		player->print("You succeeded.\n");
		player->checkImprove("steal", true);
		player->statistics.steal();
		if(creature->coins.isZero()) {
			player->print("%M doesn't have any coins!\n",creature);
			return;
		}

		formula = mrand(creature->coins[GOLD]/10, creature->coins[GOLD]/3);
		amt = MIN(creature->coins[GOLD], MAX(1, formula));
		player->print("You grabbed %d coins.\n", amt);
		creature->coins.sub(amt, GOLD);
		player->coins.add(amt, GOLD);

		if(creature->isPlayer())
			log_immort(false,player, "%s stole %d gold from %s.\n", player->getCName(), amt, creature->getCName());
		if(creature->getAsMonster())
			gServer->logGold(GOLD_IN, player, Money(amt, GOLD), creature, "StealGold");


	} else {
		player->print("You failed to grab any coins!\n");
		player->checkImprove("steal", false);
		player->smashInvis();

		if(creature->isPlayer()) {
			if(!player->isEffected("blindness"))
				creature->print("%M tried to steal some of your gold!\n",player);
			else
				creature->print("Someone tried to steal something from you.\n");
		} else
			creature->getAsMonster()->addEnemy(player);

	}
}

//*********************************************************************
//						get_steal_chance
//*********************************************************************

int get_steal_chance(Player* player, Creature* target, Object* object) {
	Player	*pTarget = target->getAsPlayer();
	int		chance=0, classmod=0, bulk=0, weight=0, level=0;

	if(!target || !object || !player)
		return(0);

	if(player->isStaff())
		return(101);

	// return 0 right away so there is absolutely no chance of stealing.
	if(	object->flagIsSet(O_NO_STEAL) ||
		object->flagIsSet(O_BODYPART) ||
		!Lore::canHave(player, object) ||
		object->flagIsSet(O_NO_DROP) ||
		object->flagIsSet(O_STARTING) ||
		!Unique::canGet(player, object, true) ||
		// no stealing uniques from monsters
		(Unique::is(object) && !target->getAsPlayer()) ||
		object->getQuestnum()
	)
		return(0);

	// mob specific nosteal checks
	if(	target->isMonster() &&
		(	target->flagIsSet(M_CANT_BE_STOLEN_FROM) ||
			target->flagIsSet(M_TRADES)
		)
	)
		return(0);


	// Level modifications for multi-classed thieves
	level = (int)player->getSkillLevel("steal");
	if(player->getSecondClass() == THIEF && player->getClass() == FIGHTER)
		level = MAX(1, level-3);
	if(player->getSecondClass() == THIEF && player->getClass() == MAGE)
		level = MAX(1, level-3);
	if(player->getClass() == THIEF && player->getSecondClass() == MAGE)
		level = MAX(1, level-3);
	if(player->getClass() == CLERIC && player->getDeity() == KAMIRA)
		level = MAX(1, level-3);

	// Base success % chance set here
	chance = (player->getClass() == THIEF) ? 4*level : 3*level;

	if(player->getClass() == CLERIC && player->getDeity() == KAMIRA)
		chance += bonus((int)player->piety.getCur())*4;
	else
		chance += bonus((int)player->dexterity.getCur())*4;

	chance -= crtAwareness(target)*2; // Awareness = (int bonus + dex bonus)/2

	// Modify chance based on class of target.
	//*******************************************
	switch(target->getClass()) {
	case THIEF:		// A thief knows their trade and how to protect themself.
		if(pTarget && pTarget->getSecondClass() == MAGE)
			classmod = target->getLevel();
		else
			classmod= 3 * target->getLevel();
		break;
	case ASSASSIN:  // Roguish classes always are aware of their belongings,
	case ROGUE:     // thus they are always more aware.
	case BARD:
		classmod = 2 * target->getLevel();
		break;
	case MAGE:
		if(pTarget && (pTarget->getSecondClass() == THIEF || pTarget->getSecondClass() == ASSASSIN))
			classmod = target->getLevel();
		else
			classmod = 0;
		break;
	case FIGHTER:
		if(pTarget && pTarget->getSecondClass() == THIEF)
			classmod = target->getLevel();
		else
			classmod = 0;
		break;
	case LICH:      // Lichs' unnatural aura messes with thief.
	case PUREBLOOD:   // Vampires, rangers, druids, monks, and werewolves
	case RANGER:    // are always more accutely aware of their surroundings
	case DRUID:     // than any other non-roguish class.
	case WEREWOLF:
	case MONK:
		classmod = target->getLevel();
		break;
	default:
		classmod = 0;
		break;
	}

	chance -= classmod;
	//******************************************

	// If target is higher level, chance is -15% per level higher.
	if(target->getLevel() > level)
		chance -= 15*((int)target->getLevel() - level);

	// Camouflaged player has increased chance to steal.
	if(player->isEffected("camouflage") && !player->flagIsSet(P_HIDDEN))
		chance += 20;

	if(target->isPlayer()) {
		// Blinded players are way easier to steal from.
		if(target->isEffected("blindness"))
			chance += 30;

		// Sucks to be an outlaw and be around thieves!
		if(target->flagIsSet(P_OUTLAW))
			chance += 50;

		// Against PLAYERS, hiding first is a bonus.
		if(player->flagIsSet(P_HIDDEN))
			chance += 10;

		// Playerts fighting mobs are moving around alot.
		if(target->inCombat())
			chance /= 2;

		// Being unconscious is not a good idea around thieves.
		if(target->flagIsSet(P_UNCONSCIOUS))
			chance += 50;

		// Niether is being held magically...
		if(target->isEffected("hold-person"))
			chance += 25;

		// ...or stunned...
		if(target->flagIsSet(P_STUNNED))
			chance += 25;
	}

	// Bulk and weight of object make a difference.
	// The greater value is subtracted from the chance.
	// Dividing by 2 makes this less of a hinderance to game mechanics.
	bulk = object->getActualBulk()/2;
	weight = object->getActualWeight()/2;
	if(weight > bulk)
		chance -= weight/2;
	else
		chance -= bulk/2;

	// Maximum chance to steal ever is 90%
	// There's always a 10% chance to fail.
	chance = MIN(chance, 90);

	// If chance is less than 0, and thief is level 10+,
	// then there is always a 1% chance to succeed
	// against another player.
	if(	target->isPlayer() &&
		level >= 10 &&
		chance < 0
	)
		chance = MAX(1,chance);

	return(chance);
}


//*********************************************************************
//						cmdSteal
//*********************************************************************
// this function allows a player to steal from a monster or another player

int cmdSteal(Player* player, cmd* cmnd) {
	Creature* target=0;
	Monster	*mTarget=0;
	Player* pTarget=0;
	BaseRoom* room = player->getRoomParent();
	Object		*object=0;
	long		i=0, t = time(0);
	int			cantSteal=0;
	int			caught=0, chance=0, roll=0;

	player->clearFlag(P_AFK);

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

	if(!player->knowsSkill("steal")) {
		player->print("You lack the skills to steal from someone without being caught.\n");
		return(0);
	}

	if(!player->isCt()) {
		// Cannot steal if sitting.
		if(player->flagIsSet(P_SITTING)) {
			player->print("You have to stand up first.\n");
			return(0);
		}
		if(player->isEffected("mist")) {
			player->print("You must be in corporeal form to steal!\n");
			return(0);
		}

		// Cannot steal if stunned. Keeps people from stealing during
		// pkill combat while being stunned, which is stupid.
		if(player->flagIsSet(P_STUNNED)) {
			player->print("You are stunned! How can you do that?\n");
			return(0);
		}

		if(player->isBlind()) {
			player->printColor("^CHow can you steal when you are blind?\n");
			return(0);
		}

		// Thief cannot steal from players if they are fighting a non-pet monster
		if(player->inCombat()) {
			player->print("You're fighting! You have no time for that now!\n");
			return(0);
		}

		if(player->checkHeavyRestrict("steal"))
			return(0);
	}

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

	if(cmnd->num < 3) {
		player->print("Steal what from whom?\n");
		return(0);
	}



	if(!player->isCt()) {

		i = LT(player, LT_STEAL);
		if(t < i) {
			player->pleaseWait(i-t);
			return(0);
		}

		// Steal must wait for both the spell and attack timers
		//********************************************************
		i = LT(player, LT_SPELL);
		if(t < i) {
			player->pleaseWait(i-t);
			return(0);
		}

		if(!player->checkAttackTimer())
			return(0);
		//********************************************************

		// Lower dex means bad bonus, means more waiting between steals.
		player->lasttime[LT_STEAL].ltime = t;
		player->lasttime[LT_STEAL].interval = 9 - bonus((int)player->dexterity.getCur());

		player->updateAttackTimer(true, 10);
	}



	target = room->findCreature(player, cmnd, 2);

	if(!target || target == player) {
		player->print("That creature is not here.\n");
		return(0);
	}

	mTarget = target->getAsMonster();
	pTarget = target->getAsPlayer();

	if(pTarget) {
		// Cannot steal from those they are dueling with.
		if(induel(player, pTarget)) {
			player->print("You cannot steal from people you are dueling.\n");
			return(0);
		}

		// Staff cannot be stolen from by players. Ever.
		if(pTarget->isStaff() && !player->isDm()) {
			player->print("Stealing from an immortal is not a good thing for your health.\n");
			pTarget->print("%s tried to steal stuff from you.\n", player->getCName());
			return(0);
		}

		// Impossible to steal from an incorpreal mist.
		if(	pTarget->isEffected("mist") &&
			!player->checkStaff("You cannot steal from a misted vampire.\n") )
			return(0);

		// Stealing out of bags will be re-written later.
		/*
			if((player->isCt() || player->getClass() == THIEF) && cmnd->num > 3) {
				return(steal_bag, cmnd);
			} */

	} else {

		// Cannot steal from a mob while fighting it.
		if(mTarget->isEnemy(player)) {
			player->print("Not while %s's attacking you.\n", mTarget->heShe());
			return(0);
		}

	}

	if(!player->canAttack(target, true))
		return(0);

	// Check for stealing gold
	if(cmnd->str[1][0] == '$') {
		// Stealing gold makes steal timer set to 30 seconds.
		player->lasttime[LT_STEAL].interval = 30L;
		player->clearFlag(P_NO_PKILL);
		steal_gold(player, target);
		return(0);
	}

	object = target->findObject(player, cmnd, 1);
	if(!object) {
		player->print("%s doesn't have that.\n", target->upHeShe());
		return(0);
	}

	// Check if object stolen would be too heavy.
	if(player->getWeight() + object->getActualWeight() > player->maxWeight()) {
		player->print("You wouldn't be able to carry that.\n");
		return(0);
	}

	// Check if object stolen would be too bulky.
	if(player->tooBulky(object->getActualBulk())) {
		player->print("That would be too bulky.\n");
		return(0);
	}

	if(Unique::is(object) && !Unique::canGet(player, object, true)) {
		if(Unique::isUnique(object))
			player->print("You currently cannot steal that limited object.\n");
		else
			player->print("You currently cannot steal that item as it contains a limited object you cannot currently have.\n");
		return(0);
	}

	chance = get_steal_chance(player, target, object);

	// Offensive action by player clears their pkill protection flag,
	// but only if stealing from another player.
	if(!mTarget)
		player->clearFlag(P_NO_PKILL);

	roll = mrand(1,100);
	player->statistics.attemptSteal();

	// A roll of 100 always fails
	if(!cantSteal && ((roll <= chance && roll < 100) || player->isCt()) ) {
		player->print("You succeeded.\n");
		player->checkImprove("steal", true);
		player->statistics.steal();

		// Checks for quest, unique, nosteal, and no-drop items in bags.
		// Removes them from the bag and into target's inventory.
		object->popBag(target);

		log_immort(false, player, "%s stole %s from %s.\n",
			player->getCName(), object->getCName(), target->getCName());

		logn("log.steal", "%s(L%d) stole %s from %s(L%d) in room %s.\n",
		     player->getCName(), player->getLevel(), object->getCName(), target->getCName(), target->getLevel(),
		     room->fullName().c_str());

		// Other people in the room will possibly notice what's going on.
		for(Player* bystander : room->players ) {

			// We only want to test bystanders.
			if(bystander == player || bystander == target)
				continue;

			// If player is in combat, they're too busy to notice.
			if(bystander->inCombat())
				continue;

			// Blind players will not notice.
			if(bystander->isEffected("blindness"))
				continue;

			// Staff who are DM-Invis and steal will not be noticed, other
			// than by those on the staff higher than they are.
			if(player->flagIsSet(P_DM_INVIS) && (bystander->getClass() < player->getClass()))
				continue;

			// Thieves cannot mist, but putting this here for completeness.
			if(player->isEffected("mist") && !bystander->isEffected("true-sight"))
				continue;

			// If thief is invisible, only those with d-i will possibly see.
			if(player->isInvisible() && !bystander->isEffected("detect-invisible"))
				continue;



			roll = mrand(1,100);
			// adjust roll to reflect chance for new observer.
			//***************************************************
			roll += crtAwareness(target)*2;
			roll -= crtAwareness(bystander)*2;
			roll = MAX(1,roll);
			//***************************************************

			caught = 100 - chance;
			caught = MAX(1,MIN(50,caught));
			if(roll <= caught || isCt(bystander)) {
				// If roll is less than 10% of chance, bystander will see
				// what was trying to be stolen.
				if(roll <= chance/10 || isCt(bystander))
					bystander->printColor("%M tried to steal %1P from %N.\n", player, object, target);
				else
					bystander->print("%M tried to steal something from %N.\n", player, target);
			}
		}

		// Assisting monsters will now all watch one another's backs against stealing.
		// If thief steals from one, others in room might notice and attack.
		if(mTarget && !player->flagIsSet(P_DM_INVIS)) {
		    for(Monster* assist : room->monsters) {

				// Victim doesn't assist itself
				if(!assist || assist == mTarget)
					continue;

				// If mob is blind it won't assist.
				if(assist->isEffected("blindness"))
					continue;

				// If assisting mob has an enemy and that enemy is NOT the
				// thief, they will not be watching because they are distracted.
				if(assist->hasEnemy() && !assist->getAsMonster()->isEnemy(player))
					continue;

				// If assisting monster can't see the thief,
				// then it will not catch the thief stealing.
				if(!assist->canSee(player))
					continue;

				// If monster doesn't assist the victim, it doesn't care.
				if(	!mTarget->willAssist(assist) &&
					!(mTarget->flagIsSet(M_WILL_BE_ASSISTED) && assist->flagIsSet(M_WILL_ASSIST))
				)
					continue;

				roll = mrand(1,100);
				// adjust roll to reflect chance for new observer.
				//**************************************************
				roll += crtAwareness(target)*2;
				roll -= crtAwareness(assist)*2;
				roll = MAX(1,roll);
				//***************************************************
				caught = 100 - chance;
				caught = MAX(1,MIN(50,caught));
				if(roll > caught && !player->isCt()) {
					player->printColor("^r%M notices you in the act and attacks!\n", assist);
					broadcast(player->getSock(), room, "^r%M catches %N stealing and attacks!", assist, player);
					assist->getAsMonster()->addEnemy(player);
				}
			}
		}

		if(target->isMonster() && object->flagIsSet(O_JUST_LOADED)) {
			// We're stealing from a monster, and the object was just loaded:
			// ie it hasn't been stolen, given, or taken then we can get some
			// experience for stealing this item
			long expGain = target->getExperience() / 10;
			object->setDroppedBy(target, "Theft");
			expGain = MAX(expGain, 1);
			if(!player->halftolevel()) {
				player->printColor("You %s ^Y%d^x experience for the theft of %P.\n", gConfig->isAprilFools() ? "lose" : "gain", expGain, object);
				player->addExperience(expGain);
			}
		}

		// BUGFIX:  Now stealing gold items (ie: pot of gold) add to your coins instead of your inventory
		// Moved here to avoid referecing object after calling doGetObject
		// Correctly handle quest items, objects and such
		// NOTE: Object MAY be undefined after this function, don't reference it any longer
		target->delObj(object);
		Limited::transferOwner(target->getAsPlayer(), player, object);
		doGetObject(object, player, false);

	} else {

		player->print("You failed.\n");
		player->checkImprove("steal", false);
		player->smashInvis();

		broadcast(player->getSock(), target->getSock(), room, "%M tried to steal from %N.", player, target);

		if(!mTarget) {
			if(!player->isEffected("blindness"))
				target->printColor("%M tried to steal %1P from you.\n", player, object);
			else
				target->print("Someone tried to steal something from you.\n");
		} else {
			player->print("You are attacked!\n");
			broadcast(player->getSock(), room, "%M attacks %N!", mTarget, player);
			mTarget->addEnemy(player);
		}
	}

	return(0);
}