Room save flag functions

This commit is contained in:
kinther 2025-09-07 08:29:04 -07:00
parent 91441806a4
commit 9d54d7d1a6
9 changed files with 524 additions and 40 deletions

0
lib/world/rsv/0.rsv Normal file
View file

0
lib/world/rsv/1.rsv Normal file
View file

View file

@ -29,6 +29,7 @@
#include "shop.h"
#include "quest.h"
#include "modify.h"
#include "roomsave.h"
/* Local defined utility functions */
/* do_group utility functions */
@ -134,20 +135,44 @@ ACMD(do_quit)
ACMD(do_save)
{
if (IS_NPC(ch) || !ch->desc)
char a1[MAX_INPUT_LENGTH], a2[MAX_INPUT_LENGTH];
/* Parse up to two words so we can accept "save room" or "room save". */
two_arguments(argument, a1, a2);
/* Does either token equal "room"? (order-agnostic) */
const bool wants_room = ((*a1 && !str_cmp(a1, "room")) ||
(*a2 && !str_cmp(a2, "room")));
if (wants_room) {
room_rnum rnum = IN_ROOM(ch);
if (rnum == NOWHERE) {
send_to_char(ch, "You're not in a valid room.\r\n");
return;
}
/* Not a SAVE room? Fall back to normal character save semantics. */
if (!ROOM_FLAGGED(rnum, ROOM_SAVE)) {
send_to_char(ch, "Saving %s.\r\n", GET_NAME(ch));
save_char(ch);
Crash_crashsave(ch); /* keep whatever your tree normally calls */
return;
}
/* Room is flagged SAVE → persist its contents */
if (RoomSave_now(rnum)) {
send_to_char(ch, "Saving room.\r\n");
} else {
send_to_char(ch, "Room save failed; see logs.\r\n");
}
return;
}
/* No "room" token present → normal character save */
send_to_char(ch, "Saving %s.\r\n", GET_NAME(ch));
/* Stamp the spawn room first so it's included in this save. */
if (IN_ROOM(ch) != NOWHERE)
GET_LOADROOM(ch) = GET_ROOM_VNUM(IN_ROOM(ch));
save_char(ch);
Crash_crashsave(ch);
if (ROOM_FLAGGED(IN_ROOM(ch), ROOM_HOUSE_CRASH))
House_crashsave(GET_ROOM_VNUM(IN_ROOM(ch)));
}
/* Generic function for commands which are normally overridden by special

View file

@ -84,6 +84,7 @@ const char *room_bits[] = {
"*", /* The BFS Mark. */
"WORLDMAP",
"QUIT",
"SAVE",
"\n"
};
@ -124,8 +125,16 @@ const char *sector_types[] = {
"Mountains",
"Water (Swim)",
"Water (No Swim)",
"In Flight",
"Underwater",
"In Flight",
"Scrublands",
"Sands",
"Rocky Terrain",
"Roads",
"Underground",
"Silt Sea",
"Ashlands",
"Tablelands",
"\n"
};

View file

@ -38,6 +38,7 @@
#include "mud_event.h"
#include "msgedit.h"
#include "screen.h"
#include "roomsave.h"
#include <sys/stat.h>
/* declarations of most of the 'global' variables */
@ -782,6 +783,10 @@ void boot_db(void)
House_boot();
}
/* Restore persistent room contents last so they take precedence. */
log("Loading Room Contents.");
RoomSave_boot();
log("Cleaning up last log.");
clean_llog_entries();

View file

