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/
/*
 * fishing.cpp
 *	 Fishing
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * 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 "area.h"
#include "unique.h"
#include "calendar.h"

//**********************************************************************
//						canFish
//**********************************************************************

bool canFish(const Player* player, const Fishing** list, Object** pole) {
	*pole = player->ready[HELD-1];

	if(!player->isStaff()) {
		if(!player->ableToDoCommand())
			return(false);

		if(player->isBlind()) {
			player->printColor("^CYou can't do that! You're blind!\n");
			return(false);
		}
		if(player->inCombat()) {
			player->printColor("You are too busy to do that now!\n");
			return(false);
		}

		if(player->isEffected("mist")) {
			player->printColor("You must be in corporeal form to work with items.\n");
			return(false);
		}
		if(player->isEffected("berserk")) {
			player->print("You are too angry to go fishing!\n");
			return(0);
		}

		if(!*pole || !(*pole)->flagIsSet(O_FISHING)) {
			player->printColor("You need a fishing pole to go fishing.\n");
			return(false);
		}
	}

	if(player->inAreaRoom())
		*list = player->getConstAreaRoomParent()->getFishing();
	else if(player->inUniqueRoom())
		*list = player->getConstUniqueRoomParent()->getFishing();

	if(!*list || (*list)->empty()) {
		player->printColor("You can't go fishing here!\n");
		return(false);
	}

	return(true);
}

//**********************************************************************
//						failFishing
//**********************************************************************

bool failFishing(Player* player, bstring adminMsg, bool almost=true) {
	if(almost) {
		switch(mrand(0,1)) {
		case 1:
			player->print("You almost caught a fish, but it got away.\n");
			break;
		default:
			player->print("You almost caught something, but it got away.\n");
			break;
		}
	} else {
		switch(mrand(0,1)) {
		case 1:
			player->print("You failed to catch anything.\n");
			break;
		default:
			player->print("You failed to catch any fish.\n");
			break;
		}
	}
	if(player->isCt())
		player->checkStaff("%s\n", adminMsg.c_str());
	// don't improve if they almost caught something
	if(!almost)
		player->checkImprove("fishing", false);
	return(false);
}

//**********************************************************************
//						doFish
//**********************************************************************

bool hearMobAggro(Socket* sock);

bool doFish(Player* player) {
	const Fishing* list=0;
	const FishingItem* item=0;
	Monster* monster=0;
	Object* pole=0, *fish=0;
	double chance=0.0, comp=0.0, skill=0.0, quality=0.0;
	bool day = isDay();

	player->unhide();

	if(!canFish(player, &list, &pole))
		return(false);

	if(!player->knowsSkill("fishing"))
		player->addSkill("fishing", 1);

	// did they break the fishing pole?
	if(pole) {
		if(!mrand(0, 3))
			pole->decShotsCur();
		if(pole->getShotsCur() < 1) {
			player->breakObject(pole, HELD);
			return(false);
		}
	}

	skill = player->getSkillLevel("fishing");
	comp = skill * 100 / 40;  // turn from 1-40 to %
	comp = comp * 2 / 3;  // skill accounts for 2/3 of your success
	chance += comp;

	quality = pole ? pole->getQuality() : 0;
	comp = quality * 10 / 40;  // turn from 1-400 to %
	comp /= 3;  // the fishing pole accounts for 1/3 of your success
	chance += comp;

	chance = MAX(10, MIN(95, chance));

	if(mrand(1,100) > chance)
		return(failFishing(player, "Dice roll.", false));

	item = list->getItem((short)skill, (short)quality);

	if(!item)
		return(failFishing(player, "No FishingItem found.", false));
	if(list->id == "river" && player->getRoomParent()->isWinter())
		return(failFishing(player, "Winter."));
	if(day && item->isNightOnly())
		return(failFishing(player, "Night-time only.", false));
	if(!day && item->isDayOnly())
		return(failFishing(player, "Day-time only.", false));

	if(!item->isMonster()) {
		// most fish they get will be objects
		if(!loadObject(item->getFish(), &fish))
			return(failFishing(player, "Object failed to load.", false));
		if(!Unique::canGet(player, fish)) {
			delete fish;
			return(failFishing(player, "Cannot have unique item."));
		}
		if(!Lore::canHave(player, fish, false)) {
			delete fish;
			return(failFishing(player, "Cannot have lore item (inventory)."));
		}
		if(!Lore::canHave(player, fish)) {
			delete fish;
			return(failFishing(player, "Cannot have lore item (bag)."));
		}
		if(player->getLevel() < fish->getLevel() && fish->getQuestnum() > 0) {
			delete fish;
			return(failFishing(player, "Too low level."));
		}
		if((player->getWeight() + fish->getActualWeight()) > player->maxWeight()) {
			delete fish;
			return(failFishing(player, "Too heavy."));
		}
		if(player->tooBulky(fish->getActualBulk())) {
			delete fish;
			return(failFishing(player, "Too bulky."));
		}
		if(fish->getQuestnum() && player->questIsSet(fish->getQuestnum()-1)) {
			delete fish;
			return(failFishing(player, "Already completed the quest."));
		}
	} else {
		// some fish they get will be monsters
		if(!loadMonster(item->getFish(), &monster))
			return(failFishing(player, "Monster failed to load.", false));
		if(player->getRoomParent()->countCrt() + 1 >= player->getRoomParent()->getMaxMobs()) {
			delete monster;
			return(failFishing(player, "Room too full.", false));
		}
		// TODO: do this so the print functions work properly
		monster->setParent(player->getParent());
//		monster->parent_rom = player->parent_rom;
//		monster->area_room = player->area_room;
	}

	// they caught something!

	if(!item->isMonster()) {
		if(item->getExp()) {
			if(!player->halftolevel()) {
				player->printColor("You %s %d experience for catching %1P!\n", gConfig->isAprilFools() ? "lose" : "gain", item->getExp(), fish);
				player->addExperience(item->getExp());
			}
		} else {
			player->printColor("You catch %1P!\n", fish);
		}
		broadcast(player->getSock(), player->getParent(), "%M catches something!", player);
	} else {
		if(item->getExp()) {
			if(!player->halftolevel()) {
				player->printColor("You %s %d experience for catching %1N!\n", gConfig->isAprilFools() ? "lose" : "gain", item->getExp(), monster);
				player->addExperience(item->getExp());
			}
		} else {
			player->printColor("You catch %1N!\n", monster);
		}
		broadcast(player->getSock(), player->getParent(), "%M catches %1N!", player, monster);
	}

	player->statistics.fish();
	player->checkImprove("fishing", true);

	if(!item->isMonster()) {
		doGetObject(fish, player);
	} else {
		monster->addToRoom(player->getRoomParent(), 1);

		// most fish will be angry about this
		if(item->willAggro()) {
			// don't let them swing right away
			monster->updateAttackTimer(true, DEFAULT_WEAPON_DELAY);
			monster->addEnemy(player, true);

			broadcast(hearMobAggro, "^y*** %s(R:%s) added %s to %s attack list (fishing aggro).",
				monster->getCName(), player->getRoomParent()->fullName().c_str(), player->getCName(), monster->hisHer());
		}
	}
	return(true);
}

void doFish(const DelayedAction* action) {
	doFish(action->target->getAsPlayer());
}

//**********************************************************************
//						cmdFish
//**********************************************************************

int cmdFish(Player* player, cmd* cmnd) {
	const Fishing* list=0;
	Object* pole=0;

	player->unhide();

	if(gServer->hasAction(player, ActionFish)) {
		player->print("You are already fishing!\n");
		return(0);
	}

	if(!canFish(player, &list, &pole))
		return(0);

	player->interruptDelayedActions();
	gServer->addDelayedAction(doFish, player, 0, ActionFish, 10 - (int)(player->getSkillLevel("fishing") / 10) - mrand(0,3));

	player->print("You begin fishing.\n");
	broadcast(player->getSock(), player->getParent(), "%M begins fishing.", player);
	return(0);
}

//*********************************************************************
//						FishingItem
//*********************************************************************

FishingItem::FishingItem() {
	dayOnly = nightOnly = monster = aggro = false;
	weight = minQuality = minSkill = exp = 0;
}

//*********************************************************************
//						getFish
//*********************************************************************

CatRef FishingItem::getFish() const { return(fish); }

//*********************************************************************
//						isDayOnly
//*********************************************************************

bool FishingItem::isDayOnly() const { return(dayOnly); }

//*********************************************************************
//						isNightOnly
//*********************************************************************

bool FishingItem::isNightOnly() const { return(nightOnly); }

//*********************************************************************
//						getWeight
//*********************************************************************

short FishingItem::getWeight() const { return(weight); }

//*********************************************************************
//						getMinQuality
//*********************************************************************

short FishingItem::getMinQuality() const { return(minQuality); }

//*********************************************************************
//						getMinSkill
//*********************************************************************

short FishingItem::getMinSkill() const { return(minSkill); }

//*********************************************************************
//						getExp
//*********************************************************************

long FishingItem::getExp() const { return(exp); }

//*********************************************************************
//						isMonster
//*********************************************************************

bool FishingItem::isMonster() const { return(monster); }

//*********************************************************************
//						willAggro
//*********************************************************************

bool FishingItem::willAggro() const { return(aggro); }

//*********************************************************************
//						Fishing
//*********************************************************************

Fishing::Fishing() {
	id = "";
}

//*********************************************************************
//						empty
//*********************************************************************

bool Fishing::empty() const {
	return(items.empty());
}

//*********************************************************************
//						getItem
//*********************************************************************

const FishingItem* Fishing::getItem(short skill, short quality) const {
	int total=0, pick=0;
	std::list<FishingItem>::const_iterator it;
	bool day = isDay();

	for(it = items.begin() ; it != items.end() ; it++) {
		if(skill < (*it).getMinSkill() || quality < (*it).getMinQuality())
			continue;
		if((day && (*it).isNightOnly()) || (!day && (*it).isDayOnly()))
			continue;
		total += (*it).getWeight();
	}

	// nothing to catch?
	if(!total)
		return(0);

	// which item to pick?
	pick = mrand(1, total);
	total = 0;

	for(it = items.begin() ; it != items.end() ; it++) {
		if(skill < (*it).getMinSkill() || quality < (*it).getMinQuality())
			continue;
		if((day && (*it).isNightOnly()) || (!day && (*it).isDayOnly()))
			continue;
		total += (*it).getWeight();
		if(total >= pick)
			return(&(*it));
	}
	return(0);
}

//*********************************************************************
//						doGetFishing
//*********************************************************************

const Fishing* AreaRoom::doGetFishing(short y, short x) const {
	const TileInfo* tile = area->getTile(area->getTerrain(0, &mapmarker, y, x, 0, true), area->getSeasonFlags(&mapmarker));
	const AreaZone* zone=0;
	const Fishing* list=0;
	std::list<AreaZone*>::const_iterator it;

	if(!tile || !tile->isWater())
		return(0);

	// zone comes first
	for(it = area->zones.begin() ; it != area->zones.end() ; it++) {
		zone = (*it);
		if(zone->inside(area, &mapmarker) && zone->getFishing() != "") {
			list = gConfig->getFishing(zone->getFishing());
			if(list)
				return(list);
		}
	}

	// then tile
	if(tile->getFishing() != "") {
		list = gConfig->getFishing(tile->getFishing());
		if(list)
			return(list);
	}

	// then catrefinfo
	const CatRefInfo* cri = gConfig->getCatRefInfo(this);
	if(cri && cri->getFishing() != "") {
		list = gConfig->getFishing(cri->getFishing());
		if(list)
			return(list);
	}

	return(0);
}

//*********************************************************************
//						getFishing
//*********************************************************************

const Fishing* AreaRoom::getFishing() const {
	short y=0,x=0;
	const Fishing* list = doGetFishing(0, 0);
	if(list)
		return(list);

	for(y=-1; y<=1; y++) {
		for(x=-1; x<=1; x++) {
			// we check 0,0 in the beginning
			if(!x && !y)
				continue;
			list = doGetFishing(y, x);
			if(list)
				return(list);
		}
	}

	return(0);
}

const Fishing* UniqueRoom::getFishing() const {
	const Fishing *list=0;

	// room fish list comes first
	if(fishing != "") {
		list = gConfig->getFishing(fishing);
		if(list)
			return(list);
	}

	// then catrefinfo
	const CatRefInfo* cri = gConfig->getCatRefInfo(this);
	if(cri && cri->getFishing() != "") {
		list = gConfig->getFishing(cri->getFishing());
		if(list)
			return(list);
	}

	return(0);
}

bstring AreaZone::getFishing() const { return(fishing); }
bstring TileInfo::getFishing() const { return(fishing); }
bstring CatRefInfo::getFishing() const { return(fishing); }
bstring UniqueRoom::getFishingStr() const { return(fishing); }

//*********************************************************************
//						setFishing
//*********************************************************************

void UniqueRoom::setFishing(bstring id) { fishing = id; }

//*********************************************************************
//						loadFishing
//*********************************************************************

bool Config::loadFishing() {
	xmlDocPtr xmlDoc;
	xmlNodePtr curNode;

	char filename[80];
	snprintf(filename, 80, "%s/fishing.xml", Path::Game);
	xmlDoc = xml::loadFile(filename, "Fishing");
	if(xmlDoc == NULL)
		return(false);

	curNode = xmlDocGetRootElement(xmlDoc);

	curNode = curNode->children;
	while(curNode && xmlIsBlankNode(curNode))
		curNode = curNode->next;

	if(curNode == 0) {
		xmlFreeDoc(xmlDoc);
		return(false);
	}

	clearFishing();
	bstring id = "";
	while(curNode != NULL) {
		if(NODE_NAME(curNode, "List")) {
			xml::copyPropToBString(id, curNode, "id");
			if(id != "") {
				Fishing list;
				list.load(curNode);
				list.id = id;
				fishing[id] = list;
			}
		}
		curNode = curNode->next;
	}

	xmlFreeDoc(xmlDoc);
	xmlCleanupParser();
	return(true);
}

//*********************************************************************
//						load
//*********************************************************************

void Fishing::load(xmlNodePtr rootNode) {
	xmlNodePtr curNode = rootNode->children;
	xml::copyPropToBString(id, rootNode, "id");

	while(curNode) {
		if(NODE_NAME(curNode, "Item")) {
			FishingItem item;
			item.load(curNode);
			items.push_back(item);
		}
		curNode = curNode->next;
	}
}

void FishingItem::load(xmlNodePtr rootNode) {
	xmlNodePtr curNode = rootNode->children;

	while(curNode) {
			 if(NODE_NAME(curNode, "Fish")) fish.load(curNode);
		else if(NODE_NAME(curNode, "DayOnly")) dayOnly = true;
		else if(NODE_NAME(curNode, "NightOnly")) nightOnly = true;
		else if(NODE_NAME(curNode, "Weight")) xml::copyToNum(weight, curNode);
		else if(NODE_NAME(curNode, "Experience")) xml::copyToNum(exp, curNode);
		else if(NODE_NAME(curNode, "MinQuality")) xml::copyToNum(minQuality, curNode);
		else if(NODE_NAME(curNode, "MinSkill")) xml::copyToNum(minSkill, curNode);
		else if(NODE_NAME(curNode, "Monster")) monster = true;
		else if(NODE_NAME(curNode, "Aggro")) aggro = true;
		curNode = curNode->next;
	}
}

//*********************************************************************
//						clearFishing
//*********************************************************************

void Config::clearFishing() {
	fishing.clear();
}

//*********************************************************************
//						getFishing
//*********************************************************************

const Fishing *Config::getFishing(bstring id) const {
	std::map<bstring, Fishing>::const_iterator it = fishing.find(id);

	if(it == fishing.end())
		return(0);

	return(&(*it).second);
}

//*********************************************************************
//						dmFishing
//*********************************************************************

int dmFishing(Player* player, cmd* cmnd) {
	std::map<bstring, Fishing>::const_iterator it;
	std::list<FishingItem>::const_iterator ft;
	std::ostringstream oStr;
	const Fishing *list=0;
	const FishingItem *item=0;
	Object* fish=0;
	Monster* monster=0;
	bstring name="";
	bool all = !strcmp(cmnd->str[1], "all");

	oStr.setf(std::ios::left, std::ios::adjustfield);
	oStr << "^yFishing Information:  Type ^y*fishing all^x to view all information.^x\n";

	for(it = gConfig->fishing.begin() ; it != gConfig->fishing.end() ; it++) {
		list = &(*it).second;
		oStr << "ID: ^C" << list->id << "^x\n"
			 << "    Items:\n";

		for(ft = list->items.begin() ; ft != list->items.end() ; ft++) {
			item = &(*ft);

			oStr << "      ^c" << item->getFish().str();


			name = "";
			if(!item->isMonster()) {
				// are they catching an object?
				if(loadObject(item->getFish(), &fish)) {
					name = fish->getName();
					delete fish;
				}
			} else {
				// or a monster?
				if(loadObject(item->getFish(), &fish)) {
					name = monster->getName();
					delete monster;
				}
			}


			if(name != "")
				oStr << "^x, ^c" << name;
			oStr << "^x\n";

			if(all) {
				oStr << "        Day Only? " << (item->isDayOnly() ? "^gYes" : "^rNo")
					 << "^x Night Only? " << (item->isNightOnly() ? "^gYes" : "^rNo") << "^x\n"
					 << "        Weight: ^c" << item->getWeight() << "^x Exp: ^c" << item->getExp() << "^x\n"
					 << "        MinQuality: ^c" << item->getMinQuality() << "^x ^cMinSkill: " << item->getMinSkill() << "^x\n";
			}
		}
	}

	player->printColor("%s\n", oStr.str().c_str());
	return(0);
}