diff --git a/src/act.other.c b/src/act.other.c index 93c7460..005e8ed 100644 --- a/src/act.other.c +++ b/src/act.other.c @@ -137,12 +137,12 @@ ACMD(do_save) { char a1[MAX_INPUT_LENGTH], a2[MAX_INPUT_LENGTH]; - /* Parse up to two words so we can accept "save room" or "room save". */ + /* Accept both orders: "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"))); + /* order-agnostic check */ + int wants_room = ((*a1 && !str_cmp(a1, "room")) || + (*a2 && !str_cmp(a2, "room"))); if (wants_room) { room_rnum rnum = IN_ROOM(ch); @@ -163,8 +163,20 @@ ACMD(do_save) /* Room is flagged SAVE → persist its contents */ if (RoomSave_now(rnum)) { send_to_char(ch, "Saving room.\r\n"); + mudlog(NRM, LVL_IMMORT, FALSE, + "RoomSave: manual save of room %d by %s.", + world[rnum].number, GET_NAME(ch)); + /* If you added a dirty-save API and want to clear the bit on manual save, + you can optionally call it here (guard with a macro if desired): + #ifdef ROOMSAVE_HAVE_DIRTY_API + RoomSave_clear_dirty(rnum); + #endif + */ } else { send_to_char(ch, "Room save failed; see logs.\r\n"); + mudlog(NRM, LVL_IMMORT, TRUE, + "SYSERR: RoomSave: manual save FAILED for room %d by %s.", + world[rnum].number, GET_NAME(ch)); } return; } diff --git a/src/db.c b/src/db.c index 526cd7f..1d3e4fa 100644 --- a/src/db.c +++ b/src/db.c @@ -784,7 +784,8 @@ void boot_db(void) } /* Restore persistent room contents last so they take precedence. */ - log("Loading Room Contents."); + log("Loading Room Contents."); + RoomSave_init_dirty(); RoomSave_boot(); log("Cleaning up last log."); diff --git a/src/handler.c b/src/handler.c index 87269bb..1f4f53e 100644 --- a/src/handler.c +++ b/src/handler.c @@ -24,6 +24,7 @@ #include "fight.h" #include "quest.h" #include "mud_event.h" +#include "roomsave.h" /* local file scope variables */ static int extractions_pending = 0; @@ -419,11 +420,15 @@ void char_to_room(struct char_data *ch, room_rnum room) /* Give an object to a char. */ void obj_to_char(struct obj_data *object, struct char_data *ch) { + room_rnum __rs_room = RoomSave_room_of_obj(object); /* where the item currently lives */ + if (object && ch) { object->next_content = ch->carrying; ch->carrying = object; object->carried_by = ch; IN_ROOM(object) = NOWHERE; + if (__rs_room != NOWHERE) + RoomSave_mark_dirty_room(__rs_room); IS_CARRYING_W(ch) += GET_OBJ_WEIGHT(object); IS_CARRYING_N(ch)++; @@ -440,6 +445,7 @@ void obj_to_char(struct obj_data *object, struct char_data *ch) void obj_from_char(struct obj_data *object) { struct obj_data *temp; + room_rnum __rs_room = IN_ROOM(object->carried_by); if (object == NULL) { log("SYSERR: NULL object passed to obj_from_char."); @@ -455,6 +461,8 @@ void obj_from_char(struct obj_data *object) IS_CARRYING_N(object->carried_by)--; object->carried_by = NULL; object->next_content = NULL; + if (__rs_room != NOWHERE) + RoomSave_mark_dirty_room(__rs_room); } /* Return the effect of a piece of armor in position eq_pos */ @@ -689,6 +697,8 @@ void obj_to_room(struct obj_data *object, room_rnum room) object->carried_by = NULL; if (ROOM_FLAGGED(room, ROOM_HOUSE)) SET_BIT_AR(ROOM_FLAGS(room), ROOM_HOUSE_CRASH); + /* RoomSave: this room’s contents changed */ + RoomSave_mark_dirty_room(room); } } @@ -697,6 +707,7 @@ void obj_from_room(struct obj_data *object) { struct obj_data *temp; struct char_data *t, *tempch; + room_rnum __rs_was_room = IN_ROOM(object); if (!object || IN_ROOM(object) == NOWHERE) { log("SYSERR: NULL object (%p) or obj not in a room (%d) passed to obj_from_room", @@ -719,6 +730,8 @@ void obj_from_room(struct obj_data *object) SET_BIT_AR(ROOM_FLAGS(IN_ROOM(object)), ROOM_HOUSE_CRASH); IN_ROOM(object) = NOWHERE; object->next_content = NULL; + /* RoomSave: room lost an object */ + RoomSave_mark_dirty_room(__rs_was_room); } /* put an object in an object (quaint) */ @@ -735,6 +748,8 @@ void obj_to_obj(struct obj_data *obj, struct obj_data *obj_to) obj->next_content = obj_to->contains; obj_to->contains = obj; obj->in_obj = obj_to; + /* RoomSave: container changed; mark the room the container ultimately lives in */ + RoomSave_mark_dirty_room(RoomSave_room_of_obj(obj_to)); /* Add weight to container, unless unlimited. */ if (GET_OBJ_VAL(obj->in_obj, 0) > 0) { @@ -752,6 +767,7 @@ void obj_to_obj(struct obj_data *obj, struct obj_data *obj_to) void obj_from_obj(struct obj_data *obj) { struct obj_data *temp, *obj_from; + struct obj_data *__rs_container = obj->in_obj; /* capture before unlink */ if (obj->in_obj == NULL) { log("SYSERR: (%s): trying to illegally extract obj from obj.", __FILE__); @@ -772,6 +788,8 @@ void obj_from_obj(struct obj_data *obj) } obj->in_obj = NULL; obj->next_content = NULL; + /* RoomSave: container changed; mark the room the container ultimately lives in */ + RoomSave_mark_dirty_room(RoomSave_room_of_obj(__rs_container)); } /* Set all carried_by to point to new owner */ @@ -789,6 +807,7 @@ void extract_obj(struct obj_data *obj) { struct char_data *ch, *next = NULL; struct obj_data *temp; + room_rnum __rs_room = RoomSave_room_of_obj(obj); if (obj->worn_by != NULL) if (unequip_char(obj->worn_by, obj->worn_on) != obj) @@ -837,6 +856,9 @@ void extract_obj(struct obj_data *obj) if (GET_OBJ_RNUM(obj) == NOTHING || obj->proto_script != obj_proto[GET_OBJ_RNUM(obj)].proto_script) free_proto_script(obj, OBJ_TRIGGER); + if (__rs_room != NOWHERE) + RoomSave_mark_dirty_room(__rs_room); + free_obj(obj); } diff --git a/src/roomsave.c b/src/roomsave.c index 6d2845a..8493d16 100644 --- a/src/roomsave.c +++ b/src/roomsave.c @@ -13,6 +13,7 @@ #include "conf.h" #include "sysdep.h" +#include #include #include #include @@ -36,6 +37,28 @@ #define ROOMSAVE_EXT ".rsv" #endif +static unsigned char *roomsave_dirty = NULL; + +void RoomSave_init_dirty(void) { + free(roomsave_dirty); + roomsave_dirty = calloc((size_t)top_of_world + 1, 1); +} + +void RoomSave_mark_dirty_room(room_rnum rnum) { + if (!roomsave_dirty) return; + if (rnum != NOWHERE && rnum >= 0 && rnum <= top_of_world) + roomsave_dirty[rnum] = 1; +} + +/* Where does an object “live” (topmost location -> room)? */ +room_rnum RoomSave_room_of_obj(struct obj_data *o) { + if (!o) return NOWHERE; + while (o->in_obj) o = o->in_obj; + if (o->carried_by) return IN_ROOM(o->carried_by); + if (o->worn_by) return IN_ROOM(o->worn_by); + return o->in_room; +} + /* --- 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]; @@ -397,8 +420,9 @@ void RoomSave_boot(void) { /* 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); - } + if (!ROOM_FLAGGED(rnum, ROOM_SAVE)) continue; + if (!roomsave_dirty || !roomsave_dirty[rnum]) continue; + if (RoomSave_now(rnum)) + roomsave_dirty[rnum] = 0; } -} \ No newline at end of file +} diff --git a/src/roomsave.h b/src/roomsave.h index a31332b..c754743 100644 --- a/src/roomsave.h +++ b/src/roomsave.h @@ -25,4 +25,10 @@ int RoomSave_now(room_rnum rnum); /* Autosave pass for all rooms flagged ROOM_SAVE. */ void RoomSave_autosave_tick(void); +/* Only save rooms when modified */ +void RoomSave_init_dirty(void); +void RoomSave_mark_dirty_room(room_rnum rnum); +/* For container edits: find the room an object ultimately lives in */ +room_rnum RoomSave_room_of_obj(struct obj_data *obj); + #endif /* ROOMSAVE_H_ */