@ -23,6 +23,7 @@
#include "fight.h"
#include "screen.h"
#include "mud_event.h"
#include "roomsave.h"
#include <time.h>
/* local file scope function prototypes */
@ -440,6 +441,8 @@ void point_update(void)
{
struct char_data *i, *next_char;
struct obj_data *j, *next_thing, *jj, *next_thing2;
/* Room-save autosave pulse counter (static so it persists across calls) */
static int roomsave_pulse = 0;
/* characters */
for (i = character_list; i; i = next_char) {
@ -454,59 +457,59 @@ void point_update(void)
GET_MANA(i) = MIN(GET_MANA(i) + mana_gain(i), GET_MAX_MANA(i));
GET_MOVE(i) = MIN(GET_MOVE(i) + move_gain(i), GET_MAX_MOVE(i));
if (AFF_FLAGGED(i, AFF_POISON))
if (damage(i, i, 2, SPELL_POISON) == -1)
continue; /* Oops, they died. -gg 6/24/98 */
if (damage(i, i, 2, SPELL_POISON) == -1)
continue; /* Oops, they died. -gg 6/24/98 */
if (GET_POS(i) <= POS_STUNNED)
update_pos(i);
update_pos(i);
} else if (GET_POS(i) == POS_INCAP) {
if (damage(i, i, 1, TYPE_SUFFERING) == -1)
continue;
continue;
} else if (GET_POS(i) == POS_MORTALLYW) {
if (damage(i, i, 2, TYPE_SUFFERING) == -1)
continue;
continue;
}
if (!IS_NPC(i)) {
update_char_objects(i);
(i->char_specials.timer)++;
if (GET_LEVEL(i) < CONFIG_IDLE_MAX_LEVEL)
check_idling(i);
check_idling(i);
}
}
/* objects */
for (j = object_list; j; j = next_thing) {
next_thing = j->next; /* Next in object list */
next_thing = j->next; /* Next in object list */
/* If this is a corpse */
if (IS_CORPSE(j)) {
/* timer count down */
if (GET_OBJ_TIMER(j) > 0)
GET_OBJ_TIMER(j)--;
GET_OBJ_TIMER(j)--;
if (!GET_OBJ_TIMER(j)) {
if (j->carried_by)
act("$p decays in your hands.", FALSE, j->carried_by, j, 0, TO_CHAR);
else if ((IN_ROOM(j) != NOWHERE) && (world[IN_ROOM(j)].people)) {
act("A quivering horde of maggots consumes $p.",
TRUE, world[IN_ROOM(j)].people, j, 0, TO_ROOM);
act("A quivering horde of maggots consumes $p.",
TRUE, world[IN_ROOM(j)].people, j, 0, TO_CHAR);
}
for (jj = j->contains; jj; jj = next_thing2) {
next_thing2 = jj->next_content; /* Next in inventory */
obj_from_obj(jj);
if (j->carried_by)
act("$p decays in your hands.", FALSE, j->carried_by, j, 0, TO_CHAR);
else if ((IN_ROOM(j) != NOWHERE) && (world[IN_ROOM(j)].people)) {
act("A quivering horde of maggots consumes $p.",
TRUE, world[IN_ROOM(j)].people, j, 0, TO_ROOM);
act("A quivering horde of maggots consumes $p.",
TRUE, world[IN_ROOM(j)].people, j, 0, TO_CHAR);
}
for (jj = j->contains; jj; jj = next_thing2) {
next_thing2 = jj->next_content; /* Next in inventory */
obj_from_obj(jj);
if (j->in_obj)
obj_to_obj(jj, j->in_obj);
else if (j->carried_by)
obj_to_room(jj, IN_ROOM(j->carried_by));
else if (IN_ROOM(j) != NOWHERE)
obj_to_room(jj, IN_ROOM(j));
else
core_dump();
}
extract_obj(j);
if (j->in_obj)
obj_to_obj(jj, j->in_obj);
else if (j->carried_by)
obj_to_room(jj, IN_ROOM(j->carried_by));
else if (IN_ROOM(j) != NOWHERE)
obj_to_room(jj, IN_ROOM(j));
else
core_dump();
}
extract_obj(j);
}
}
/* If the timer is set, count it down and at 0, try the trigger
@ -517,6 +520,15 @@ void point_update(void)
timer_otrigger(j);
}
}
/* ---- Room SAVE autosave (every 10 minutes; adjust the 600 as desired) ----
* Requires: #include "roomsave.h" at the top of this file.
* Saves all rooms flagged ROOM_SAVE via roomsave.c.
*/
if (++roomsave_pulse >= (PASSES_PER_SEC * 600)) {
roomsave_pulse = 0;
RoomSave_autosave_tick();
}
}
/* Note: amt may be negative */

