SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik Staerfeldt, Tom Madsen, and Katja Nyboe.    *
 ****************************************************************************/
// Races.cpp

#include	"stdafx.h"
#include	"smaug.h"
#include	"Smaugx.h"
#include	"Bitvector.h"
#include	"objects.h"
#include	"Rooms.h"
#include	"character.h"
#include	"language.h"
#include	"skill.h"
#include	"races.h"
#include	"class.h"
#include	"SmaugFiles.h"

#if defined (KEY)
#undef KEY
#endif


CRaceData::~CRaceData ()
{
	for (int i=0; i < MAX_WEAR; ++i)
		delete m_WhereName [i];
}


#define KEY(literal,field,value)					\
				if (!str_cmp (word, literal)) {		\
				    field  = value;					\
				    fMatch = TRUE;					\
				    break;							\
				}
#define KEY2(literal, field, value)					\
				if (!str_cmp (word, literal)) {		\
				    field (value);					\
				    fMatch = TRUE;					\
				    break;							\
				}


BOOL CRaceData::Read (FILE* fp)
{
	char	*word;
	BOOL	fMatch;
	char	*pLine;
	int		version = 0;
	int		wear = 0;

	m_Race = RaceTable.GetCount ();

	for (;;) {
		fMatch = FALSE;
		if (! feof (fp)) {
			pLine = fread_line (fp);
			word = ParseWord (pLine);
		}
		else word = "End";

		switch  (UPPER (word [0])) {
		  case '*':
			fMatch = TRUE;
			break;

		  case 'A':
			KEY ("AC_Plus",		m_AcPlus,		ParseNumber (pLine));
			KEY ("Align",		m_Alignment,	ParseNumber (pLine));
			if (! str_cmp (word, "Affected")) {
				m_Affects.Parse (pLine);
				fMatch = TRUE;
				break;
			}
			if (! str_cmp (word, "Attacks")) {
				m_Attacks.Parse (pLine);
				fMatch = TRUE;
				break;
			}
			break;

		  case 'C':
			KEY ("Con_Plus",	con_plus,		ParseNumber (pLine));
			KEY ("Cha_Plus",	cha_plus,		ParseNumber (pLine));
			break;

		  case 'D':
			KEY ("Dex_Plus",	dex_plus,		ParseNumber (pLine));
			if (! str_cmp (word, "Defenses")) {
				m_Defenses.Parse (pLine);
				fMatch = TRUE;
				break;
			}
			break;

		  case 'E':
			KEY ("Exp_Mult",	m_ExpMultiplier, ParseNumber (pLine));
			if (! str_cmp (word, "End"))
				return TRUE;
			break;

		  case 'H':
			KEY ("Height",		m_Height,		ParseNumber (pLine));
			KEY ("Hit",			hit,			ParseNumber (pLine));
			KEY ("HP_Regen",	m_HpRegen,		ParseNumber (pLine));
			KEY ("Hunger_Mod",	m_HungerMod,	ParseNumber (pLine));
			break;

		  case 'I':
			KEY ("Int_Plus",	int_plus,		ParseNumber (pLine));
			break;

		  case 'L':
			if (! stricmp (word, "Languages")) {
				LanguageTable.SetFromString (pLine, m_Languages);
				fMatch = TRUE;
				break;
			}
			KEY ("Lck_Plus",	lck_plus,		ParseNumber (pLine));
			break;

		  case 'M':
			KEY ("Mana",		mana,			ParseNumber (pLine));
			KEY ("Mana_Regen",	m_ManaRegen,	ParseNumber (pLine));
			KEY ("Max_Align",	m_Maxalign,		ParseNumber (pLine));
			KEY ("Min_Align",	m_Minalign,		ParseNumber (pLine));
			break;

		  case 'N':
			KEY2("Name",		SetName,		ParseCString (pLine, fp));
			break;

		  case 'R':
			KEY ("Race_Recall",	m_RaceRecall,	ParseNumber (pLine));
			KEY ("Resist",		resist,			ParseNumber (pLine));

			if (! str_cmp (word, "RestrictClass")) {
				CClassData	*pClass =
					ClassTable.Find (ParseCString (pLine, fp));
				if (pClass)
					SetClassRestrict (pClass->GetClass ());
				fMatch = TRUE;
			}
			break;

		  case 'S':
			KEY ("SavingPoisonDeath", m_SavingPoisonDeath, ParseNumber (pLine));
			KEY ("SavingWand",		 m_SavingWand,		ParseNumber (pLine));
			KEY ("SavingParaPetri",	 m_SavingParaPetri,	ParseNumber (pLine));
			KEY ("SavingBreath",	 m_SavingBreath,	ParseNumber (pLine));
			KEY ("SavingSpellstaff", m_SavingSpellstaff,ParseNumber (pLine));

			KEY ("Suscept",		suscept,		ParseNumber (pLine));
			KEY ("Str_Plus",	str_plus,		ParseNumber (pLine));
			KEY ("ShoveDrag",	m_ShoveDrag,	ParseNumber (pLine));
			if (! str_cmp (word, "Skill")) {
				word = ParseWord (pLine);
				int	lev = ParseNumber (pLine);
				int	adp = ParseNumber (pLine);
				CSkill *pSkill = SkillTable.Find (word);
				if (! pSkill)
					bug ("CRaceData::Read: %s, Skill %s unknown",
						GetName (), word);
				else {
					pSkill->SetRaceLevel (m_Race, lev);
					pSkill->SetRaceAdept (m_Race, adp);
				}
				fMatch = TRUE;
				break;
			}
			break;

		  case 'T':
			KEY ("Thirst_mod",	m_ThirstMod,	ParseNumber (pLine));
			break;

		  case 'V':
			if (! str_cmp (word, "Version")) {
				version = ParseNumber (pLine);
				if (version != GetCurrentVersion ()) {
					bug ("Incompatible Race File: %s, Version %d (%d required)",
						GetName (), version, GetCurrentVersion ());
					return FALSE;
				}
				fMatch = TRUE;
			}
			break;

		  case 'W':
			KEY ("Weight",		m_Weight,		ParseNumber (pLine));
			KEY ("Wis_Plus",	wis_plus,		ParseNumber (pLine));
			if (! str_cmp (word, "WhereName")) {
				if (wear < MAX_WEAR)
					m_WhereName [wear++] = ParseStringNohash (pLine, fp);
				else
					bug ("CRaceData::Read: %s, Too many Where Names",
						GetName ());

				fMatch = TRUE;
				break;
			}
			break;
		}
		if (fMatch && version == 0) {
			bug ("CRaceData::Read: %s, Incompatible Race file, no Version.",
				GetName ());
			break;
		}

		if (! fMatch)
			bug ("CRaceData::Read: %s, no match: %s", GetName (), word);
	}
	return FALSE;
}


