From 21c66a16ae4a0cdac2c34dba396ebacd7e141d1c Mon Sep 17 00:00:00 2001 From: kinther Date: Sat, 6 Sep 2025 15:39:42 -0700 Subject: [PATCH] Add in quit flag and fix quit logic --- lib/world/wld/1.wld | 4 +-- src/act.informative.c | 26 ++++++++++------ src/act.other.c | 71 ++++++++++++++++++++++++++++++++++++++++--- src/comm.c | 4 --- src/constants.c | 1 + src/interpreter.c | 57 ++++++++++++++++------------------ src/objsave.c | 34 ++++++++++++++++----- src/structs.h | 3 +- 8 files changed, 141 insertions(+), 59 deletions(-) diff --git a/lib/world/wld/1.wld b/lib/world/wld/1.wld index 3c37afd..0e2f7bf 100644 --- a/lib/world/wld/1.wld +++ b/lib/world/wld/1.wld @@ -492,7 +492,7 @@ S Draqoman Station~ This unfinished room was created by Kinther. ~ -1 0 0 0 0 0 +1 131072 0 0 0 0 D1 ~ ~ @@ -530,7 +530,7 @@ S The Golden Inix Inn~ This unfinished room was created by Kinther. ~ -1 0 0 0 0 0 +1 131072 0 0 0 0 D0 ~ ~ diff --git a/src/act.informative.c b/src/act.informative.c index 4611ac1..5287b3e 100644 --- a/src/act.informative.c +++ b/src/act.informative.c @@ -495,7 +495,7 @@ void look_at_room(struct char_data *ch, int ignore_brief) if (!ch->desc) return; - if (IS_DARK(IN_ROOM(ch)) && !CAN_SEE_IN_DARK(ch)){ + if (IS_DARK(IN_ROOM(ch)) && !CAN_SEE_IN_DARK(ch)) { send_to_char(ch, "It is pitch black...\r\n"); return; } @@ -510,7 +510,12 @@ void look_at_room(struct char_data *ch, int ignore_brief) sprintbitarray(ROOM_FLAGS(IN_ROOM(ch)), room_bits, RF_ARRAY_MAX, buf); send_to_char(ch, "[%5d] ", GET_ROOM_VNUM(IN_ROOM(ch))); - send_to_char(ch, "%s[ %s][ %s ]", world[IN_ROOM(ch)].name, buf, sector_types[world[IN_ROOM(ch)].sector_type]); + send_to_char(ch, "%s[ %s][ %s ]", + world[IN_ROOM(ch)].name, + buf, + sector_types[world[IN_ROOM(ch)].sector_type]); + + /* Do NOT append [Quit] here; QUITSAFE already shows in the flags list */ if (SCRIPT(rm)) { send_to_char(ch, "[T"); @@ -519,25 +524,28 @@ void look_at_room(struct char_data *ch, int ignore_brief) send_to_char(ch, "]"); } } - else - send_to_char(ch, "%s", world[IN_ROOM(ch)].name); + else { + /* For normal players (no PRF_SHOWVNUMS), append [Quit] to the title line */ + send_to_char(ch, "%s%s", + world[IN_ROOM(ch)].name, + ROOM_FLAGGED(IN_ROOM(ch), ROOM_QUIT) ? " [Quit]" : ""); + } send_to_char(ch, "%s\r\n", CCNRM(ch, C_NRM)); if ((!IS_NPC(ch) && !PRF_FLAGGED(ch, PRF_BRIEF)) || ignore_brief || - ROOM_FLAGGED(IN_ROOM(ch), ROOM_DEATH)) { + ROOM_FLAGGED(IN_ROOM(ch), ROOM_DEATH)) { if (!IS_NPC(ch) && PRF_FLAGGED(ch, PRF_AUTOMAP) && can_see_map(ch)) str_and_map(world[target_room].description, ch, target_room); else - send_to_char(ch, "%s", world[IN_ROOM(ch)].description); + send_to_char(ch, "%s", world[IN_ROOM(ch)].description); } - - /*autoexits */ + /* autoexits */ if (!IS_NPC(ch) && PRF_FLAGGED(ch, PRF_AUTOEXIT)) do_auto_exits(ch); - /*now list characters &objects */ + /* now list characters & objects */ list_obj_to_char(world[IN_ROOM(ch)].contents, ch, SHOW_OBJ_LONG, FALSE); list_char_to_char(world[IN_ROOM(ch)].people, ch); } diff --git a/src/act.other.c b/src/act.other.c index 73e8f3f..b54895d 100644 --- a/src/act.other.c +++ b/src/act.other.c @@ -37,19 +37,74 @@ static void display_group_list(struct char_data * ch); ACMD(do_quit) { + char first[MAX_INPUT_LENGTH]; + char *rest; + if (IS_NPC(ch) || !ch->desc) return; - if (subcmd != SCMD_QUIT && GET_LEVEL(ch) < LVL_IMMORT) + /* Parse optional "ooc" sub-arg: quit ooc */ + rest = (char *)argument; + skip_spaces(&rest); + rest = one_argument(rest, first); + bool quit_ooc = (*first && is_abbrev(first, "ooc")) ? TRUE : FALSE; + + /* Keep original safety controls */ + if (!quit_ooc && subcmd != SCMD_QUIT && GET_LEVEL(ch) < LVL_IMMORT) send_to_char(ch, "You have to type quit--no less, to quit!\r\n"); else if (GET_POS(ch) == POS_FIGHTING) send_to_char(ch, "No way! You're fighting for your life!\r\n"); else if (GET_POS(ch) < POS_STUNNED) { send_to_char(ch, "You die before your time...\r\n"); die(ch, NULL); - } else { + } + /* New: normal quit must be in a QUIT room (mortals only). */ + else if (!quit_ooc && GET_LEVEL(ch) < LVL_IMMORT && + !ROOM_FLAGGED(IN_ROOM(ch), ROOM_QUIT)) { + send_to_char(ch, "You cannot quit here. Find a room marked [Quit].\r\n"); + } + /* For quit ooc, require a message for staff context. */ + else if (quit_ooc) { + skip_spaces(&rest); + if (!*rest) { + send_to_char(ch, "Usage must include a reason to quit ooc: quit ooc \r\n"); + return; + } + act("$n has left the game.", TRUE, ch, 0, 0, TO_ROOM); - mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s has quit the game.", GET_NAME(ch)); + mudlog(CMP, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, + "%s used QUIT OOC in room %d: %s", + GET_NAME(ch), GET_ROOM_VNUM(IN_ROOM(ch)), rest); + + if (GET_QUEST_TIME(ch) != -1) + quest_timeout(ch); + + send_to_char(ch, "You step out-of-character and leave the world...\r\n"); + + /* We used to check here for duping attempts, but we may as well do it right + * in extract_char(), since there is no check if a player rents out and it + * can leave them in an equally screwy situation. */ + + if (CONFIG_FREE_RENT) + Crash_rentsave(ch, 0); + + /* Requirement: respawn in the same (possibly non-QUIT) room. */ + GET_LOADROOM(ch) = GET_ROOM_VNUM(IN_ROOM(ch)); + + /* Stop snooping so you can't see passwords during deletion or change. */ + if (ch->desc->snoop_by) { + write_to_output(ch->desc->snoop_by, "Your victim is no longer among us.\r\n"); + ch->desc->snoop_by->snooping = NULL; + ch->desc->snoop_by = NULL; + } + + extract_char(ch); /* Char is saved before extracting. */ + } + else { + /* Normal quit (in a QUIT room, or immortal bypass) */ + act("$n has left the game.", TRUE, ch, 0, 0, TO_ROOM); + mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, + "%s has quit the game.", GET_NAME(ch)); if (GET_QUEST_TIME(ch) != -1) quest_timeout(ch); @@ -63,6 +118,7 @@ ACMD(do_quit) if (CONFIG_FREE_RENT) Crash_rentsave(ch, 0); + /* Requirement: respawn in the same QUIT room they logged out in. */ GET_LOADROOM(ch) = GET_ROOM_VNUM(IN_ROOM(ch)); /* Stop snooping so you can't see passwords during deletion or change. */ @@ -72,7 +128,7 @@ ACMD(do_quit) ch->desc->snoop_by = NULL; } - extract_char(ch); /* Char is saved before extracting. */ + extract_char(ch); /* Char is saved before extracting. */ } } @@ -82,11 +138,16 @@ ACMD(do_save) return; 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))); - GET_LOADROOM(ch) = GET_ROOM_VNUM(IN_ROOM(ch)); } /* Generic function for commands which are normally overridden by special diff --git a/src/comm.c b/src/comm.c index 677fd2a..c0417c8 100644 --- a/src/comm.c +++ b/src/comm.c @@ -477,10 +477,6 @@ void copyover_recover() enter_player_game(d); - /* Clear their load room if it's not persistant. */ - if (!PLR_FLAGGED(d->character, PLR_LOADROOM)) - GET_LOADROOM(d->character) = NOWHERE; - d->connected = CON_PLAYING; look_at_room(d->character, 0); diff --git a/src/constants.c b/src/constants.c index 073aad1..e646782 100644 --- a/src/constants.c +++ b/src/constants.c @@ -83,6 +83,7 @@ const char *room_bits[] = { "OLC", "*", /* The BFS Mark. */ "WORLDMAP", + "QUIT", "\n" }; diff --git a/src/interpreter.c b/src/interpreter.c index b3f57e4..92a788c 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -215,7 +215,6 @@ cpp_extern const struct command_info cmd_info[] = { { "open" , "o" , POS_SITTING , do_gen_door , 0, SCMD_OPEN }, { "order" , "ord" , POS_RESTING , do_order , 1, 0 }, - { "offer" , "off" , POS_STANDING, do_not_here , 1, 0 }, { "olc" , "olc" , POS_DEAD , do_show_save_list, LVL_BUILDER, 0 }, { "olist" , "olist" , POS_DEAD , do_oasis_list, LVL_BUILDER, SCMD_OASIS_OLIST }, { "oedit" , "oedit" , POS_DEAD , do_oasis_oedit, LVL_BUILDER, 0 }, @@ -253,7 +252,6 @@ cpp_extern const struct command_info cmd_info[] = { { "receive" , "rece" , POS_STANDING, do_not_here , 1, 0 }, { "recent" , "recent" , POS_DEAD , do_recent , LVL_IMMORT, 0 }, { "remove" , "rem" , POS_RESTING , do_remove , 0, 0 }, - { "rent" , "rent" , POS_STANDING, do_not_here , 1, 0 }, { "report" , "repo" , POS_RESTING , do_report , 0, 0 }, { "reroll" , "rero" , POS_DEAD , do_wizutil , LVL_GRGOD, SCMD_REROLL }, { "rescue" , "resc" , POS_FIGHTING, do_rescue , 1, 0 }, @@ -1220,50 +1218,50 @@ static bool perform_new_char_dupe_check(struct descriptor_data *d) int enter_player_game (struct descriptor_data *d) { int load_result; - room_vnum load_room; + room_vnum saved_vnum; /* cache pfile value BEFORE any mutations */ + room_rnum start_r = NOWHERE; + + if (!d || !d->character) + return 0; + + /* Cache the pfile's saved load room FIRST. */ + saved_vnum = GET_LOADROOM(d->character); reset_char(d->character); if (PLR_FLAGGED(d->character, PLR_INVSTART)) GET_INVIS_LEV(d->character) = GET_LEVEL(d->character); - /* We have to place the character in a room before equipping them - * or equip_char() will gripe about the person in NOWHERE. */ - if ((load_room = GET_LOADROOM(d->character)) != NOWHERE) - load_room = real_room(load_room); + /* Resolve the room to enter (use cached vnum -> rnum). */ + if (saved_vnum != NOWHERE) + start_r = real_room(saved_vnum); - /* If char was saved with NOWHERE, or real_room above failed... */ - if (load_room == NOWHERE) { - if (GET_LEVEL(d->character) >= LVL_IMMORT) - load_room = r_immort_start_room; + /* Fallbacks if invalid/missing. */ + if (start_r == NOWHERE) { + if (PLR_FLAGGED(d->character, PLR_FROZEN)) + start_r = r_frozen_start_room; + else if (GET_LEVEL(d->character) >= LVL_IMMORT) + start_r = r_immort_start_room; else - load_room = r_mortal_start_room; + start_r = r_mortal_start_room; } - if (PLR_FLAGGED(d->character, PLR_FROZEN)) - load_room = r_frozen_start_room; - - /* copyover */ + /* DG Scripts setup */ d->character->script_id = GET_IDNUM(d->character); - /* find_char helper */ add_to_lookup_table(d->character->script_id, (void *)d->character); - - /* After moving saving of variables to the player file, this should only - * be called in case nothing was found in the pfile. If something was - * found, SCRIPT(ch) will be set. */ if (!SCRIPT(d->character)) read_saved_vars(d->character); + /* Link character and place before equipping. */ d->character->next = character_list; character_list = d->character; - char_to_room(d->character, load_room); - load_result = Crash_load(d->character); - - /* Save the character and their object file */ - save_char(d->character); - Crash_crashsave(d->character); + char_to_room(d->character, start_r); - /* Check for a login trigger in the players' start room */ + /* Load inventory/equipment */ + load_result = Crash_load(d->character); + + /* DO NOT save here — avoids clobbering Room with NOWHERE on login. */ + /* login_wtrigger can remain. */ login_wtrigger(&world[IN_ROOM(d->character)], d->character); return load_result; @@ -1649,9 +1647,6 @@ void nanny(struct descriptor_data *d, char *arg) load_result = enter_player_game(d); send_to_char(d->character, "%s", CONFIG_WELC_MESSG); - /* Clear their load room if it's not persistant. */ - if (!PLR_FLAGGED(d->character, PLR_LOADROOM)) - GET_LOADROOM(d->character) = NOWHERE; save_char(d->character); greet_mtrigger(d->character, -1); diff --git a/src/objsave.c b/src/objsave.c index ff7fb47..edec217 100644 --- a/src/objsave.c +++ b/src/objsave.c @@ -963,14 +963,34 @@ SPECIAL(cryogenicist) void Crash_save_all(void) { struct descriptor_data *d; + + /* Respect config: if autosave is off, do nothing. */ + if (!CONFIG_AUTO_SAVE) + return; + for (d = descriptor_list; d; d = d->next) { - if ((STATE(d) == CON_PLAYING) && !IS_NPC(d->character)) { - if (PLR_FLAGGED(d->character, PLR_CRASH)) { - Crash_crashsave(d->character); - save_char(d->character); - REMOVE_BIT_AR(PLR_FLAGS(d->character), PLR_CRASH); - } - } + if (STATE(d) != CON_PLAYING) + continue; + if (!d->character || IS_NPC(d->character)) + continue; + + /* Skip characters not fully placed yet (prevents clobbering Room to 65535). */ + if (IN_ROOM(d->character) == NOWHERE) + continue; + + /* Optional hardening: if spawn vnum is not yet established, skip this tick. */ + if (GET_LOADROOM(d->character) == NOWHERE) + continue; + + /* IMPORTANT: Do NOT modify GET_LOADROOM here. + Autosave should not change the player's spawn point. */ + + /* Persist character and object file. */ + save_char(d->character); + Crash_crashsave(d->character); + + if (PLR_FLAGGED(d->character, PLR_CRASH)) + REMOVE_BIT_AR(PLR_FLAGS(d->character), PLR_CRASH); } } diff --git a/src/structs.h b/src/structs.h index ce10637..6ed2711 100644 --- a/src/structs.h +++ b/src/structs.h @@ -88,8 +88,9 @@ #define ROOM_OLC 14 /**< (R) Modifyable/!compress */ #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 */ /** The total number of Room Flags */ -#define NUM_ROOM_FLAGS 17 +#define NUM_ROOM_FLAGS 18 /* Zone info: Used in zone_data.zone_flags */ #define ZONE_CLOSED 0 /**< Zone is closed - players cannot enter */