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