void CRaceData::Write ()
{
	FILE	*fp;

	if (! m_pName) {
		bug ("Race %d has null name, not writing .race file.", m_Race);
		return;
	}

	CString	Fname = FileTable.MakeRaceName (m_pName);

	if ((fp = fopen (Fname, "w")) == NULL) {
		bug ("Cannot open: %s for writing", Fname);
		return;
	}

	fprintf (fp, "Version     %d           SmaugWiz V2\n\n",
		GetCurrentVersion ());

	fprintf (fp, "Name        %s~\n", m_pName);
	fprintf (fp, "Affected    %s\n", m_Affects.PrintVector ());
	fprintf (fp, "Str_Plus    %d\n", str_plus);
	fprintf (fp, "Dex_Plus    %d\n", dex_plus);
	fprintf (fp, "Wis_Plus    %d\n", wis_plus);
	fprintf (fp, "Int_Plus    %d\n", int_plus);
	fprintf (fp, "Con_Plus    %d\n", con_plus);
	fprintf (fp, "Cha_Plus    %d\n", cha_plus);
	fprintf (fp, "Lck_Plus    %d\n", lck_plus);
	fprintf (fp, "Hit         %d\n", hit);
	fprintf (fp, "Mana        %d\n", mana);
	fprintf (fp, "Resist      %d\n", resist);
	fprintf (fp, "Suscept     %d\n", suscept);
	fprintf (fp, "Languages   %s~\n", LanguageTable.PrintAssigned (m_Languages));

	for (int cl=0; cl < ClassTable.GetCount (); ++cl)
		if (IsClassRestricted (cl))
			fprintf (fp, "RestrictClass  %s~\n", ClassTable.GetName (cl));

	fprintf (fp, "Align       %d\n", m_Alignment);
	fprintf (fp, "Min_Align   %d\n", m_Minalign);
	fprintf (fp, "Max_Align   %d\n", m_Maxalign);
	fprintf (fp, "AC_Plus     %d\n", m_AcPlus);
	fprintf (fp, "Exp_Mult    %d\n", m_ExpMultiplier); 
	fprintf (fp, "Attacks     %s\n", m_Attacks.PrintVector ());
	fprintf (fp, "Defenses    %s\n", m_Defenses.PrintVector ());
	fprintf (fp, "Height      %d\n", m_Height);
	fprintf (fp, "Weight      %d\n", m_Weight);
	fprintf (fp, "Hunger_Mod  %d\n", m_HungerMod);
	fprintf (fp, "Thirst_mod  %d\n", m_ThirstMod);
	fprintf (fp, "ShoveDrag   %d\n", m_ShoveDrag);
	fprintf (fp, "Mana_Regen  %d\n", m_ManaRegen);
	fprintf (fp, "HP_Regen    %d\n", m_HpRegen);
	fprintf (fp, "Race_Recall %d\n", m_RaceRecall);

	if (m_SavingPoisonDeath)
		fprintf (fp, "SavingPoisonDeath %d\n", m_SavingPoisonDeath);
	if (m_SavingWand)
		fprintf (fp, "SavingWand        %d\n", m_SavingWand);
	if (m_SavingParaPetri)
		fprintf (fp, "SavingParaPetri   %d\n", m_SavingParaPetri);
	if (m_SavingBreath)
		fprintf (fp, "SavingBreath      %d\n", m_SavingBreath);
	if (m_SavingSpellstaff)
		fprintf (fp, "SavingSpellstaff  %d\n", m_SavingSpellstaff);

	for (int i=0; i < MAX_WEAR; ++i)
		fprintf (fp, "WhereName  %s~\n",
			m_WhereName [i] ? m_WhereName [i] : where_name [i]);

	for (int sn=0; sn < SkillTable.GetCount (); ++sn) {
		if (SkillTable.IsValid (sn)) {
			CSkill	*pSkill = SkillTable.GetSkill (sn);
			int lev = pSkill->GetRaceLevel (m_Race);
			int adp = pSkill->GetRaceAdept (m_Race);
			if ((lev < LEVEL_IMMORTAL) && (lev || adp))
				fprintf (fp, "Skill  '%s'  %d %d\n",
					pSkill->GetName (), lev, adp);
		}
	}

	fprintf (fp, "End\n");
	fclose (fp);
}