404
src/roomsave.c Normal file
View file

@ -0,0 +1,404 @@
/**
* @file roomsave.c
* Numeric and string contants used by the MUD.
*
* An addition to the core tbaMUD source code distribution, which is a derivative
* of, and continuation of, CircleMUD.
*
* All rights reserved. See license for complete information.
* Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University
* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.
*
*/
#include "conf.h"
#include "sysdep.h"
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#include "structs.h"
#include "utils.h"
#include "db.h"
#include "handler.h"
#include "comm.h"
#include "constants.h"
#include "roomsave.h"
/* Write saved rooms under lib/world/rsv/<vnum>.rsv (like wld/ zon/ obj/). */
#ifndef ROOMSAVE_PREFIX
#define ROOMSAVE_PREFIX LIB_WORLD "rsv/"
#endif
#ifndef ROOMSAVE_EXT
#define ROOMSAVE_EXT ".rsv"
#endif
/* --- helper: read a list of objects until '.' or 'E' and return the head --- */
static struct obj_data *roomsave_read_list(FILE *fl) {
char line[256];
struct obj_data *head = NULL, *tail = NULL;
while (fgets(line, sizeof(line), fl)) {
if (line[0] == '.' || line[0] == 'E') {
/* End of this list scope */
break;
}
if (line[0] != 'O')
continue; /* ignore junk / blank lines */
/* Parse object header: O vnum timer weight cost rent */
int vnum, timer, weight, cost, rent;
if (sscanf(line, "O %d %d %d %d %d", &vnum, &timer, &weight, &cost, &rent) != 5)
continue;
/* IMPORTANT: read by VNUM (VIRTUAL), not real index */
struct obj_data *obj = read_object((obj_vnum)vnum, VIRTUAL);
if (!obj) {
mudlog(NRM, LVL_IMMORT, TRUE, "RoomSave: read_object(vnum=%d) failed.", vnum);
/* Skip to next object/header or end-of-scope */
long backpos;
while (fgets(line, sizeof(line), fl)) {
if (line[0] == 'O' || line[0] == '.' || line[0] == 'E') {
backpos = -((long)strlen(line));
fseek(fl, backpos, SEEK_CUR);
break;
}
}
continue;
}
/* Apply core scalars */
GET_OBJ_TIMER(obj) = timer;
GET_OBJ_WEIGHT(obj) = weight;
GET_OBJ_COST(obj) = cost;
GET_OBJ_RENT(obj) = rent;
/* Clear array flags so missing slots don't keep proto bits */
#ifdef EF_ARRAY_MAX
# ifdef GET_OBJ_EXTRA_AR
for (int i = 0; i < EF_ARRAY_MAX; i++) GET_OBJ_EXTRA_AR(obj, i) = 0;
# else
for (int i = 0; i < EF_ARRAY_MAX; i++) GET_OBJ_EXTRA(obj)[i] = 0;
# endif
#endif
#ifdef TW_ARRAY_MAX
for (int i = 0; i < TW_ARRAY_MAX; i++) GET_OBJ_WEAR(obj)[i] = 0;
#endif
/* Read per-object lines until next 'O' or '.' or 'E' */
long backpos;
while (fgets(line, sizeof(line), fl)) {
if (line[0] == 'V') {
int idx, val;
if (sscanf(line, "V %d %d", &idx, &val) == 2) {
#ifdef NUM_OBJ_VAL_POSITIONS
if (idx >= 0 && idx < NUM_OBJ_VAL_POSITIONS) GET_OBJ_VAL(obj, idx) = val;
#else
if (idx >= 0 && idx < 6) GET_OBJ_VAL(obj, idx) = val;
#endif
}
continue;
} else if (line[0] == 'X') { /* extra flags */
int idx, val;
if (sscanf(line, "X %d %d", &idx, &val) == 2) {
#if defined(EF_ARRAY_MAX) && defined(GET_OBJ_EXTRA_AR)
if (idx >= 0 && idx < EF_ARRAY_MAX) GET_OBJ_EXTRA_AR(obj, idx) = val;
#elif defined(EF_ARRAY_MAX)
if (idx >= 0 && idx < EF_ARRAY_MAX) GET_OBJ_EXTRA(obj)[idx] = val;
#else
if (idx == 0) GET_OBJ_EXTRA(obj) = val;
#endif
}
continue;
} else if (line[0] == 'W') { /* wear flags */
int idx, val;
if (sscanf(line, "W %d %d", &idx, &val) == 2) {
#ifdef TW_ARRAY_MAX
if (idx >= 0 && idx < TW_ARRAY_MAX) GET_OBJ_WEAR(obj)[idx] = val;
#else
if (idx == 0) GET_OBJ_WEAR(obj) = val;
#endif
}
continue;
} else if (line[0] == 'B') {
/* Nested contents until matching 'E' */
struct obj_data *child_head = roomsave_read_list(fl);
/* CRITICAL FIX: detach each node before obj_to_obj(), otherwise we lose siblings */
for (struct obj_data *it = child_head, *next; it; it = next) {
next = it->next_content; /* remember original sibling */
it->next_content = NULL; /* detach from temp list */
obj_to_obj(it, obj); /* push into container (LIFO) */
}
continue;
} else if (line[0] == 'O' || line[0] == '.' || line[0] == 'E') {
/* Next object / end-of-scope: rewind one line for outer loop to see it */
backpos = -((long)strlen(line));
fseek(fl, backpos, SEEK_CUR);
break;
} else {
/* ignore unknown lines (e.g., stray '...') */
continue;
}
}
/* Append to this scope's list */
obj->next_content = NULL;
if (!head) head = tail = obj;
else { tail->next_content = obj; tail = obj; }
}
return head;
}
/* ---------- Minimal line format ----------
#R <vnum> <unix_time>
O <vnum> <timer> <extra_flags> <wear_flags> <weight> <cost> <rent>
V <i> <val[i]> ; repeated for all value slots present on this obj
B ; begin contents of this object (container)
E ; end contents of this object
. ; end of room
------------------------------------------ */
static void ensure_dir_exists(const char *path) {
if (mkdir(path, 0775) == -1 && errno != EEXIST) {
mudlog(CMP, LVL_IMMORT, TRUE, "SYSERR: roomsave mkdir(%s): %s", path, strerror(errno));
}
}
/* zone vnum for a given room rnum (e.g., 134 -> zone 1) */
static int roomsave_zone_for_rnum(room_rnum rnum) {
if (rnum == NOWHERE) return -1;
if (world[rnum].zone < 0 || world[rnum].zone > top_of_zone_table) return -1;
return zone_table[ world[rnum].zone ].number; /* zone virtual number (e.g., 1) */
}
/* lib/world/rsv/<zone>.rsv */
static void roomsave_zone_filename(int zone_vnum, char *out, size_t outsz) {
snprintf(out, outsz, "%s%d%s", ROOMSAVE_PREFIX, zone_vnum, ROOMSAVE_EXT);
}
/* Write one object (and its recursive contents) */
static void write_one_object(FILE *fl, struct obj_data *obj) {
int i;
/* Core scalars (flags printed separately per-slot) */
fprintf(fl, "O %d %d %d %d %d\n",
GET_OBJ_VNUM(obj),
GET_OBJ_TIMER(obj),
GET_OBJ_WEIGHT(obj),
GET_OBJ_COST(obj),
GET_OBJ_RENT(obj));
/* Extra flags array */
#if defined(EF_ARRAY_MAX) && defined(GET_OBJ_EXTRA_AR)
for (i = 0; i < EF_ARRAY_MAX; i++)
fprintf(fl, "X %d %d\n", i, GET_OBJ_EXTRA_AR(obj, i));
#elif defined(EF_ARRAY_MAX)
for (i = 0; i < EF_ARRAY_MAX; i++)
fprintf(fl, "X %d %d\n", i, GET_OBJ_EXTRA(obj)[i]);
#else
fprintf(fl, "X %d %d\n", 0, GET_OBJ_EXTRA(obj));
#endif
/* Wear flags array */
#ifdef TW_ARRAY_MAX
for (i = 0; i < TW_ARRAY_MAX; i++)
fprintf(fl, "W %d %d\n", i, GET_OBJ_WEAR(obj)[i]);
#else
fprintf(fl, "W %d %d\n", 0, GET_OBJ_WEAR(obj));
#endif
/* Values[] (durability, liquids, charges, etc.) */
#ifdef NUM_OBJ_VAL_POSITIONS
for (i = 0; i < NUM_OBJ_VAL_POSITIONS; i++)
fprintf(fl, "V %d %d\n", i, GET_OBJ_VAL(obj, i));
#else
for (i = 0; i < 6; i++)
fprintf(fl, "V %d %d\n", i, GET_OBJ_VAL(obj, i));
#endif
/* Contents (recursive) */
if (obj->contains) {
struct obj_data *cont;
fprintf(fl, "B\n");
for (cont = obj->contains; cont; cont = cont->next_content)
write_one_object(fl, cont);
fprintf(fl, "E\n");
}
}
/* Public: write the entire rooms contents */
int RoomSave_now(room_rnum rnum) {
char path[PATH_MAX], tmp[PATH_MAX], line[512];
FILE *in = NULL, *out = NULL;
room_vnum rvnum;
int zvnum;
if (rnum == NOWHERE)
return 0;
rvnum = world[rnum].number;
zvnum = roomsave_zone_for_rnum(rnum);
if (zvnum < 0)
return 0;
ensure_dir_exists(ROOMSAVE_PREFIX);
roomsave_zone_filename(zvnum, path, sizeof(path));
/* Build temp file path safely (same dir as final file). */
{
int n = snprintf(tmp, sizeof(tmp), "%s.tmp", path);
if (n < 0 || n >= (int)sizeof(tmp)) {
mudlog(NRM, LVL_IMMORT, TRUE, "SYSERR: RoomSave: temp path too long for %s", path);
return 0;
}
}
/* Open output temp; copy existing zone file minus this rooms block */
if (!(out = fopen(tmp, "w"))) {
mudlog(NRM, LVL_IMMORT, TRUE, "SYSERR: RoomSave: fopen(%s) failed: %s", tmp, strerror(errno));
return 0;
}
if ((in = fopen(path, "r")) != NULL) {
while (fgets(line, sizeof(line), in)) {
if (strncmp(line, "#R ", 3) == 0) {
int file_rvnum;
long ts;
if (sscanf(line, "#R %d %ld", &file_rvnum, &ts) == 2) {
if (file_rvnum == (int)rvnum) {
/* Skip old block for this room until '.' line */
while (fgets(line, sizeof(line), in)) {
if (line[0] == '.')
break;
}
continue; /* do NOT write skipped lines */
}
}
}
/* Keep unrelated content */
fputs(line, out);
}
fclose(in);
}
/* Append fresh block for this room */
fprintf(out, "#R %d %ld\n", rvnum, (long)time(0));
/* Top-level room contents (write_one_object handles recursion into containers) */
for (struct obj_data *obj = world[rnum].contents; obj; obj = obj->next_content)
write_one_object(out, obj);
fprintf(out, ".\n");
/* Finish and atomically replace */
if (fclose(out) != 0) {
mudlog(NRM, LVL_IMMORT, TRUE, "SYSERR: RoomSave: fclose(%s) failed: %s", tmp, strerror(errno));
return 0;
}
if (rename(tmp, path) != 0) {
mudlog(NRM, LVL_IMMORT, TRUE, "SYSERR: RoomSave: rename(%s -> %s) failed: %s", tmp, path, strerror(errno));
/* Leave tmp in place for debugging */
return 0;
}
return 1;
}
void RoomSave_boot(void) {
DIR *dirp;
struct dirent *dp;
ensure_dir_exists(ROOMSAVE_PREFIX);
dirp = opendir(ROOMSAVE_PREFIX);
if (!dirp) {
mudlog(NRM, LVL_IMMORT, TRUE, "SYSERR: RoomSave_boot: cannot open %s", ROOMSAVE_PREFIX);
return;
}
log("RoomSave: scanning %s for *.rsv", ROOMSAVE_PREFIX);
while ((dp = readdir(dirp))) {
size_t n = strlen(dp->d_name);
if (n < 5) continue; /* skip . .. */
if (strcmp(dp->d_name + n - 4, ROOMSAVE_EXT) != 0) continue;
char path[PATH_MAX];
int wn = snprintf(path, sizeof(path), "%s%s", ROOMSAVE_PREFIX, dp->d_name);
if (wn < 0 || wn >= (int)sizeof(path)) {
mudlog(NRM, LVL_IMMORT, TRUE, "SYSERR: RoomSave_boot: path too long: %s%s", ROOMSAVE_PREFIX, dp->d_name);
continue;
}
FILE *fl = fopen(path, "r");
if (!fl) {
mudlog(NRM, LVL_IMMORT, TRUE, "SYSERR: RoomSave_boot: fopen(%s) failed: %s", path, strerror(errno));
continue;
}
log("RoomSave: reading %s", path);
char line[512];
int blocks = 0, restored_total = 0;
while (fgets(line, sizeof(line), fl)) {
if (!strncmp(line, "#R ", 3)) {
int rvnum; long ts;
if (sscanf(line, "#R %d %ld", &rvnum, &ts) != 2) {
mudlog(NRM, LVL_IMMORT, TRUE, "RoomSave: malformed #R header in %s: %s", path, line);
/* skip to next block end */
while (fgets(line, sizeof(line), fl)) if (line[0] == '.') break;
continue;
}
room_rnum rnum = real_room((room_vnum)rvnum);
blocks++;
if (rnum == NOWHERE) {
mudlog(NRM, LVL_IMMORT, FALSE, "RoomSave: unknown room vnum %d in %s (skipping)", rvnum, path);
while (fgets(line, sizeof(line), fl)) if (line[0] == '.') break;
continue;
}
/* Clear this room's current contents */
while (world[rnum].contents)
extract_obj(world[rnum].contents);
/* Read list between this #R and '.' */
struct obj_data *list = roomsave_read_list(fl);
int count = 0;
for (struct obj_data *it = list; it; ) {
struct obj_data *next = it->next_content;
it->next_content = NULL;
obj_to_room(it, rnum);
count++;
it = next;
}
restored_total += count;
log("RoomSave: room %d <- %d object(s)", rvnum, count);
}
}
log("RoomSave: finished %s (blocks=%d, restored=%d)", path, blocks, restored_total);
fclose(fl);
}
closedir(dirp);
}
/* Save all rooms flagged ROOM_SAVE. Called from point_update() on a cadence. */
void RoomSave_autosave_tick(void) {
for (room_rnum rnum = 0; rnum <= top_of_world; rnum++) {
if (ROOM_FLAGGED(rnum, ROOM_SAVE)) {
RoomSave_now(rnum);
}
}
}

