/**************************************************************************** * [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. * * ------------------------------------------------------------------------ * * Character saving and loading module * ****************************************************************************/ #include "stdafx.h" #include "smaug.h" #include "SysData.h" #include "skill.h" #include "mobiles.h" #include "objects.h" #include "rooms.h" #include "deity.h" #include "class.h" #include "SmaugWizDoc.h" #include "SmaugFiles.h" #include "descriptor.h" #include "character.h" // Array to keep track of equipment temporarily. -Thoric CObjData *save_equipment[MAX_WEAR][8]; CCharacter *quitting_char, *loading_char, *saving_char; int file_ver; // Array of containers read for proper re-nesting of objects. static CObjData * rgObjNest [MAX_NEST]; // Un-equip character before saving to ensure proper -Thoric // stats are saved in case of changes to or removal of EQ void de_equip_char (CCharacter *ch) { CObjData *obj; int x, y; for (x = 0; x < MAX_WEAR; x++) for (y = 0; y < MAX_LAYERS; y++) save_equipment [x][y] = NULL; POSITION pos = ch->GetHeadCarryPos (); while (obj = ch->GetNextCarrying (pos)) { if (obj->wear_loc > -1 && obj->wear_loc < MAX_WEAR) { if (ch->GetTrustLevel () >= obj->GetLevel ()) { for (x = 0; x < MAX_LAYERS; ++x) if (! save_equipment [obj->wear_loc][x]) { save_equipment [obj->wear_loc][x] = obj; break; } if (x == MAX_LAYERS) { bug ("%s had on more than %d layers of clothing in one" " location (%d): %s", ch->GetName (), MAX_LAYERS, obj->wear_loc, obj->GetName ()); } } else { bug ("%s had on %s: ch->GetLevel () = %d " "obj->GetLevel () = %d", ch->GetName (), obj->GetName (), ch->GetLevel (), obj->GetLevel ()); } unequip_char (ch, obj); } } } /* * Re-equip character -Thoric */ void re_equip_char (CCharacter *ch) { int x,y; for (x = 0; x < MAX_WEAR; x++) for (y = 0; y < MAX_LAYERS; y++) if (save_equipment[x][y] != NULL) { if (quitting_char != ch) equip_char (ch, save_equipment[x][y], x); save_equipment[x][y] = NULL; } else break; } // Save a character and inventory. // Would be cool to save NPC's too for quest purposes, // some of the infrastructure is provided. void save_char_obj (CCharacter *ch) { CString strsave; CString strback; FILE *fp; if (!ch) { bug ("Save_char_obj: null ch!", 0); return; } if (ch->IsNpc () || ch->GetLevel () < 2) return; saving_char = ch; // save pc's clan's data while we're at it to keep the data in sync if (!ch->IsNpc () && ch->GetPcData ()->GetClan ()) ch->GetPcData ()->GetClan ()->Save (); // save deity's data to keep it in sync -ren if (!ch->IsNpc () && ch->GetPcData ()->deity) save_deity (ch->GetPcData ()->deity); if (ch->GetDesc () && ch->GetDesc ()->m_pOriginal) ch = ch->GetDesc ()->m_pOriginal; de_equip_char (ch); ch->SetSaveTime (CurrentTime); strsave = FileTable.PlayerName (capitalize (ch->GetName ())); // Save immortal stats, level & vnums for wizlist -Thoric // and do_vnums command // // Also save the player flags so we the wizlist builder can see // who is a guest and who is retired. if (ch->GetLevel () >= LEVEL_IMMORTAL) { strback = FileTable.MakeName (SD_GOD_DIR, capitalize (ch->GetName ())); if ((fp = fopen (strback, "w")) == NULL) { bug ("Save_god_level: fopen, file = %s", NCCP strsave); } else { fprintf (fp, "Level %d\n", ch->GetLevel ()); fprintf (fp, "Pcflags %d\n", ch->GetPcFlags ()); if (ch->GetPcData ()->r_range_lo && ch->GetPcData ()->r_range_hi) fprintf (fp, "RoomRange %d %d\n", ch->GetPcData ()->r_range_lo, ch->GetPcData ()->r_range_hi); if (ch->GetPcData ()->o_range_lo && ch->GetPcData ()->o_range_hi) fprintf (fp, "ObjRange %d %d\n", ch->GetPcData ()->o_range_lo, ch->GetPcData ()->o_range_hi); if (ch->GetPcData ()->m_range_lo && ch->GetPcData ()->m_range_hi) fprintf (fp, "MobRange %d %d\n", ch->GetPcData ()->m_range_lo, ch->GetPcData ()->m_range_hi); fclose (fp); } } // Find out if player directory exists, and create it if not. // Make sure Dir is there, or change strsave to alternate save loc. BOOL bChanged = FileTable.CreatePlayerDirectory (strsave,ch->GetName ()); if (bChanged) ch->SendText ("Not Saved in Player Directory. Ask imm's to fix\n"); if ((fp = fopen (strsave, "w")) == NULL) { bug ("Save_char_obj: fopen, file = %s", NCCP strsave); } else { ch->Write (fp); if (! ch->GetCarryList ().IsEmpty ()) fwrite_obj (ch->GetCarryList (), fp, 0, OS_CARRY, ch->GetLevel ()); if (ch->HasPet ()) ch->GetPet ()->Write (fp); if (ch->comments) fwrite_comments (ch, fp); // comments fprintf (fp, "#END\n"); fclose (fp); } // Auto-backup pfile (can cause lag with high disk access situtations if (SysData.IsBackup ()) { // Find out if backup player directory exists, and create it if not. // Make sure Dir is there, or change strsave to alternate save loc. // (TRUE means create a backup directory) FileTable.CreatePlayerDirectory (strback, ch->GetName (), TRUE); if (! CopyFile (strsave, strback, FALSE)) { bug ("save_char_obj::Error %d: Unable to copy %s to %s.", GetLastError (), NCCP strsave, NCCP strback); } } re_equip_char (ch); write_corpses (ch, NULL); quitting_char = NULL; saving_char = NULL; } // Write an object and its contents. void fwrite_obj (CObjectList& List, FILE *fp, int iNest, short os_type, short ChLevel /* = 0 */) { if (iNest >= MAX_NEST) { bug ("fwrite_obj: iNest hit MAX_NEST %d", iNest); return; } POSITION pos = List.GetHeadPosition (); while (pos) { CObjData &Obj = *List.GetNext (pos); Obj.Write (fp, iNest, os_type, ChLevel); if (! Obj.GetContentList ().IsEmpty ()) fwrite_obj (Obj.GetContentList (), fp, iNest + 1, OS_CARRY, ChLevel); } } // Load a char and inventory into a new ch structure. BOOL load_char_obj (CDescriptor *d, char *name, BOOL preload) { char buf [MAX_INPUT_LENGTH]; char strsave [MAX_INPUT_LENGTH]; extern char strArea [MAX_INPUT_LENGTH]; FILE *fp; char *pLine; struct stat fst; int i, x; extern FILE *fpArea; extern int gAcount; CCharacter *ch = new CCharacter; ch->InitChar (); for (x = 0; x < MAX_WEAR; x++) for (i = 0; i < MAX_LAYERS; i++) save_equipment [x][i] = NULL; loading_char = ch; ch->SetPcData (new pc_data); pc_data &Pc = *ch->GetPcData (); d->m_pCharacter = ch; ch->SetDesc (d); ch->SetName (name); ch->SetBlank (); ch->SetCombine (); ch->SetHasPrompt (); ch->perm_str = 13; ch->perm_int = 13; ch->perm_wis = 13; ch->perm_dex = 13; ch->perm_con = 13; ch->perm_cha = 13; ch->perm_lck = 13; Pc.condition [COND_THIRST] = 48; Pc.condition [COND_FULL] = 48; Pc.condition [COND_BLOODTHIRST] = 10; ch->SetMentalState (-10); ch->m_Style = STYLE_FIGHTING; Pc.pagerlen = 24; ch->LockMenus (TRUE); ch->SetMenuPagelen (24); // BUILD INTERFACE strcpy (strsave, FileTable.PlayerName (capitalize (name))); if (stat (strsave, &fst) != -1) { if (fst.st_size == 0) { strcpy (strsave, FileTable.BackupPlayerName (capitalize (name))); ch->SendText ("Restoring your backup player file..."); } else { sprintf (buf, "%s player data for: %s (%dK)", preload ? "Preloading" : "Loading", ch->GetName (), (int) fst.st_size/1024); gpDoc->LogString (buf, LOG_COMM, LEVEL_GREATER); } } // else no player file BOOL bFound = FALSE; if (fp = fopen (strsave, "r")) { bFound = TRUE; for (int iNest=0; iNest < MAX_NEST; ++iNest) rgObjNest [iNest] = NULL; // Cheat so that bug will show line #'s -- Altrag fpArea = fp; strcpy (strArea, strsave); gAcount = 0; // clear line count for (;;) { char letter; char *word; pLine = fread_line (fp); letter = *pLine++; if (letter == '*') continue; if (letter != '#') { bug ("Load_char_obj: %s, # character not found.", name); delete ch; d->m_pCharacter = NULL; return TRUE; } word = ParseWord (pLine); if (! str_cmp (word, "PLAYER")) { if (! ch->Read (fp, preload)) { delete ch; d->m_pCharacter = NULL; return TRUE; } if (preload) break; } else if (! str_cmp (word, "OBJECT")) // Objects fread_obj (ch, fp, OS_CARRY); // 1.4add // else if (! strcmp (word, "MorphData")) // Morphs // fread_morph_data (ch, fp); else if (! str_cmp (word, "COMMENT")) fread_comment (ch, fp); // Comments else if (! str_cmp (word, "MOBILE")) { CCharacter *pet = fread_mobile (fp); ch->SetPet (pet); pet->SetMaster (ch); pet->SetCharmed (); // Temporarily set the pet in the polymorph room, until // the player gets fully loaded. pet->SetInRoom (RoomTable.GetRoom (SysData.m_RoomPoly)); // pet->SendToRoom (ch->GetInRoom ()); } else if (! str_cmp (word, "END")) // Done break; else { bug ("Load_char_obj: %s, bad section.", name); break; } } fclose (fp); fpArea = NULL; strcpy (strArea, "$"); } if (! Pc.ice_listen) Pc.ice_listen = str_dup (""); if (! bFound) { ch->SetShortDescr (""); ch->SetLongDescr (""); ch->SetDescription (""); Pc.SetClanName (STRALLOC ("")); Pc.SetCouncilName (STRALLOC ("")); Pc.SetDeityName (STRALLOC ("")); Pc.SetPassWord (str_dup ("")); Pc.SetBamfin (str_dup ("")); Pc.SetBamfout (str_dup ("")); Pc.SetRank (str_dup ("")); Pc.SetBestowments (str_dup ("")); Pc.SetTitle (STRALLOC ("")); Pc.SetHomepage (str_dup ("")); Pc.bio = STRALLOC (""); Pc.authed_by = STRALLOC (""); Pc.SetPrompt (STRALLOC ("")); Pc.SetFightPrompt (STRALLOC ("")); } else { if (! Pc.GetClanName ()) { Pc.SetClanName (STRALLOC ("")); Pc.SetClan (NULL); } if (! Pc.GetCouncilName ()) { Pc.SetCouncilName (STRALLOC ("")); Pc.council = NULL; } if (! Pc.GetDeityName ()) { Pc.SetDeityName (STRALLOC ("")); Pc.deity = NULL; } if (! Pc.bio) Pc.bio = STRALLOC (""); if (! Pc.authed_by) Pc.authed_by = STRALLOC (""); if (!ch->IsNpc () && ch->IsImmortal ()) { if (Pc.wizinvis < 2) Pc.wizinvis = ch->GetLevel (); if (! preload) assign_area (ch); } for (i = 0; i < MAX_WEAR; i++) for (x = 0; x < MAX_LAYERS; x++) if (save_equipment [i][x]) { equip_char (ch, save_equipment [i][x], i); save_equipment [i][x] = NULL; } } loading_char = NULL; return bFound; } CCharacter *fread_mobile (FILE *fp) { char *word = "", *pLine; if (! feof (fp)) { pLine = fread_line (fp); word = ParseWord (pLine); } if (strcmp (word, "Vnum")) { ScanToEnd (fp, "EndMobile"); bug ("fread_mobile: Vnum not found"); return NULL; } int vnum = ParseNumber (pLine); CCharacter *mob = create_mobile (MobTable.GetMob (vnum)); if (! mob) { ScanToEnd (fp, "EndMobile"); bug ("fread_mobile: No index data for vnum %d", vnum); return NULL; } mob->ReadMob (fp); return mob; } #if defined (KEY) #undef KEY #endif #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; \ } void fread_obj (CCharacter *ch, FILE *fp, short os_type) { CObjData *obj; char *word; int iNest; BOOL fMatch; CRoomIndexData *room = NULL; char *pLine; obj = new CObjData; obj->count = 1; obj->wear_loc = -1; obj->weight = 1; BOOL fNest = TRUE; // Requiring a Nest 0 is a waste BOOL fVnum = TRUE; iNest = 0; 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': KEY2("ActionDesc", obj->SetActionDescr, ParseCString (pLine, fp)); if (!str_cmp (word, "Affect") || !str_cmp (word, "AffectData")) { CAffectData *paf; int pafmod; paf = new CAffectData; if (! str_cmp (word, "Affect")) { paf->type = ParseNumber (pLine); } else { int sn = SkillTable.Lookup (ParseWord (pLine)); if (sn < 0) bug ("Fread_obj: unknown skill."); else paf->type = sn; } paf->duration = ParseNumber (pLine); pafmod = ParseNumber (pLine); paf->location = ParseNumber (pLine); paf->SetVector (ParseWord (pLine)); if (paf->location == APPLY_WEAPONSPELL || paf->location == APPLY_WEARSPELL || paf->location == APPLY_REMOVESPELL || paf->location == APPLY_STRIPSN || paf->location == APPLY_RECURRINGSPELL) paf->modifier = slot_lookup (pafmod); else paf->modifier = pafmod; obj->AddAffect (paf); fMatch = TRUE; break; } if (! str_cmp (word, "AntiClass")) { CClassData &Cl = *ClassTable.Find (ParseCString (pLine, fp)); if (&Cl) obj->SetAntiClass (Cl.GetClass ()); fMatch = TRUE; break; } break; case 'C': KEY ("Cost", obj->cost, ParseNumber (pLine)); KEY ("Count", obj->count, ParseNumber (pLine)); break; case 'D': KEY2("Description", obj->SetDescription, ParseCString (pLine, fp)); break; case 'E': if (! str_cmp (word, "ExtraFlags")) { obj->m_ExtraFlags.Parse (pLine); fMatch = TRUE; break; } if (! str_cmp (word, "ExtraDescr")) { CExtraDescrData *ed = new CExtraDescrData; ed->keyword = ParseString (pLine, fp); ed->SetDescription (ParseString (pLine, fp)); obj->ExDesList.AddTail (ed); fMatch = TRUE; break; } if (! str_cmp (word, "End")) { if (! fNest || ! fVnum) { bug ("Fread_obj: %s, incomplete object.", obj->GetName ()); delete obj; return; } else { short wear_loc = obj->wear_loc; CObjIndexData &Idx = *obj->GetIndex (); if (! obj->GetName ()) obj->SetName (Idx.GetName ()); if (! obj->GetDescription ()) obj->SetDescription (Idx.GetDescription ()); if (!obj->GetShortDescr ()) obj->SetShortDescr (Idx.GetShortDescr ()); if (! obj->GetActionDescr ()) obj->SetActionDescr (Idx.GetActionDescr ()); if (! obj->serial) { cur_obj_serial = UMAX ((cur_obj_serial + 1) & (BV30-1), 1); obj->serial = obj->GetIndex ()->serial = cur_obj_serial; } if (fNest) rgObjNest [iNest] = obj; Idx.m_ObjList.AddTail (obj); Idx.count += obj->count; obj->wear_loc = -1; // Why? // Corpse saving. -- Altrag if (os_type == OS_CORPSE) { if (! room) { bug ("Fread_obj: %s, Corpse without room", obj->GetName ()); room = RoomTable.GetRoom (SysData.m_RoomLimbo); } // Give the corpse a timer if there isn't one if (obj->timer < 1) obj->timer = 40; if (room->vnum == SysData.m_RoomHallOfFallen && ! obj->GetContentList ().IsEmpty ()) obj->timer = -1; obj = obj_to_room (obj, room); } else if (iNest == 0 || rgObjNest [iNest] == NULL) { int slot; int x; BOOL reslot = FALSE; if (wear_loc > -1 && wear_loc < MAX_WEAR) { for (x = 0; x < MAX_LAYERS; x++) if (! save_equipment [wear_loc][x]) { save_equipment [wear_loc][x] = obj; slot = x; reslot = TRUE; break; } if (x == MAX_LAYERS) bug ("Fread_obj: too many layers %d", wear_loc); } obj = obj_to_char (obj, ch); if (reslot && slot != -1) save_equipment [wear_loc][slot] = obj; } else { if (rgObjNest [iNest-1]) { separate_obj (rgObjNest [iNest-1]); obj = obj_to_obj (obj, rgObjNest [iNest-1]); } else bug ("Fread_obj: nest layer missing %d", iNest-1); } if (fNest) rgObjNest [iNest] = obj; return; } } break; case 'I': KEY ("ItemType", obj->item_type, ParseNumber (pLine)); break; case 'L': KEY2("Level", obj->SetLevel, ParseNumber (pLine)); break; case 'N': KEY2("Name", obj->SetName, ParseCString (pLine, fp)); if (! str_cmp (word, "Nest")) { iNest = ParseNumber (pLine); if (iNest < 0 || iNest >= MAX_NEST) { bug ("Fread_obj: bad nest %d.", iNest); iNest = 0; fNest = FALSE; } fMatch = TRUE; } break; case 'R': KEY ("Room", room, RoomTable.GetRoom (ParseNumber (pLine))); case 'S': KEY2("ShortDescr", obj->SetShortDescr, ParseCString (pLine, fp)); if (! str_cmp (word, "Spell")) { int iValue = ParseNumber (pLine); int sn = SkillTable.Lookup (ParseWord (pLine)); if (iValue < 0 || iValue > 5) bug ("Fread_obj: bad iValue %d.", iValue); else if (sn < 0) bug ("Fread_obj: unknown skill."); else obj->value [iValue] = sn; fMatch = TRUE; break; } break; case 'T': KEY ("Timer", obj->timer, ParseNumber (pLine)); break; case 'V': if (! str_cmp (word, "Values")) { int x1, x2, x3, x4, x5, x6; x1 = x2 = x3 = x4 = x5 = x6 = 0; sscanf (pLine, "%d %d %d %d %d %d", &x1, &x2, &x3, &x4, &x5, &x6); obj->value [0] = x1; obj->value [1] = x2; obj->value [2] = x3; obj->value [3] = x4; obj->value [4] = x5; obj->value [5] = x6; fMatch = TRUE; break; } if (! str_cmp (word, "Vnum")) { int vnum = ParseNumber (pLine); if ((obj->pIndexData = OIdxTable.GetObj (vnum)) == NULL) fVnum = FALSE; else { fVnum = TRUE; obj->cost = obj->GetIndex ()->cost; obj->weight = obj->GetIndex ()->weight; obj->item_type = obj->GetIndex ()->item_type; obj->wear_flags = obj->GetIndex ()->wear_flags; obj->m_ExtraFlags = obj->GetIndex ()->m_ExtraFlags; } fMatch = TRUE; break; } break; case 'W': KEY ("WearFlags", obj->wear_flags, ParseNumber (pLine)); KEY ("WearLoc", obj->wear_loc, ParseNumber (pLine)); KEY ("Weight", obj->weight, ParseNumber (pLine)); break; } if (! fMatch) { bug ("Fread_obj: no match: %s", word); delete obj; return; } } } // Based on last time modified, show when a player was last on -Thoric void do_last (CCharacter *ch, char *argument) { char buf [MAX_STRING_LENGTH]; char arg [MAX_INPUT_LENGTH]; char name[MAX_INPUT_LENGTH]; struct stat fst; one_argument (argument, arg); if (arg[0] == '\0') { ch->SendText ("Usage: last <playername>\n\r"); return; } strcpy (name, capitalize (arg)); strcpy (buf, FileTable.PlayerName (name)); if (stat (buf, &fst) != -1) sprintf (buf, "%s was last on: %s\r", name, NCCP gpDoc->GetTimeString (fst.st_mtime)); else sprintf (buf, "%s was not found.\n\r", name); ch->SendText (buf); } void write_corpses (CCharacter *ch, const char *name) { FILE *fp = NULL; // Name and ch support so that we dont have to have a char to save their // corpses.. (ie: decayed corpses while offline) if (ch && ch->IsNpc ()) { bug ("Write_corpses: writing NPC corpse."); return; } if (ch) name = ch->GetName (); CString Cname = FileTable.MakeCorpseName (capitalize (name)); // Go by vnum, less chance of screwups. -- Altrag CObjIndexData &Idx = *OIdxTable.Find (OBJ_VNUM_CORPSE_PC); if (&Idx) { POSITION pos = Idx.m_ObjList.GetHeadPosition (); while (pos) { CObjData &Corpse = *Idx.m_ObjList.GetNext (pos); if (Corpse.GetInRoom () && !str_cmp (Corpse.GetShortDescr ()+14, name)) { if (! fp) { if (! (fp = fopen (Cname, "w"))) { bug ("Write_corpses: Cannot open file: %s", Cname); return; } } Corpse.Write (fp, 0, OS_CORPSE); fwrite_obj (Corpse.GetContentList (), fp, 1, OS_CARRY); } } } if (fp) { fprintf (fp, "#END\n\n"); fclose (fp); } else { remove (FileTable.MakeCorpseName (capitalize (name))); } } void load_corpses () { CString Fname; FILE *fp; extern int falling; WIN32_FIND_DATA Fd; HANDLE Dh = FindFirstFile (FileTable.MakeCorpseName ("*"), &Fd); if (Dh == INVALID_HANDLE_VALUE) then return; gpDoc->LogString ("Loading corpses...", LOG_BOOT); falling = 1; // Arbitrary, must be >0 though. do { Fname = FileTable.MakeName (SD_CORPSE_DIR, Fd.cFileName); gpDoc->m_pLog->Printf (LOG_ALWAYS, "Corpse -> %s\n", NCCP Fname); if (! (fp = fopen (Fname, "r"))) { perror (Fname); continue; } for (;;) { char letter; char *word, *pLine; pLine = fread_line (fp); letter = *pLine++; if (letter == '*') continue; if (letter != '#') { bug ("Load_corpses: # not found."); break; } word = ParseWord (pLine); if (! str_cmp (word, "CORPSE")) fread_obj (NULL, fp, OS_CORPSE); else if (! str_cmp (word, "OBJECT")) fread_obj (NULL, fp, OS_CARRY); else if (! str_cmp (word, "END")) break; else { bug ("Load_corpses: bad section."); break; } } fclose (fp); } while (FindNextFile (Dh, &Fd)); FindClose (Dh); falling = 0; }