void CRaceData::ShowRacialWearNames (CCharacter* ch)
{
	for (int i=0; i < MAX_WEAR; ++i) {
		CString	s = GetWhereName (i);
		if (s.IsEmpty ())
			break;
		ch->SendTextf ("%2d) %s\r\n", i+1, s);
	}
}


// Load in all the race files.
void CRaceTable::Load ()
{
	FILE	*fp;
	char	*pLine;
	CString	RFname;

	CString	Fname = FileTable.GetName (SM_RACE_LIST);
	if ((fp = fopen (Fname, "r")) == NULL) {
		perror (Fname);
		ThrowSmaugException (SE_RACE);
	}

	for (;;) {
		if (! feof (fp)) {
			pLine = fread_line (fp);
			RFname = ParseWord (pLine);
		}
		else RFname = "$";
		if (RFname [0] == '$') then break;

		if (RFname [0] != '*')
			if (! LoadRaceFile (RFname))
				bug ("Cannot load race file: %s", RFname);
	}
	fclose (fp);

	memset (&m_NullRace, 0, sizeof (CRaceData));
	m_NullRace.m_Race = -1;
	m_NullRace.m_pName = "Null";
	m_NullRace.SetSpeaks (LanguageTable.GetCommon ());
}


bool CRaceTable::WriteList ()
{
	FILE	*fp;

	fp = fopen (FileTable.GetName (SM_RACE_LIST), "w");
	if (! fp) {
		bug ("FATAL: cannot open race.lst for writing!\n\r");
		return FALSE;
	}	  

	POSITION	pos = GetHeadPosition ();
	while (pos)
		fprintf (fp, "%s.race\n", GetNext (pos)->GetName ());

	fprintf (fp, "$\n");
	fclose (fp);

	return TRUE;
}


