/****************************************************************************
* [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);
}