28
src/roomsave.h Normal file
View file

@ -0,0 +1,28 @@
/**
* @file roomsave.h
* Core structures used within the core mud code.
*
* An addition to the core tbaMUD source code distribution, which is a derivative
* of, and continuation of, CircleMUD.
*
* All rights reserved. See license for complete information.
* Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University
* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.
*/
#ifndef ROOMSAVE_H_
#define ROOMSAVE_H_
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
/* Boot-time loader: scans ROOMSAVE_DIR and restores contents into rooms. */
void RoomSave_boot(void);
/* Immediate save for a single room (room_rnum). Safe to call anytime. */
int RoomSave_now(room_rnum rnum);
/* Autosave pass for all rooms flagged ROOM_SAVE. */
void RoomSave_autosave_tick(void);
#endif /* ROOMSAVE_H_ */

View file

@ -89,8 +89,9 @@
#define ROOM_BFS_MARK 15 /**< (R) breath-first srch mrk */
#define ROOM_WORLDMAP 16 /**< World-map style maps here */
#define ROOM_QUIT 17 /**< Room allows players to quit in it */
#define ROOM_SAVE 18 /**< Room saves object contents to it periodically */
/** The total number of Room Flags */
#define NUM_ROOM_FLAGS 18
#define NUM_ROOM_FLAGS 19
/* Zone info: Used in zone_data.zone_flags */
#define ZONE_CLOSED 0 /**< Zone is closed - players cannot enter */