BOOL CRaceTable::LoadRaceFile (const char *fname)
{
	FILE		*fp;

	CString	Fname = FileTable.MakeName (SD_RACE_DIR, fname);
	if ((fp = fopen (Fname, "r")) == NULL) {
		perror (Fname);
		return FALSE;
	}

	CRaceData	*pRace = new CRaceData;

	if (pRace->Read (fp)) {
		fclose (fp);
		Add (pRace);
		return TRUE;
	}

	bug ("LoadRaceFile: Race (%s) bad/not found.",
		pRace->m_pName ? pRace->m_pName : "name not found");
	delete pRace;
	return FALSE;
}


CRaceData *CRaceTable::Find (const char* prefix)
{
	POSITION	pos = GetHeadPosition ();
	while (pos) {
		CRaceData	*ra = GetNext (pos);
		if (toupper (prefix [0]) == toupper (ra->m_pName [0])
		  && ! str_prefix (prefix, ra->m_pName))
				return ra;
	}
	return NULL;
}


int CRaceTable::GetRace (const char* name)
{
	CRaceData	*pRace = Find (name);

	return pRace ? pRace->GetRace () : RACE_NONE;
}


NpcRaces CRaceTable::GetNpcRace (const char *name)
{
	for (int x=0; x < MAX_NPC_RACE; ++x)
		if (! str_cmp (NpcRaceNames [x], name))
			return (NpcRaces) x;
	return (NpcRaces) -1;
}


void CRaceTable::Save ()
{
	POSITION	pos = GetHeadPosition ();

	while (pos)
		GetNext (pos)->Write ();
}


void CRaceTable::RemoveAll ()
{
	while (! IsEmpty ())
		delete RemoveTail ();

	CPtrList::RemoveAll ();
}


void CRaceTable::ListRaces (CCharacter* ch)
{
	POSITION	pos = GetHeadPosition ();
	while (pos) {
		CRaceData	&Ra = *GetNext (pos);
		ch->SendTextf ("%2d) %s\r\n", Ra.GetRace (), Ra.GetName ());
	}
}


const CRaceData& CRaceData::operator= (const CRaceData& in)
{
	ZeroMemory (this, sizeof (CRaceData));
	m_Race = in.m_Race;
	SetName (in.GetName ());
	memcpy (&m_Affects, &in.m_Affects, (int) ((char*) m_WhereName - (char*) &m_Affects));
	memcpy (&m_ManaRegen, &in.m_ManaRegen, sizeof (m_ClassRestrict) +
		(int) ((char*) &m_ClassRestrict - (char*) &m_ManaRegen));

	for (int i=0; i < MAX_WEAR; ++i)
		if (in.GetWhereName (i))
			SetWhereName (i, in.GetWhereName (i));

	return *this;
}


void CreateRace (CCharacter* ch, CString Name, CString CopyRace)
{
	CRaceData	*pRace = NULL, *pCopy = NULL;

	if (! CopyRace.IsEmpty ()) {
		pCopy = RaceTable.Find (CopyRace);
		if (! pCopy) {
			ch->SendTextf ("Cannot copy from race %s. Race does not exist.\r\n",
				CopyRace);
			return;
		}
	}

	if (pCopy)
		pRace = new CRaceData (*pCopy);
	else
		pRace = new CRaceData;

	pRace->SetName (Name);
	pRace->SetRace (RaceTable.GetCount ());
	RaceTable.AddTail (pRace);

	if (! pCopy) {
		pRace->SetMinAlign (-1000);
		pRace->SetMaxAlign (1000);
		pRace->SetExpMultiplier (100);
		pRace->SetHeight (66);
		pRace->SetWeight (150);
	}

	CString	FName = FileTable.MakeRaceName (Name);
	if (FileTable.Exists (FName)) {
		CString		BName = FileTable.MakeBackupName (FName);
		if (FileTable.Exists (BName)) {
			ch->SendTextf ("Removing backup race file: %s\r\n", BName);
			FileTable.Remove (BName);
		}
		ch->SendTextf ("Renaming existing racefile for %s race to .bak\r\n", Name);
		FileTable.Rename (FName, BName);
	}
	pRace->Write ();

	ch->SendTextf ("%s race created and saved.\r\n", Name);
}