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/
/*
 * raceData.cpp
 *	 Race data storage file
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * 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"


//*********************************************************************
//						raceCount
//*********************************************************************

unsigned short Config::raceCount() {
	return(races.size());
}

//*********************************************************************
//						getPlayableRaceCount
//*********************************************************************

unsigned short Config::getPlayableRaceCount() {
	RaceData* curRace;
	int numPlayableRaces = 0;
	for(std::pair<int, RaceData*> rp : races) {
		curRace = rp.second;
		if(!curRace) continue;

		if(curRace->isPlayable())
			numPlayableRaces++;
	}
	return(numPlayableRaces);
}

//*********************************************************************
//						RaceData
//*********************************************************************

RaceData::RaceData(xmlNodePtr rootNode) {
	Sex		sex;
	Dice	dice;
	char	sizeTxt[20];
	int		i=0;
	short	n=0;
	bstring str;

	id = 0;
	name = adjective = abbr = "";
	infra = bonus = 0;
	startAge = parentRace = porphyriaResistance = 0;
	size = NO_SIZE;
	isParentRace = false;
	playable = gendered = true;
	zero(stats, sizeof(stats));
	zero(saves, sizeof(saves));
	zero(classes, sizeof(classes));

	zero(multiClerDeities, sizeof(multiClerDeities));
	zero(clerDeities, sizeof(clerDeities));
	zero(palDeities, sizeof(palDeities));
	zero(dkDeities, sizeof(dkDeities));

	id = xml::getIntProp(rootNode, "id");
	xml::copyPropToBString(name, rootNode, "name");

	xmlNodePtr subNode, childNode, curNode = rootNode->children;

	while(curNode) {
			 if(NODE_NAME(curNode, "Adjective")) xml::copyToBString(adjective, curNode);
		else if(NODE_NAME(curNode, "Abbr")) xml::copyToBString(abbr, curNode);
		else if(NODE_NAME(curNode, "NotPlayable")) playable = false;
		else if(NODE_NAME(curNode, "ParentRace")) {
			xml::copyToNum(parentRace, curNode);
			if(gConfig->races.find(parentRace) != gConfig->races.end())
				gConfig->races[parentRace]->makeParent();
		}
		else if(NODE_NAME(curNode, "Data")) {
			childNode = curNode->children;
			while(childNode) {
				if(NODE_NAME(childNode, "Size")) {
					xml::copyToCString(sizeTxt, childNode);
					size = ::getSize(sizeTxt);
				}
				else if(NODE_NAME(childNode, "BaseHeight")) {
					sex = (Sex)xml::getIntProp(childNode, "Sex");
					xml::copyToNum(n, childNode);
					baseHeight[sex] = n;
				}
				else if(NODE_NAME(childNode, "BaseWeight")) {
					sex = (Sex)xml::getIntProp(childNode, "Sex");
					xml::copyToNum(n, childNode);
					baseWeight[sex] = n;
				}
				else if(NODE_NAME(childNode, "VarHeight")) {
					sex = (Sex)xml::getIntProp(childNode, "Sex");
					dice.load(childNode);
					varHeight[sex] = dice;
				}
				else if(NODE_NAME(childNode, "VarWeight")) {
					sex = (Sex)xml::getIntProp(childNode, "Sex");
					dice.load(childNode);
					varWeight[sex] = dice;
				}
				else if(NODE_NAME(childNode, "Gendered")) xml::copyToBool(gendered, childNode);
				else if(NODE_NAME(childNode, "BonusStat")) {
					xml::copyToNum(i, childNode);
					if(i)
						bonus = true;
				}
				else if(NODE_NAME(childNode, "StartAge")) xml::copyToNum(startAge, childNode);
				else if(NODE_NAME(childNode, "PorphyriaResistance")) xml::copyToNum(porphyriaResistance, childNode);
				else if(NODE_NAME(childNode, "Infravision")) xml::copyToBool(infra, childNode);
				else if(NODE_NAME(childNode, "Stats")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "Stat")) {
							xml::copyPropToBString(str, subNode, "id");
							xml::copyToNum(stats[gConfig->stattoNum(str)], subNode);
						}
						subNode = subNode->next;
					}
				}
				else if(NODE_NAME(childNode, "Classes")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "Class")) {
							xml::copyPropToBString(str, subNode, "id");
							classes[gConfig->classtoNum(str)] = true;
						}
						subNode = subNode->next;
					}
				}
				else if(NODE_NAME(childNode, "MultiClericDeities")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "Deity")) {
							xml::copyPropToBString(str, subNode, "id");
							multiClerDeities[gConfig->deitytoNum(str)] = true;
						}
						subNode = subNode->next;
					}
				}
				else if(NODE_NAME(childNode, "ClericDeities")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "Deity")) {
							xml::copyPropToBString(str, subNode, "id");
							clerDeities[gConfig->deitytoNum(str)] = true;
						}
						subNode = subNode->next;
					}
				}
				else if(NODE_NAME(childNode, "PaladinDeities")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "Deity")) {
							xml::copyPropToBString(str, subNode, "id");
							palDeities[gConfig->deitytoNum(str)] = true;
						}
						subNode = subNode->next;
					}
				}
				else if(NODE_NAME(childNode, "DeathknightDeities")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "Deity")) {
							xml::copyPropToBString(str, subNode, "id");
							dkDeities[gConfig->deitytoNum(str)] = true;
						}
						subNode = subNode->next;
					}
				}
				else if(NODE_NAME(childNode, "SavingThrows")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "SavingThrow")) {
							xml::copyPropToBString(str, subNode, "id");
							xml::copyToNum(saves[gConfig->savetoNum(str)], subNode);
						}
						subNode = subNode->next;
					}
				}
				else if(NODE_NAME(childNode, "Effects")) {
					subNode = childNode->children;
					while(subNode) {
						if(NODE_NAME(subNode, "Effect")) {
							xml::copyToBString(str, subNode);
							effects.push_back(str);
						}
						subNode = subNode->next;
					}
				} else if(NODE_NAME(childNode, "Skills")) {
					xmlNodePtr skillNode = childNode->children;
					while(skillNode) {
						if(NODE_NAME(skillNode, "Skill")) {
							SkillGain* skillGain = new SkillGain(skillNode);
							baseSkills.push_back(skillGain);
						}
						skillNode = skillNode->next;
					}
				}
				childNode = childNode->next;
			}
		}
		curNode = curNode->next;
	}
}
RaceData::~RaceData() {
    for(SkillGain* skGain : baseSkills) {
        delete skGain;
    }
    baseSkills.clear();
}
//*********************************************************************
//						stattoNum
//*********************************************************************

int Config::stattoNum(bstring str) {
	if(str == "Str") return(STR);
	if(str == "Con") return(CON);
	if(str == "Dex") return(DEX);
	if(str == "Int") return(INT);
	if(str == "Pty") return(PTY);
	return(0);
}

//*********************************************************************
//						savetoNum
//*********************************************************************

int Config::savetoNum(bstring str) {
	if(str == "Poi") return(POI);
	if(str == "Dea") return(DEA);
	if(str == "Bre") return(BRE);
	if(str == "Men") return(MEN);
	if(str == "Spl") return(SPL);
	return(0);
}

//*********************************************************************
//						classtoNum
//*********************************************************************

int Config::classtoNum(bstring str) {
	if(str == "Assassin") return(ASSASSIN);
	if(str == "Berserker") return(BERSERKER);
	if(str == "Cleric") return(CLERIC);
	if(str == "Fighter") return(FIGHTER);
	if(str == "Mage") return(MAGE);
	if(str == "Paladin") return(PALADIN);
	if(str == "Ranger") return(RANGER);
	if(str == "Thief") return(THIEF);
	if(str == "Pureblood") return(PUREBLOOD);
	if(str == "Monk") return(MONK);
	if(str == "Deathknight") return(DEATHKNIGHT);
	if(str == "Druid") return(DRUID);
	if(str == "Lich") return(LICH);
	if(str == "Werewolf") return(WEREWOLF);
	if(str == "Bard") return(BARD);
	if(str == "Rogue") return(ROGUE);
	if(str == "Fighter/Mage") return(MULTI_BASE+0);
	if(str == "Fighter/Thief") return(MULTI_BASE+1);
	if(str == "Cleric/Assassin") return(MULTI_BASE+2);
	if(str == "Mage/Thief") return(MULTI_BASE+3);
	if(str == "Thief/Mage") return(MULTI_BASE+4);
	if(str == "Cleric/Fighter") return(MULTI_BASE+5);
	if(str == "Mage/Assassin") return(MULTI_BASE+6);
	return(0);
}

//*********************************************************************
//						racetoNum
//*********************************************************************

int Config::racetoNum(bstring str) {
	for(unsigned i=0; i<races.size(); i++) {
		if(!races[i])
			continue;
		if(str == races[i]->getName() || str == races[i]->getAdjective())
			return(i);
	}
	return(-1);
}

//*********************************************************************
//						deitytoNum
//*********************************************************************

int Config::deitytoNum(bstring str) {
	for(unsigned i=0; i<deities.size(); i++) {
		if(!deities[i])
			continue;
		if(str == deities[i]->getName())
			return(i);
	}
	return(-1);
}

//**********************************************************************
//						loadRaces
//**********************************************************************

bool Config::loadRaces() {
	xmlDocPtr xmlDoc;
	xmlNodePtr curNode;
	int		i=0;

	char filename[80];
	snprintf(filename, 80, "%s/races.xml", Path::Game);
	xmlDoc = xml::loadFile(filename, "Races");

	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);
	}

	clearRaces();
	while(curNode != NULL) {
		if(NODE_NAME(curNode, "Race")) {
			i = xml::getIntProp(curNode, "id");

			if(races.find(i) == races.end())
				races[i] = new RaceData(curNode);
		}
		curNode = curNode->next;
	}
	xmlFreeDoc(xmlDoc);
	xmlCleanupParser();
	return(true);
}


//**********************************************************************
//						getRace
//**********************************************************************

const RaceData* Config::getRace(bstring race) const {
	std::map<int, RaceData*>::const_iterator it;
	RaceData* data=0;
	int len = race.getLength();
	race = race.toLower();

	for(it = races.begin() ; it != races.end() ; it++) {
		// exact match
		if((*it).second->getName().toLower() == race)
			return((*it).second);
		if((*it).second->getName().substr(0, len) == race) {
			// not unique
			if(data)
				return(0);
			data = (*it).second;
		}
	}

	return(data);
}

//**********************************************************************
//						getRace
//**********************************************************************

const RaceData* Config::getRace(int id) const {
	std::map<int, RaceData*>::const_iterator it = races.find(id);

	if(it == races.end())
		it = races.begin();

	return((*it).second);
}

//*********************************************************************
//						dmShowRaces
//*********************************************************************

int dmShowRaces(Player* player, cmd* cmnd) {
	std::map<int, RaceData*>::const_iterator it;
	RaceData* data=0;
	bool	all = player->isDm() && cmnd->num > 1 && !strcmp(cmnd->str[1], "all");
	std::map<Sex, short>::const_iterator bIt;
	std::map<Sex, Dice>::const_iterator dIt;

	player->printColor("Displaying Races:%s\n",
		player->isDm() && !all ? "  Type ^y*racelist all^x to view all information." : "");
	for(it = gConfig->races.begin() ; it != gConfig->races.end() ; it++) {
		data = (*it).second;
		player->printColor("Id: ^c%-2d^x   Name: ^c%s\n", data->getId(), data->getName().c_str());
		if(all) {
			if(data->bonusStat())
				player->printColor("   ^mThis race picks a bonus and a penalty stat.\n");
			else
				player->printColor("   Str: ^m%-2d^x  Dex: ^m%-2d^x  Con: ^m%-2d^x  Int: ^m%-2d^x  Pty: ^m%-2d^x\n",
					data->getStatAdj(STR), data->getStatAdj(DEX), data->getStatAdj(CON),
					data->getStatAdj(INT), data->getStatAdj(PTY));
			player->printColor("   Infravision: %-3s^x  Size: %s  isParent: %-3s\n",
				data->hasInfravision() ? "^gYes" : "^rNo",
				getSizeName(data->getSize()).c_str(),
				data->isParent() ? "^gYes" : "^rNo");
			if(data->getParentRace())
				player->print("   Parent Race: %s", gConfig->getRace(data->getParentRace())->getName().c_str());
			if(data->getPorphyriaResistance())
				player->print("   Porphyria Resistance: %d", data->getPorphyriaResistance());
			player->printColor("   Playable: %s\n", data->isPlayable() ? "^gYes" : "^rNo");

			// base weight for all genders
			for(bIt = data->baseHeight.begin() ; bIt != data->baseHeight.end() ; bIt++) {
				player->printColor("   BaseHeight: %s: ^c%d", getSexName((*bIt).first).c_str(), (*bIt).second);
			}
			if(data->baseHeight.size())
				player->print("\n");
			// variable weight for all genders
			for(dIt = data->varHeight.begin() ; dIt != data->varHeight.end() ; dIt++) {
				player->printColor("   VarHeight: %s: ^c%s", getSexName((*dIt).first).c_str(), (*dIt).second.str().c_str());
			}
			if(data->varHeight.size())
				player->print("\n");

			// base height for all genders
			for(bIt = data->baseWeight.begin() ; bIt != data->baseWeight.end() ; bIt++) {
				player->printColor("   BaseWeight: %s: ^c%d", getSexName((*bIt).first).c_str(), (*bIt).second);
			}
			if(data->baseWeight.size())
				player->print("\n");
			// variable height for all genders
			for(dIt = data->varWeight.begin() ; dIt != data->varWeight.end() ; dIt++) {
				player->printColor("   VarWeight: %s: ^c%s", getSexName((*dIt).first).c_str(), (*dIt).second.str().c_str());
			}
			if(data->varWeight.size())
				player->print("\n");
		}
	}
	player->print("\n");
	return(0);
}

//**********************************************************************
//						clearRaces
//**********************************************************************

void Config::clearRaces() {
	std::map<int, RaceData*>::iterator it;

	for(it = races.begin() ; it != races.end() ; it++) {
		RaceData* r = (*it).second;
		delete r;
	}
	races.clear();
}

//*********************************************************************
//						getAdjective
//*********************************************************************

bstring RaceData::getAdjective() const { return(adjective); }

//*********************************************************************
//						getAbbr
//*********************************************************************

bstring RaceData::getAbbr() const { return(abbr); }

//*********************************************************************
//						getParentRace
//*********************************************************************

int RaceData::getParentRace() const { return(parentRace); }

//*********************************************************************
//						getPorphyriaResistance
//*********************************************************************

short RaceData::getPorphyriaResistance() const { return(porphyriaResistance); }

//*********************************************************************
//						makeParent
//*********************************************************************

void RaceData::makeParent() { isParentRace = true; }

//*********************************************************************
//						isParent
//*********************************************************************

bool RaceData::isParent() const { return(isParentRace); }

//*********************************************************************
//						isPlayable
//*********************************************************************

bool RaceData::isPlayable() const { return(playable); }

//*********************************************************************
//						isGendered
//*********************************************************************

bool RaceData::isGendered() const { return(gendered); }

//*********************************************************************
//						hasInfravision
//*********************************************************************

bool RaceData::hasInfravision() const { return(infra); }

//*********************************************************************
//						bonusStat
//*********************************************************************

bool RaceData::bonusStat() const { return(bonus); }

//*********************************************************************
//						getSize
//*********************************************************************

Size RaceData::getSize() const { return(size); }

//*********************************************************************
//						getStartAge
//*********************************************************************

int RaceData::getStartAge() const { return(startAge); }

//*********************************************************************
//						getStatAdj
//*********************************************************************

int RaceData::getStatAdj(int stat) const { return(stats[stat]); }

//*********************************************************************
//						getSave
//*********************************************************************

int RaceData::getSave(int save) const { return(saves[save]); }

//*********************************************************************
//						allowedClass
//*********************************************************************

bool RaceData::allowedClass(int cls) const { return(classes[cls]); }

//*********************************************************************
//						allowedDeity
//*********************************************************************

bool RaceData::allowedDeity(int cls, int cls2, int dty) const {
	return(	(cls == CLERIC && !cls2 && allowedClericDeity(dty)) ||
			(cls == PALADIN && !cls2 && allowedPaladinDeity(dty)) ||
			(cls == DEATHKNIGHT && !cls2 && allowedDeathknightDeity(dty)) ||
			(cls == CLERIC && cls2 && allowedMultiClericDeity(dty))
	);
}

//*********************************************************************
//						allowedMultiClericDeity
//*********************************************************************

bool RaceData::allowedMultiClericDeity(int dty) const { return(multiClerDeities[dty]); }

//*********************************************************************
//						allowedClericDeity
//*********************************************************************

bool RaceData::allowedClericDeity(int dty) const { return(clerDeities[dty]); }

//*********************************************************************
//						allowedPaladinDeity
//*********************************************************************

bool RaceData::allowedPaladinDeity(int dty) const { return(palDeities[dty]); }

//*********************************************************************
//						allowedDeathknightDeity
//*********************************************************************

bool RaceData::allowedDeathknightDeity(int dty) const { return(dkDeities[dty]); }

//*********************************************************************
//						getId
//*********************************************************************

int RaceData::getId() const { return(id); }

//*********************************************************************
//						getName
//*********************************************************************

bstring RaceData::getName(int tryToShorten) const {
	if(!tryToShorten)
		return(name);
	bstring txt = name;
	if(txt.length() > (unsigned)tryToShorten)
		txt.Replace("Half-", "H-");
	return(txt);
}

//*********************************************************************
//						getSkillBegin
//*********************************************************************

std::list<SkillGain*>::const_iterator RaceData::getSkillBegin() const {
	return(baseSkills.begin());
}

//*********************************************************************
//						getSkillEnd
//*********************************************************************

std::list<SkillGain*>::const_iterator RaceData::getSkillEnd() const {
	return(baseSkills.end());
}