tbamud/src/set.c

4054 lines
102 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file set.c
* Builder room/object creation and utility functions.
*
* This set of code was not originally part of the circlemud distribution.
*/
#include "conf.h"
#include "sysdep.h"
#include <stdlib.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 "comm.h"
#include "interpreter.h"
#include "spells.h"
#include "handler.h"
#include "db.h"
#include "constants.h"
#include "boards.h"
#include "genolc.h"
#include "genwld.h"
#include "genzon.h"
#include "oasis.h"
#include "improved-edit.h"
#include "modify.h"
#include "genobj.h"
#include "genmob.h"
#include "dg_scripts.h"
#include "fight.h"
#include "toml.h"
#include "set.h"
static void rset_show_usage(struct char_data *ch)
{
send_to_char(ch,
"Usage:\r\n"
" rset show\r\n"
" rset add name <text>\r\n"
" rset add sector <sector>\r\n"
" rset add flags <flag> [flag ...]\r\n"
" rset add exit <direction> <room number>\r\n"
" rset add door <direction> <name of door>\r\n"
" rset add key <direction> <key number>\r\n"
" rset add hidden <direction>\r\n"
" rset add forage <object vnum> <dc check>\r\n"
" rset add edesc <keyword> <description>\r\n"
" rset add desc\r\n"
" rset del <field>\r\n"
" rset clear force\r\n"
" rset validate\r\n");
}
static void rset_show_set_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds basic configuration to the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset add name <text>\r\n"
" rset add sector <sector>\r\n"
"\r\n"
"Examples:\r\n"
" rset add name \"A wind-scoured alley\"\r\n"
" rset add sector desert\r\n");
}
static void rset_show_set_sector_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds room sector type.\r\n"
"\r\n"
"Usage:\r\n"
" rset add sector <sector>\r\n"
"\r\n"
"Examples:\r\n"
" rset add sector desert\r\n"
"\r\n"
"Sectors:\r\n");
column_list(ch, 0, sector_types, NUM_ROOM_SECTORS, FALSE);
}
static void rset_show_add_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds specific configuration to the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset add name <text>\r\n"
" rset add sector <sector>\r\n"
" rset add flags <flag> [flag ...]\r\n"
" rset add exit <direction> <room number>\r\n"
" rset add door <direction> <name of door>\r\n"
" rset add key <direction> <key number>\r\n"
" rset add hidden <direction>\r\n"
" rset add forage <object vnum> <dc check>\r\n"
" rset add edesc <keyword> <description>\r\n"
" rset add desc\r\n");
}
static void rset_show_del_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes specific configuration from the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset del flags <flag> [flag ...]\r\n"
" rset del exit <direction>\r\n"
" rset del door <direction>\r\n"
" rset del key <direction>\r\n"
" rset del hidden <direction>\r\n"
" rset del forage <object vnum>\r\n"
" rset del edesc <keyword>\r\n"
"\r\n"
"Examples:\r\n"
" rset del flags INDOORS QUITSAFE\r\n"
" rset del exit n\r\n"
" rset del door n\r\n"
" rset del key n\r\n"
" rset del hidden n\r\n"
" rset del forage 301\r\n"
" rset del edesc mosaic\r\n");
}
static void rset_show_validate_usage(struct char_data *ch)
{
send_to_char(ch,
"Verifies your new room meets building standards and looks for any errors. If\r\n"
"correct, you can use the rsave command to finish building.\r\n"
"\r\n"
"Usage:\r\n"
" rset validate\r\n"
"\r\n"
"Examples:\r\n"
" rset validate\r\n");
}
static void rset_show_desc_usage(struct char_data *ch)
{
send_to_char(ch,
"Enters text editor for editing the main description of the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset add desc\r\n");
}
static void rset_show_rcreate_usage(struct char_data *ch)
{
send_to_char(ch,
"Creates a new unfinished room which can be entered and configured.\r\n"
"\r\n"
"Usage:\r\n"
" rcreate <vnum>\r\n"
"\r\n"
"Examples:\r\n"
" rcreate 1001\r\n");
}
static void rset_show_add_flags_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds room flags.\r\n"
"\r\n"
"Usage:\r\n"
" rset add flags <flag> [flag ...]\r\n"
"\r\n"
"Examples:\r\n"
" rset add flags INDOORS QUITSAFE\r\n"
"\r\n"
"Flags:\r\n");
column_list(ch, 0, room_bits, NUM_ROOM_FLAGS, FALSE);
}
static void rset_show_del_flags_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes room flags.\r\n"
"\r\n"
"Usage:\r\n"
" rset del flags <flag> [flag ...]\r\n"
"\r\n"
"Examples:\r\n"
" rset del flags INDOORS QUITSAFE\r\n"
"\r\n"
"Flags:\r\n");
column_list(ch, 0, room_bits, NUM_ROOM_FLAGS, FALSE);
}
static void rset_show_add_exit_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds an exit to the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset add exit <direction> <room number>\r\n"
"\r\n"
"Examples:\r\n"
" rset add exit n 101\r\n");
}
static void rset_show_add_door_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds a door to an existing exit.\r\n"
"\r\n"
"Usage:\r\n"
" rset add door <direction> <name of door>\r\n"
"\r\n"
"Examples:\r\n"
" rset add door n door\r\n");
}
static void rset_show_add_key_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds a key to an existing door.\r\n"
"\r\n"
"Usage:\r\n"
" rset add key <direction> <key number>\r\n"
"\r\n"
"Examples:\r\n"
" rset add key n 201\r\n");
}
static void rset_show_add_hidden_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds the hidden flag to an existing exit.\r\n"
"\r\n"
"Usage:\r\n"
" rset add hidden <direction>\r\n"
"\r\n"
"Examples:\r\n"
" rset add hidden n\r\n");
}
static void rset_show_add_forage_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds a forage entry to the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset add forage <object vnum> <dc check>\r\n"
"\r\n"
"Examples:\r\n"
" rset add forage 301 15\r\n");
}
static void rset_show_add_edesc_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds an extra description to the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset add edesc <keyword> <description>\r\n"
"\r\n"
"Examples:\r\n"
" rset add edesc mosaic A beautiful mosaic is here on the wall.\r\n");
}
static void rset_show_del_exit_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes an exit from the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset del exit <direction>\r\n"
"\r\n"
"Examples:\r\n"
" rset del exit n\r\n");
}
static void rset_show_del_door_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes a door from an existing exit.\r\n"
"\r\n"
"Usage:\r\n"
" rset del door <direction>\r\n"
"\r\n"
"Examples:\r\n"
" rset del door n\r\n");
}
static void rset_show_del_key_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes a key from an existing door.\r\n"
"\r\n"
"Usage:\r\n"
" rset del key <direction>\r\n"
"\r\n"
"Examples:\r\n"
" rset del key n\r\n");
}
static void rset_show_del_hidden_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes the hidden flag from an existing exit.\r\n"
"\r\n"
"Usage:\r\n"
" rset del hidden <direction>\r\n"
"\r\n"
"Examples:\r\n"
" rset del hidden n\r\n");
}
static void rset_show_del_forage_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes a forage entry from the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset del forage <object vnum>\r\n"
"\r\n"
"Examples:\r\n"
" rset del forage 301\r\n");
}
static void rset_show_del_edesc_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes an extra description from the room.\r\n"
"\r\n"
"Usage:\r\n"
" rset del edesc <keyword>\r\n"
"\r\n"
"Examples:\r\n"
" rset del edesc mosaic\r\n");
}
static int rset_find_room_flag(const char *arg)
{
int i;
for (i = 0; i < NUM_ROOM_FLAGS; i++)
if (is_abbrev(arg, room_bits[i]))
return i;
return -1;
}
static int rset_find_sector(const char *arg)
{
int i;
for (i = 0; i < NUM_ROOM_SECTORS; i++)
if (is_abbrev(arg, sector_types[i]))
return i;
return -1;
}
static int rset_find_dir(const char *arg)
{
char dirbuf[MAX_INPUT_LENGTH];
int dir;
strlcpy(dirbuf, arg, sizeof(dirbuf));
dir = search_block(dirbuf, dirs, FALSE);
if (dir == -1) {
strlcpy(dirbuf, arg, sizeof(dirbuf));
dir = search_block(dirbuf, autoexits, FALSE);
}
if (dir >= DIR_COUNT)
return -1;
return dir;
}
static void rset_mark_room_modified(room_rnum rnum)
{
if (rnum == NOWHERE || rnum < 0 || rnum > top_of_world)
return;
add_to_save_list(zone_table[world[rnum].zone].number, SL_WLD);
}
static void rset_show_room(struct char_data *ch, struct room_data *room)
{
char buf[MAX_STRING_LENGTH];
char buf2[MAX_STRING_LENGTH];
int i, count = 0;
send_to_char(ch, "Room [%d]: %s\r\n", room->number, room->name ? room->name : "<None>");
send_to_char(ch, "Zone [%d]: %s\r\n", zone_table[room->zone].number, zone_table[room->zone].name);
sprinttype(room->sector_type, sector_types, buf, sizeof(buf));
send_to_char(ch, "Sector: %s\r\n", buf);
sprintbitarray(room->room_flags, room_bits, RF_ARRAY_MAX, buf);
send_to_char(ch, "Flags: %s\r\n", buf);
send_to_char(ch, "Description:\r\n%s", room->description ? room->description : " <None>\r\n");
send_to_char(ch, "Exits:\r\n");
for (i = 0; i < DIR_COUNT; i++) {
struct room_direction_data *exit = room->dir_option[i];
room_rnum to_room;
const char *dir = dirs[i];
char keybuf[32];
if (!exit)
continue;
to_room = exit->to_room;
if (to_room == NOWHERE || to_room < 0 || to_room > top_of_world)
snprintf(buf2, sizeof(buf2), "NOWHERE");
else
snprintf(buf2, sizeof(buf2), "%d", world[to_room].number);
sprintbit(exit->exit_info, exit_bits, buf, sizeof(buf));
if (IS_SET(exit->exit_info, EX_HIDDEN))
strlcat(buf, "HIDDEN ", sizeof(buf));
if (exit->key == NOTHING)
strlcpy(keybuf, "None", sizeof(keybuf));
else
snprintf(keybuf, sizeof(keybuf), "%d", exit->key);
send_to_char(ch, " %-5s -> %s (door: %s, key: %s, flags: %s)\r\n",
dir,
buf2,
exit->keyword ? exit->keyword : "None",
keybuf,
buf);
count++;
}
if (!count)
send_to_char(ch, " None.\r\n");
send_to_char(ch, "Forage:\r\n");
count = 0;
for (struct forage_entry *entry = room->forage; entry; entry = entry->next) {
obj_rnum rnum = real_object(entry->obj_vnum);
const char *sdesc = (rnum != NOTHING) ? obj_proto[rnum].short_description : "Unknown object";
send_to_char(ch, " [%d] DC %d - %s\r\n", entry->obj_vnum, entry->dc, sdesc);
count++;
}
if (!count)
send_to_char(ch, " None.\r\n");
send_to_char(ch, "Extra Descs:\r\n");
count = 0;
for (struct extra_descr_data *desc = room->ex_description; desc; desc = desc->next) {
send_to_char(ch, " %s\r\n", desc->keyword ? desc->keyword : "<None>");
count++;
}
if (!count)
send_to_char(ch, " None.\r\n");
}
static void rset_desc_edit(struct char_data *ch, struct room_data *room)
{
char *oldtext = NULL;
send_editor_help(ch->desc);
write_to_output(ch->desc, "Enter room description:\r\n\r\n");
if (room->description) {
write_to_output(ch->desc, "%s", room->description);
oldtext = strdup(room->description);
}
string_write(ch->desc, &room->description, MAX_ROOM_DESC, 0, oldtext);
rset_mark_room_modified(IN_ROOM(ch));
}
static bool rset_room_has_flags(struct room_data *room)
{
int i;
for (i = 0; i < RF_ARRAY_MAX; i++)
if (room->room_flags[i])
return TRUE;
return FALSE;
}
static void rset_validate_room(struct char_data *ch, struct room_data *room)
{
int errors = 0;
int i;
if (!room->name || !*room->name) {
send_to_char(ch, "Error: room name is not set.\r\n");
errors++;
}
if (!room->description || !*room->description) {
send_to_char(ch, "Error: room description is not set.\r\n");
errors++;
}
if (room->sector_type < 0 || room->sector_type >= NUM_ROOM_SECTORS) {
send_to_char(ch, "Error: sector type is invalid.\r\n");
errors++;
}
if (!rset_room_has_flags(room)) {
send_to_char(ch, "Error: at least one room flag should be set.\r\n");
errors++;
}
for (i = 0; i < DIR_COUNT; i++) {
struct room_direction_data *exit = room->dir_option[i];
bool exit_valid;
if (!exit)
continue;
exit_valid = !(exit->to_room == NOWHERE || exit->to_room < 0 || exit->to_room > top_of_world);
if (!exit_valid) {
send_to_char(ch, "Error: exit %s does not point to a valid room.\r\n", dirs[i]);
errors++;
}
if (IS_SET(exit->exit_info, EX_ISDOOR) && !exit_valid) {
send_to_char(ch, "Error: door on %s has no valid exit.\r\n", dirs[i]);
errors++;
}
if (exit->key > 0) {
if (!IS_SET(exit->exit_info, EX_ISDOOR)) {
send_to_char(ch, "Error: key on %s is set without a door.\r\n", dirs[i]);
errors++;
}
if (real_object(exit->key) == NOTHING) {
send_to_char(ch, "Error: key vnum %d on %s does not exist.\r\n", exit->key, dirs[i]);
errors++;
}
}
if (IS_SET(exit->exit_info, EX_HIDDEN) && !exit_valid) {
send_to_char(ch, "Error: hidden flag on %s has no valid exit.\r\n", dirs[i]);
errors++;
}
}
for (struct forage_entry *entry = room->forage; entry; entry = entry->next) {
if (entry->obj_vnum <= 0 || real_object(entry->obj_vnum) == NOTHING) {
send_to_char(ch, "Error: forage object vnum %d is invalid.\r\n", entry->obj_vnum);
errors++;
}
if (entry->dc <= 0) {
send_to_char(ch, "Error: forage object vnum %d is missing a valid DC.\r\n", entry->obj_vnum);
errors++;
}
}
for (struct extra_descr_data *desc = room->ex_description; desc; desc = desc->next) {
if (!desc->keyword || !*desc->keyword) {
send_to_char(ch, "Error: extra description is missing a keyword.\r\n");
errors++;
}
if (!desc->description || !*desc->description) {
send_to_char(ch, "Error: extra description for %s is missing text.\r\n",
desc->keyword ? desc->keyword : "<None>");
errors++;
}
}
if (!errors)
send_to_char(ch, "Room validates cleanly.\r\n");
else
send_to_char(ch, "Validation failed: %d issue%s.\r\n", errors, errors == 1 ? "" : "s");
}
ACMD(do_rcreate)
{
char arg[MAX_INPUT_LENGTH];
char namebuf[MAX_INPUT_LENGTH];
char descbuf[MAX_STRING_LENGTH];
char timestr[64];
struct room_data room;
room_vnum vnum;
room_rnum rnum;
zone_rnum znum;
time_t now;
if (IS_NPC(ch) || ch->desc == NULL) {
send_to_char(ch, "rcreate is only usable by connected players.\r\n");
return;
}
argument = one_argument(argument, arg);
if (!*arg) {
rset_show_rcreate_usage(ch);
return;
}
if (!is_number(arg)) {
rset_show_rcreate_usage(ch);
return;
}
vnum = atoi(arg);
if (vnum <= 0) {
send_to_char(ch, "That is not a valid room vnum.\r\n");
return;
}
if (real_room(vnum) != NOWHERE) {
send_to_char(ch, "Room %d already exists.\r\n", vnum);
return;
}
if ((znum = real_zone_by_thing(vnum)) == NOWHERE) {
send_to_char(ch, "That room number is not in a valid zone.\r\n");
return;
}
if (!can_edit_zone(ch, znum)) {
send_to_char(ch, "You do not have permission to modify that zone.\r\n");
return;
}
now = time(0);
strftime(timestr, sizeof(timestr), "%c", localtime(&now));
snprintf(namebuf, sizeof(namebuf), "Unfinished room made by %s", GET_NAME(ch));
snprintf(descbuf, sizeof(descbuf),
"This is an unfinished room created by %s on %s\r\n",
GET_NAME(ch), timestr);
memset(&room, 0, sizeof(room));
room.number = vnum;
room.zone = znum;
room.sector_type = SECT_INSIDE;
room.name = strdup(namebuf);
room.description = strdup(descbuf);
room.ex_description = NULL;
room.forage = NULL;
rnum = add_room(&room);
free(room.name);
free(room.description);
if (rnum == NOWHERE) {
send_to_char(ch, "Room creation failed.\r\n");
return;
}
send_to_char(ch, "Room %d created.\r\n", vnum);
}
ACMD(do_rset)
{
char arg1[MAX_INPUT_LENGTH];
char arg2[MAX_INPUT_LENGTH];
char arg3[MAX_INPUT_LENGTH];
struct room_data *room;
room_rnum rnum;
if (IS_NPC(ch) || ch->desc == NULL) {
send_to_char(ch, "rset is only usable by connected players.\r\n");
return;
}
rnum = IN_ROOM(ch);
if (rnum == NOWHERE || rnum < 0 || rnum > top_of_world) {
send_to_char(ch, "You are not in a valid room.\r\n");
return;
}
if (!can_edit_zone(ch, world[rnum].zone)) {
send_to_char(ch, "You do not have permission to modify this zone.\r\n");
return;
}
room = &world[rnum];
argument = one_argument(argument, arg1);
if (!*arg1) {
rset_show_usage(ch);
return;
}
if (is_abbrev(arg1, "show")) {
rset_show_room(ch, room);
return;
}
if (is_abbrev(arg1, "add") || is_abbrev(arg1, "set")) {
bool set_alias = is_abbrev(arg1, "set");
argument = one_argument(argument, arg2);
if (!*arg2) {
rset_show_add_usage(ch);
return;
}
if (is_abbrev(arg2, "name")) {
skip_spaces(&argument);
if (!*argument) {
rset_show_set_usage(ch);
return;
}
genolc_checkstring(ch->desc, argument);
if (count_non_protocol_chars(argument) > MAX_ROOM_NAME / 2) {
send_to_char(ch, "Size limited to %d non-protocol characters.\r\n", MAX_ROOM_NAME / 2);
return;
}
if (room->name)
free(room->name);
argument[MAX_ROOM_NAME - 1] = '\0';
room->name = str_udup(argument);
rset_mark_room_modified(rnum);
send_to_char(ch, "Room name set.\r\n");
return;
}
if (is_abbrev(arg2, "sector")) {
int sector;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_set_sector_usage(ch);
return;
}
if (is_number(arg3))
sector = atoi(arg3) - 1;
else
sector = rset_find_sector(arg3);
if (sector < 0 || sector >= NUM_ROOM_SECTORS) {
send_to_char(ch, "Invalid sector type.\r\n");
return;
}
room->sector_type = sector;
rset_mark_room_modified(rnum);
send_to_char(ch, "Sector type set.\r\n");
return;
}
if (is_abbrev(arg2, "desc")) {
if (*argument) {
rset_show_add_usage(ch);
return;
}
rset_desc_edit(ch, room);
return;
}
if (is_abbrev(arg2, "flags")) {
bool any = FALSE;
if (!*argument) {
rset_show_add_flags_usage(ch);
return;
}
while (*argument) {
int flag;
argument = one_argument(argument, arg3);
if (!*arg3)
break;
flag = rset_find_room_flag(arg3);
if (flag < 0) {
send_to_char(ch, "Unknown room flag: %s\r\n", arg3);
continue;
}
SET_BIT_AR(room->room_flags, flag);
any = TRUE;
}
if (any) {
rset_mark_room_modified(rnum);
send_to_char(ch, "Room flags updated.\r\n");
}
return;
}
if (is_abbrev(arg2, "exit")) {
int dir;
room_rnum to_room;
argument = one_argument(argument, arg3);
argument = one_argument(argument, arg1);
if (!*arg3 || !*arg1) {
rset_show_add_exit_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!is_number(arg1)) {
send_to_char(ch, "Room number must be numeric.\r\n");
return;
}
to_room = real_room(atoi(arg1));
if (to_room == NOWHERE) {
send_to_char(ch, "That room does not exist.\r\n");
return;
}
if (!room->dir_option[dir]) {
CREATE(room->dir_option[dir], struct room_direction_data, 1);
room->dir_option[dir]->general_description = NULL;
room->dir_option[dir]->keyword = NULL;
room->dir_option[dir]->exit_info = 0;
room->dir_option[dir]->key = NOTHING;
}
room->dir_option[dir]->to_room = to_room;
rset_mark_room_modified(rnum);
send_to_char(ch, "Exit %s set to room %d.\r\n", dirs[dir], world[to_room].number);
return;
}
if (is_abbrev(arg2, "door")) {
int dir;
char *door_name;
argument = one_argument(argument, arg3);
skip_spaces(&argument);
door_name = argument;
if (!*arg3 || !*door_name) {
rset_show_add_door_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!room->dir_option[dir] || room->dir_option[dir]->to_room == NOWHERE) {
send_to_char(ch, "That exit does not exist.\r\n");
return;
}
genolc_checkstring(ch->desc, door_name);
if (room->dir_option[dir]->keyword)
free(room->dir_option[dir]->keyword);
room->dir_option[dir]->keyword = str_udup(door_name);
SET_BIT(room->dir_option[dir]->exit_info, EX_ISDOOR);
rset_mark_room_modified(rnum);
send_to_char(ch, "Door added to %s.\r\n", dirs[dir]);
return;
}
if (is_abbrev(arg2, "key")) {
int dir;
int key_vnum;
argument = one_argument(argument, arg3);
argument = one_argument(argument, arg1);
if (!*arg3 || !*arg1) {
rset_show_add_key_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!room->dir_option[dir] || room->dir_option[dir]->to_room == NOWHERE) {
send_to_char(ch, "That exit does not exist.\r\n");
return;
}
if (!IS_SET(room->dir_option[dir]->exit_info, EX_ISDOOR)) {
send_to_char(ch, "That exit has no door.\r\n");
return;
}
if (!is_number(arg1)) {
send_to_char(ch, "Key number must be numeric.\r\n");
return;
}
key_vnum = atoi(arg1);
if (real_object(key_vnum) == NOTHING) {
send_to_char(ch, "That key vnum does not exist.\r\n");
return;
}
room->dir_option[dir]->key = key_vnum;
rset_mark_room_modified(rnum);
send_to_char(ch, "Key set on %s.\r\n", dirs[dir]);
return;
}
if (is_abbrev(arg2, "hidden")) {
int dir;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_add_hidden_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!room->dir_option[dir] || room->dir_option[dir]->to_room == NOWHERE) {
send_to_char(ch, "That exit does not exist.\r\n");
return;
}
SET_BIT(room->dir_option[dir]->exit_info, EX_HIDDEN);
rset_mark_room_modified(rnum);
send_to_char(ch, "Hidden flag set on %s.\r\n", dirs[dir]);
return;
}
if (is_abbrev(arg2, "forage")) {
int vnum, dc;
struct forage_entry *entry;
argument = one_argument(argument, arg3);
argument = one_argument(argument, arg1);
if (!*arg3 || !*arg1) {
rset_show_add_forage_usage(ch);
return;
}
if (!is_number(arg3) || !is_number(arg1)) {
rset_show_add_forage_usage(ch);
return;
}
vnum = atoi(arg3);
dc = atoi(arg1);
if (vnum <= 0 || dc <= 0) {
send_to_char(ch, "Both vnum and DC must be positive.\r\n");
return;
}
if (real_object(vnum) == NOTHING) {
send_to_char(ch, "That object vnum does not exist.\r\n");
return;
}
CREATE(entry, struct forage_entry, 1);
entry->obj_vnum = vnum;
entry->dc = dc;
entry->next = room->forage;
room->forage = entry;
rset_mark_room_modified(rnum);
send_to_char(ch, "Forage entry added.\r\n");
return;
}
if (is_abbrev(arg2, "edesc")) {
struct extra_descr_data *desc;
char *keyword;
char *edesc;
argument = one_argument(argument, arg3);
skip_spaces(&argument);
keyword = arg3;
edesc = argument;
if (!*keyword || !*edesc) {
rset_show_add_edesc_usage(ch);
return;
}
genolc_checkstring(ch->desc, edesc);
genolc_checkstring(ch->desc, keyword);
CREATE(desc, struct extra_descr_data, 1);
desc->keyword = str_udup(keyword);
desc->description = str_udup(edesc);
desc->next = room->ex_description;
room->ex_description = desc;
rset_mark_room_modified(rnum);
send_to_char(ch, "Extra description added.\r\n");
return;
}
if (set_alias) {
int flag;
flag = rset_find_room_flag(arg2);
if (flag >= 0) {
SET_BIT_AR(room->room_flags, flag);
rset_mark_room_modified(rnum);
send_to_char(ch, "Room flag set.\r\n");
return;
}
}
rset_show_add_usage(ch);
return;
}
if (is_abbrev(arg1, "del")) {
argument = one_argument(argument, arg2);
if (!*arg2) {
rset_show_del_usage(ch);
return;
}
if (is_abbrev(arg2, "flags")) {
bool any = FALSE;
if (!*argument) {
rset_show_del_flags_usage(ch);
return;
}
while (*argument) {
int flag;
argument = one_argument(argument, arg3);
if (!*arg3)
break;
flag = rset_find_room_flag(arg3);
if (flag < 0) {
send_to_char(ch, "Unknown room flag: %s\r\n", arg3);
continue;
}
REMOVE_BIT_AR(room->room_flags, flag);
any = TRUE;
}
if (any) {
rset_mark_room_modified(rnum);
send_to_char(ch, "Room flags updated.\r\n");
}
return;
}
if (is_abbrev(arg2, "exit")) {
int dir;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_del_exit_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!room->dir_option[dir]) {
send_to_char(ch, "That exit does not exist.\r\n");
return;
}
if (room->dir_option[dir]->general_description)
free(room->dir_option[dir]->general_description);
if (room->dir_option[dir]->keyword)
free(room->dir_option[dir]->keyword);
free(room->dir_option[dir]);
room->dir_option[dir] = NULL;
rset_mark_room_modified(rnum);
send_to_char(ch, "Exit %s removed.\r\n", dirs[dir]);
return;
}
if (is_abbrev(arg2, "door")) {
int dir;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_del_door_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!room->dir_option[dir]) {
send_to_char(ch, "That exit does not exist.\r\n");
return;
}
if (room->dir_option[dir]->keyword) {
free(room->dir_option[dir]->keyword);
room->dir_option[dir]->keyword = NULL;
}
REMOVE_BIT(room->dir_option[dir]->exit_info, EX_ISDOOR);
REMOVE_BIT(room->dir_option[dir]->exit_info, EX_CLOSED);
REMOVE_BIT(room->dir_option[dir]->exit_info, EX_LOCKED);
REMOVE_BIT(room->dir_option[dir]->exit_info, EX_PICKPROOF);
REMOVE_BIT(room->dir_option[dir]->exit_info, EX_HIDDEN);
rset_mark_room_modified(rnum);
send_to_char(ch, "Door removed from %s.\r\n", dirs[dir]);
return;
}
if (is_abbrev(arg2, "key")) {
int dir;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_del_key_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!room->dir_option[dir]) {
send_to_char(ch, "That exit does not exist.\r\n");
return;
}
room->dir_option[dir]->key = NOTHING;
rset_mark_room_modified(rnum);
send_to_char(ch, "Key removed from %s.\r\n", dirs[dir]);
return;
}
if (is_abbrev(arg2, "hidden")) {
int dir;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_del_hidden_usage(ch);
return;
}
dir = rset_find_dir(arg3);
if (dir < 0) {
send_to_char(ch, "Invalid direction.\r\n");
return;
}
if (!room->dir_option[dir]) {
send_to_char(ch, "That exit does not exist.\r\n");
return;
}
REMOVE_BIT(room->dir_option[dir]->exit_info, EX_HIDDEN);
rset_mark_room_modified(rnum);
send_to_char(ch, "Hidden flag removed from %s.\r\n", dirs[dir]);
return;
}
if (is_abbrev(arg2, "forage")) {
int vnum;
struct forage_entry *entry = room->forage;
struct forage_entry *prev = NULL;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_del_forage_usage(ch);
return;
}
if (!is_number(arg3)) {
rset_show_del_forage_usage(ch);
return;
}
vnum = atoi(arg3);
while (entry) {
if (entry->obj_vnum == vnum)
break;
prev = entry;
entry = entry->next;
}
if (!entry) {
send_to_char(ch, "No forage entry found for vnum %d.\r\n", vnum);
return;
}
if (prev)
prev->next = entry->next;
else
room->forage = entry->next;
free(entry);
rset_mark_room_modified(rnum);
send_to_char(ch, "Forage entry removed.\r\n");
return;
}
if (is_abbrev(arg2, "edesc")) {
struct extra_descr_data *desc = room->ex_description;
struct extra_descr_data *prev = NULL;
argument = one_argument(argument, arg3);
if (!*arg3) {
rset_show_del_edesc_usage(ch);
return;
}
while (desc) {
if (desc->keyword && isname(arg3, desc->keyword))
break;
prev = desc;
desc = desc->next;
}
if (!desc) {
send_to_char(ch, "No extra description found for %s.\r\n", arg3);
return;
}
if (prev)
prev->next = desc->next;
else
room->ex_description = desc->next;
if (desc->keyword)
free(desc->keyword);
if (desc->description)
free(desc->description);
free(desc);
rset_mark_room_modified(rnum);
send_to_char(ch, "Extra description removed.\r\n");
return;
}
rset_show_del_usage(ch);
return;
}
if (is_abbrev(arg1, "desc")) {
rset_show_desc_usage(ch);
rset_desc_edit(ch, room);
return;
}
if (is_abbrev(arg1, "clear")) {
argument = one_argument(argument, arg2);
if (!*arg2 || !is_abbrev(arg2, "force")) {
rset_show_usage(ch);
return;
}
if (*argument) {
rset_show_usage(ch);
return;
}
free_room_strings(room);
room->name = strdup("An unfinished room");
room->description = strdup("You are in an unfinished room.\r\n");
room->sector_type = SECT_INSIDE;
memset(room->room_flags, 0, sizeof(room->room_flags));
rset_mark_room_modified(rnum);
send_to_char(ch, "Room cleared.\r\n");
return;
}
if (is_abbrev(arg1, "validate")) {
if (*argument) {
rset_show_validate_usage(ch);
return;
}
rset_validate_room(ch, room);
return;
}
rset_show_usage(ch);
}
static void oset_show_usage(struct char_data *ch)
{
send_to_char(ch,
"Usage:\r\n"
" oset show <obj>\r\n"
" oset add keywords <obj> <keyword> [keywords]\r\n"
" oset add sdesc <obj> <text>\r\n"
" oset add ldesc <obj> <text>\r\n"
" oset add desc <obj>\r\n"
" oset add type <obj> <item type>\r\n"
" oset add flags <obj> <flags> [flags]\r\n"
" oset add wear <obj> <wear type> [wear types]\r\n"
" oset add weight <obj> <value>\r\n"
" oset add cost <obj> <value>\r\n"
" oset add oval <obj> <oval number> <value>\r\n"
" oset del <obj> <field>\r\n"
" oset clear <obj> force\r\n"
" oset validate <obj>\r\n");
}
static void oset_show_add_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds specific configuration to the object.\r\n"
"\r\n"
"Usage:\r\n"
" oset add keywords <obj> <keyword> [keywords]\r\n"
" oset add sdesc <obj> <text>\r\n"
" oset add ldesc <obj> <text>\r\n"
" oset add desc <obj>\r\n"
" oset add type <obj> <item type>\r\n"
" oset add flags <obj> <flags> [flags]\r\n"
" oset add wear <obj> <wear type> [wear types]\r\n"
" oset add weight <obj> <value>\r\n"
" oset add cost <obj> <value>\r\n"
" oset add oval <obj> <oval number> <value>\r\n");
}
static void oset_show_add_keywords_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds keywords to object. Can add a single keyword or several at once.\r\n"
"It is always best to use the most specific keyword as the first entry.\r\n"
"\r\n"
"Examples:\r\n"
" oset add keywords sword steel\r\n"
" oset add keywords armor padded\r\n"
" oset add keywords staff gnarled oak\r\n");
}
static void oset_show_add_sdesc_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds a short description to the object. This is what is seen in an inventory,\r\n"
"in a container, on furniture, or worn by someone.\r\n"
"\r\n"
"Examples:\r\n"
" oset add sdesc sword a wooden sword\r\n"
" oset add sdesc cloak a dark, hooded cloak\r\n"
" oset add sdesc maul a massive, obsidian-studded maul\r\n");
}
static void oset_show_add_ldesc_usage(struct char_data *ch)
{
send_to_char(ch,
"Adds a long description to the object. This is what everyone sees when an item\r\n"
"is in a room.\r\n"
"\r\n"
"Examples:\r\n"
" oset add ldesc cloak A pile of dark fabric is here in a heap.\r\n"
" oset add ldesc maul Made of solid wood, a massive, obsidian-studded maul is here.\r\n"
" oset add ldesc bread A piece of bread has been discarded here.\r\n");
}
static void oset_show_add_type_usage(struct char_data *ch)
{
send_to_char(ch,
"Specifies the object type. Can only be one type.\r\n"
"\r\n"
"Examples:\r\n"
" oset add type chest container\r\n"
" oset add type armor armor\r\n"
" oset add type sword weapon\r\n"
"\r\n"
"Types:\r\n");
column_list(ch, 0, item_types, NUM_ITEM_TYPES, FALSE);
}
static void oset_show_add_flags_usage(struct char_data *ch)
{
send_to_char(ch,
"Specifies object flags. Can be a single flag or multiples.\r\n"
"\r\n"
"Examples:\r\n"
" oset add flags sword no_drop\r\n"
" oset add flags staff hum glow\r\n"
"\r\n"
"Flags:\r\n");
column_list(ch, 0, extra_bits, NUM_EXTRA_FLAGS, FALSE);
}
static void oset_show_add_wear_usage(struct char_data *ch)
{
send_to_char(ch,
"Specifies object wear types. Can be a single type or multiples.\r\n"
"\r\n"
"Examples:\r\n"
" oset add wear sword wield\r\n"
" oset add wear staff wield hold\r\n"
"\r\n"
"Wear types:\r\n");
column_list(ch, 0, wear_bits, NUM_ITEM_WEARS, FALSE);
}
static void oset_show_add_weight_usage(struct char_data *ch)
{
send_to_char(ch,
"Specifies object weight. Affects carrying capacity of PC/NPC.\r\n"
"\r\n"
"Examples:\r\n"
" oset add weight sword 1\r\n");
}
static void oset_show_add_cost_usage(struct char_data *ch)
{
send_to_char(ch,
"Specifies object cost. Determines how much it sells for and is bought for.\r\n"
"\r\n"
"Examples:\r\n"
" oset add cost sword 100\r\n");
}
static void oset_show_add_oval_usage(struct char_data *ch)
{
send_to_char(ch,
"Sets an oval property on an object. Each object type has its own respective\r\n"
"ovals. Some of these influence different code paramters, such as the type\r\n"
"CONTAINER having oval1 \"capacity\". This sets how much weight the container\r\n"
"object can hold.\r\n"
"\r\n"
"For a list of ovals each item has, check HELP OVALS.\r\n"
"\r\n"
"Examples:\r\n"
" oset add oval chest capacity 100 (for a container)\r\n"
" oset add oval armor piece_ac 2 (for armor)\r\n"
" oset add oval sword weapon_type slashing (for weapons)\r\n");
}
static void oset_show_del_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes specific configuration from the object.\r\n"
"\r\n"
"Usage:\r\n"
" oset del <obj> keywords <keyword> [keywords]\r\n"
" oset del <obj> flags <flags> [flags]\r\n"
" oset del <obj> wear <wear type> [wear types]\r\n"
" oset del <obj> oval <oval number|oval name>\r\n"
"\r\n"
"Examples:\r\n"
" oset del sword keywords sword\r\n"
" oset del sword flags hum\r\n"
" oset del sword wear wield\r\n"
" oset del sword oval 2\r\n");
}
static void oset_show_del_flags_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes object flags.\r\n"
"\r\n"
"Usage:\r\n"
" oset del <obj> flags <flag> [flags]\r\n"
"\r\n"
"Examples:\r\n"
" oset del sword flags hum\r\n"
"\r\n"
"Flags:\r\n");
column_list(ch, 0, extra_bits, NUM_EXTRA_FLAGS, FALSE);
}
static void oset_show_del_wear_usage(struct char_data *ch)
{
send_to_char(ch,
"Deletes object wear types.\r\n"
"\r\n"
"Usage:\r\n"
" oset del <obj> wear <wear type> [wear types]\r\n"
"\r\n"
"Examples:\r\n"
" oset del sword wear wield\r\n"
"\r\n"
"Wear types:\r\n");
column_list(ch, 0, wear_bits, NUM_ITEM_WEARS, FALSE);
}
static void oset_show_clear_usage(struct char_data *ch)
{
send_to_char(ch,
"Clears all configuration from an object to start over fresh.\r\n"
"\r\n"
"Usage:\r\n"
" oset clear <obj> force\r\n"
"\r\n"
"Examples:\r\n"
" oset clear sword force\r\n");
}
static struct obj_data *oset_get_target_obj_keyword(struct char_data *ch, char *keyword)
{
struct obj_data *obj;
if (!ch || !keyword || !*keyword)
return NULL;
obj = get_obj_in_list_vis(ch, keyword, NULL, ch->carrying);
if (obj)
return obj;
return get_obj_in_list_vis(ch, keyword, NULL, world[IN_ROOM(ch)].contents);
}
static void oset_replace_string(struct obj_data *obj, char **field, const char *value, const char *proto_field)
{
if (*field && (!proto_field || *field != proto_field))
free(*field);
*field = strdup(value ? value : "");
}
static int oset_find_item_type(const char *arg)
{
int i;
if (!arg || !*arg)
return -1;
for (i = 0; *item_types[i] != '\n'; i++) {
if (is_abbrev(arg, item_types[i]))
return i;
}
return -1;
}
static int oset_find_extra_flag(const char *arg)
{
int i;
if (!arg || !*arg)
return -1;
for (i = 0; i < NUM_EXTRA_FLAGS; i++) {
if (is_abbrev(arg, extra_bits[i]))
return i;
}
return -1;
}
static int oset_find_wear_flag(const char *arg)
{
int i;
if (!arg || !*arg)
return -1;
for (i = 0; i < NUM_ITEM_WEARS; i++) {
if (is_abbrev(arg, wear_bits[i]))
return i;
}
return -1;
}
static int oset_find_oval_by_name(const char *arg, const char * const *labels)
{
int i;
if (!arg || !*arg || !labels)
return -1;
for (i = 0; i < NUM_OBJ_VAL_POSITIONS; i++) {
if (labels[i] && *labels[i] && is_abbrev(arg, labels[i]))
return i;
}
return -1;
}
static int oset_find_attack_type(const char *arg)
{
int i;
if (!arg || !*arg)
return -1;
for (i = 0; i < NUM_ATTACK_TYPES; i++) {
if (is_abbrev(arg, attack_hit_text[i].singular) ||
is_abbrev(arg, attack_hit_text[i].plural))
return i;
}
if (!str_cmp(arg, "slashing"))
return oset_find_attack_type("slash");
if (!str_cmp(arg, "piercing"))
return oset_find_attack_type("pierce");
if (!str_cmp(arg, "bludgeoning"))
return oset_find_attack_type("bludgeon");
return -1;
}
static bool oset_add_keywords(struct char_data *ch, struct obj_data *obj, char *argument)
{
char buf[MAX_STRING_LENGTH];
char arg[MAX_INPUT_LENGTH];
bool changed = FALSE;
obj_rnum rnum = GET_OBJ_RNUM(obj);
const char *proto_name = (rnum != NOTHING) ? obj_proto[rnum].name : NULL;
buf[0] = '\0';
if (obj->name && *obj->name)
strlcpy(buf, obj->name, sizeof(buf));
while (*argument) {
argument = one_argument(argument, arg);
if (!*arg)
break;
if (!isname(arg, buf)) {
size_t needed = strlen(buf) + strlen(arg) + 2;
if (needed >= sizeof(buf)) {
send_to_char(ch, "Keyword list is too long.\r\n");
return FALSE;
}
if (*buf)
strlcat(buf, " ", sizeof(buf));
strlcat(buf, arg, sizeof(buf));
changed = TRUE;
}
}
if (!changed) {
send_to_char(ch, "Keywords unchanged.\r\n");
return TRUE;
}
oset_replace_string(obj, &obj->name, buf, proto_name);
send_to_char(ch, "Keywords updated.\r\n");
return TRUE;
}
static bool oset_del_keywords(struct char_data *ch, struct obj_data *obj, char *argument)
{
char buf[MAX_STRING_LENGTH];
char work[MAX_STRING_LENGTH];
char word[MAX_INPUT_LENGTH];
bool changed = FALSE;
obj_rnum rnum = GET_OBJ_RNUM(obj);
const char *proto_name = (rnum != NOTHING) ? obj_proto[rnum].name : NULL;
char *ptr;
buf[0] = '\0';
if (!obj->name || !*obj->name) {
send_to_char(ch, "Object has no keywords to remove.\r\n");
return TRUE;
}
strlcpy(work, obj->name, sizeof(work));
ptr = work;
while (*ptr) {
ptr = one_argument(ptr, word);
if (!*word)
break;
if (isname(word, argument)) {
changed = TRUE;
continue;
}
if (*buf)
strlcat(buf, " ", sizeof(buf));
strlcat(buf, word, sizeof(buf));
}
if (!changed) {
send_to_char(ch, "No matching keywords found.\r\n");
return TRUE;
}
oset_replace_string(obj, &obj->name, buf, proto_name);
send_to_char(ch, "Keywords updated.\r\n");
return TRUE;
}
static void oset_show_object(struct char_data *ch, struct obj_data *obj)
{
char buf[MAX_STRING_LENGTH];
char buf2[MAX_STRING_LENGTH];
int i;
const char * const *labels = obj_value_labels(GET_OBJ_TYPE(obj));
sprinttype(GET_OBJ_TYPE(obj), item_types, buf, sizeof(buf));
sprintbitarray(GET_OBJ_EXTRA(obj), extra_bits, EF_ARRAY_MAX, buf2);
send_to_char(ch, "Object [%d]: %s\r\n",
GET_OBJ_VNUM(obj), obj->short_description ? obj->short_description : "<None>");
send_to_char(ch, "Keywords: %s\r\n", obj->name ? obj->name : "<None>");
send_to_char(ch, "Short desc: %s\r\n", obj->short_description ? obj->short_description : "<None>");
send_to_char(ch, "Long desc: %s\r\n", obj->description ? obj->description : "<None>");
send_to_char(ch, "Main desc:\r\n%s", obj->main_description ? obj->main_description : " <None>\r\n");
send_to_char(ch, "Type: %s\r\n", buf);
send_to_char(ch, "Flags: %s\r\n", buf2);
sprintbitarray(GET_OBJ_WEAR(obj), wear_bits, TW_ARRAY_MAX, buf);
send_to_char(ch, "Wear: %s\r\n", buf);
send_to_char(ch, "Weight: %d\r\n", GET_OBJ_WEIGHT(obj));
send_to_char(ch, "Cost: %d\r\n", GET_OBJ_COST(obj));
send_to_char(ch, "Ovals:\r\n");
for (i = 0; i < NUM_OBJ_VAL_POSITIONS; i++) {
const char *label = labels ? labels[i] : "Value";
send_to_char(ch, " [%d] %s: %d\r\n", i, label, GET_OBJ_VAL(obj, i));
}
}
static void oset_desc_edit(struct char_data *ch, struct obj_data *obj)
{
char *oldtext = NULL;
send_editor_help(ch->desc);
write_to_output(ch->desc, "Enter object description:\r\n\r\n");
if (obj->main_description) {
write_to_output(ch->desc, "%s", obj->main_description);
oldtext = strdup(obj->main_description);
}
string_write(ch->desc, &obj->main_description, MAX_MESSAGE_LENGTH, 0, oldtext);
}
static void oset_clear_object(struct obj_data *obj)
{
obj_rnum rnum = GET_OBJ_RNUM(obj);
if (rnum != NOTHING)
free_object_strings_proto(obj);
else
free_object_strings(obj);
obj->name = NULL;
obj->description = NULL;
obj->short_description = NULL;
obj->main_description = NULL;
obj->ex_description = NULL;
obj->name = strdup("unfinished object");
obj->description = strdup("An unfinished object is lying here.");
obj->short_description = strdup("an unfinished object");
GET_OBJ_TYPE(obj) = 0;
GET_OBJ_WEIGHT(obj) = 0;
GET_OBJ_COST(obj) = 0;
GET_OBJ_COST_PER_DAY(obj) = 0;
GET_OBJ_TIMER(obj) = 0;
GET_OBJ_LEVEL(obj) = 0;
memset(obj->obj_flags.extra_flags, 0, sizeof(obj->obj_flags.extra_flags));
memset(obj->obj_flags.wear_flags, 0, sizeof(obj->obj_flags.wear_flags));
memset(obj->obj_flags.value, 0, sizeof(obj->obj_flags.value));
memset(obj->obj_flags.bitvector, 0, sizeof(obj->obj_flags.bitvector));
memset(obj->affected, 0, sizeof(obj->affected));
SET_BIT_AR(GET_OBJ_WEAR(obj), ITEM_WEAR_TAKE);
}
static void oset_validate_object(struct char_data *ch, struct obj_data *obj)
{
int errors = 0;
if (!obj->name || !*obj->name) {
send_to_char(ch, "Error: keywords are not set.\r\n");
errors++;
}
if (!obj->short_description || !*obj->short_description) {
send_to_char(ch, "Error: short description is not set.\r\n");
errors++;
}
if (!obj->description || !*obj->description) {
send_to_char(ch, "Error: long description is not set.\r\n");
errors++;
}
if (!obj->main_description || !*obj->main_description) {
send_to_char(ch, "Error: main description is not set.\r\n");
errors++;
}
if (GET_OBJ_TYPE(obj) < 1 || GET_OBJ_TYPE(obj) >= NUM_ITEM_TYPES) {
send_to_char(ch, "Error: object type is not set.\r\n");
errors++;
}
if (GET_OBJ_WEIGHT(obj) <= 0) {
send_to_char(ch, "Error: weight must be above zero.\r\n");
errors++;
}
if (GET_OBJ_COST(obj) <= 0) {
send_to_char(ch, "Error: cost must be above zero.\r\n");
errors++;
}
if (!errors)
send_to_char(ch, "Object validates cleanly.\r\n");
else
send_to_char(ch, "Validation failed: %d issue%s.\r\n", errors, errors == 1 ? "" : "s");
}
ACMD(do_oset)
{
char arg1[MAX_INPUT_LENGTH];
char arg2[MAX_INPUT_LENGTH];
char arg3[MAX_INPUT_LENGTH];
char arg4[MAX_INPUT_LENGTH];
struct obj_data *obj;
if (IS_NPC(ch) || ch->desc == NULL) {
send_to_char(ch, "oset is only usable by connected players.\r\n");
return;
}
argument = one_argument(argument, arg1);
if (!*arg1) {
oset_show_usage(ch);
return;
}
if (is_abbrev(arg1, "show")) {
char *keyword;
skip_spaces(&argument);
keyword = argument;
if (!*keyword) {
send_to_char(ch, "Provide a keyword of an object to show.\r\n");
return;
}
obj = oset_get_target_obj_keyword(ch, keyword);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(keyword), keyword);
return;
}
oset_show_object(ch, obj);
return;
}
if (is_abbrev(arg1, "add")) {
argument = one_argument(argument, arg2);
if (!*arg2) {
oset_show_add_usage(ch);
return;
}
if (is_abbrev(arg2, "keywords")) {
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_keywords_usage(ch);
return;
}
if (!*argument) {
oset_show_add_keywords_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
oset_add_keywords(ch, obj, argument);
return;
}
if (is_abbrev(arg2, "sdesc")) {
static size_t max_len = 64;
obj_rnum rnum;
const char *proto_sdesc;
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_sdesc_usage(ch);
return;
}
skip_spaces(&argument);
if (!*argument) {
oset_show_add_sdesc_usage(ch);
return;
}
if (strlen(argument) > max_len) {
send_to_char(ch, "Short description is too long.\r\n");
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
rnum = GET_OBJ_RNUM(obj);
proto_sdesc = (rnum != NOTHING) ? obj_proto[rnum].short_description : NULL;
oset_replace_string(obj, &obj->short_description, argument, proto_sdesc);
send_to_char(ch, "Short description set.\r\n");
return;
}
if (is_abbrev(arg2, "ldesc")) {
static size_t max_len = 128;
obj_rnum rnum;
const char *proto_ldesc;
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_ldesc_usage(ch);
return;
}
skip_spaces(&argument);
if (!*argument) {
oset_show_add_ldesc_usage(ch);
return;
}
if (strlen(argument) > max_len) {
send_to_char(ch, "Long description is too long.\r\n");
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
rnum = GET_OBJ_RNUM(obj);
proto_ldesc = (rnum != NOTHING) ? obj_proto[rnum].description : NULL;
oset_replace_string(obj, &obj->description, argument, proto_ldesc);
send_to_char(ch, "Long description set.\r\n");
return;
}
if (is_abbrev(arg2, "desc")) {
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_usage(ch);
return;
}
if (*argument) {
oset_show_add_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
oset_desc_edit(ch, obj);
return;
}
if (is_abbrev(arg2, "type")) {
int type;
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_type_usage(ch);
return;
}
skip_spaces(&argument);
if (!*argument) {
oset_show_add_type_usage(ch);
return;
}
type = oset_find_item_type(argument);
if (type < 0 || type >= NUM_ITEM_TYPES) {
send_to_char(ch, "Invalid object type.\r\n");
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
GET_OBJ_TYPE(obj) = type;
send_to_char(ch, "Object type set.\r\n");
return;
}
if (is_abbrev(arg2, "flags")) {
bool any = FALSE;
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_flags_usage(ch);
return;
}
if (!*argument) {
oset_show_add_flags_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
while (*argument) {
int flag;
argument = one_argument(argument, arg3);
if (!*arg3)
break;
flag = oset_find_extra_flag(arg3);
if (flag < 0) {
send_to_char(ch, "Unknown flag: %s\r\n", arg3);
continue;
}
SET_BIT_AR(GET_OBJ_EXTRA(obj), flag);
any = TRUE;
}
if (any)
send_to_char(ch, "Object flags updated.\r\n");
return;
}
if (is_abbrev(arg2, "wear")) {
bool any = FALSE;
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_wear_usage(ch);
return;
}
if (!*argument) {
oset_show_add_wear_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
while (*argument) {
int flag;
argument = one_argument(argument, arg3);
if (!*arg3)
break;
flag = oset_find_wear_flag(arg3);
if (flag < 0) {
send_to_char(ch, "Unknown wear type: %s\r\n", arg3);
continue;
}
SET_BIT_AR(GET_OBJ_WEAR(obj), flag);
any = TRUE;
}
if (any)
send_to_char(ch, "Wear flags updated.\r\n");
return;
}
if (is_abbrev(arg2, "weight")) {
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_weight_usage(ch);
return;
}
skip_spaces(&argument);
if (!*argument || !is_number(argument)) {
oset_show_add_weight_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
GET_OBJ_WEIGHT(obj) = LIMIT(atoi(argument), 0, MAX_OBJ_WEIGHT);
send_to_char(ch, "Weight set.\r\n");
return;
}
if (is_abbrev(arg2, "cost")) {
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_add_cost_usage(ch);
return;
}
skip_spaces(&argument);
if (!*argument || !is_number(argument)) {
oset_show_add_cost_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
GET_OBJ_COST(obj) = LIMIT(atoi(argument), 0, MAX_OBJ_COST);
send_to_char(ch, "Cost set.\r\n");
return;
}
if (is_abbrev(arg2, "oval")) {
int pos;
int value;
const char * const *labels;
argument = one_argument(argument, arg3);
argument = one_argument(argument, arg4);
if (!*arg3 || !*arg4) {
oset_show_add_oval_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg3);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg3), arg3);
return;
}
argument = one_argument(argument, arg1);
if (!*arg1) {
oset_show_add_oval_usage(ch);
return;
}
labels = obj_value_labels(GET_OBJ_TYPE(obj));
if (is_number(arg4)) {
pos = atoi(arg4);
} else {
pos = oset_find_oval_by_name(arg4, labels);
}
if (pos < 0 || pos >= NUM_OBJ_VAL_POSITIONS) {
send_to_char(ch, "Invalid oval position.\r\n");
return;
}
if (GET_OBJ_TYPE(obj) == ITEM_WEAPON && pos == 2 &&
!is_number(arg1)) {
value = oset_find_attack_type(arg1);
if (value < 0) {
send_to_char(ch, "Unknown weapon type: %s\r\n", arg1);
return;
}
} else {
if (!is_number(arg1)) {
send_to_char(ch, "Oval value must be numeric.\r\n");
return;
}
value = atoi(arg1);
}
GET_OBJ_VAL(obj, pos) = value;
send_to_char(ch, "Oval set.\r\n");
return;
}
oset_show_add_usage(ch);
return;
}
if (is_abbrev(arg1, "del")) {
argument = one_argument(argument, arg2);
if (!*arg2) {
oset_show_del_usage(ch);
return;
}
argument = one_argument(argument, arg3);
if (!*arg3) {
oset_show_del_usage(ch);
return;
}
if (is_abbrev(arg3, "keywords")) {
if (!*argument) {
oset_show_del_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg2);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg2), arg2);
return;
}
oset_del_keywords(ch, obj, argument);
return;
}
if (is_abbrev(arg3, "flags")) {
bool any = FALSE;
if (!*argument) {
oset_show_del_flags_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg2);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg2), arg2);
return;
}
while (*argument) {
int flag;
argument = one_argument(argument, arg3);
if (!*arg3)
break;
flag = oset_find_extra_flag(arg3);
if (flag < 0) {
send_to_char(ch, "Unknown flag: %s\r\n", arg3);
continue;
}
REMOVE_BIT_AR(GET_OBJ_EXTRA(obj), flag);
any = TRUE;
}
if (any)
send_to_char(ch, "Object flags updated.\r\n");
return;
}
if (is_abbrev(arg3, "wear")) {
bool any = FALSE;
if (!*argument) {
oset_show_del_wear_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg2);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg2), arg2);
return;
}
while (*argument) {
int flag;
argument = one_argument(argument, arg3);
if (!*arg3)
break;
flag = oset_find_wear_flag(arg3);
if (flag < 0) {
send_to_char(ch, "Unknown wear type: %s\r\n", arg3);
continue;
}
REMOVE_BIT_AR(GET_OBJ_WEAR(obj), flag);
any = TRUE;
}
if (any)
send_to_char(ch, "Wear flags updated.\r\n");
return;
}
if (is_abbrev(arg3, "oval")) {
int pos;
const char * const *labels;
argument = one_argument(argument, arg4);
if (!*arg4) {
oset_show_del_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg2);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg2), arg2);
return;
}
labels = obj_value_labels(GET_OBJ_TYPE(obj));
if (is_number(arg4)) {
pos = atoi(arg4);
} else {
pos = oset_find_oval_by_name(arg4, labels);
}
if (pos < 0 || pos >= NUM_OBJ_VAL_POSITIONS) {
send_to_char(ch, "Invalid oval position.\r\n");
return;
}
GET_OBJ_VAL(obj, pos) = 0;
send_to_char(ch, "Oval cleared.\r\n");
return;
}
oset_show_del_usage(ch);
return;
}
if (is_abbrev(arg1, "clear")) {
argument = one_argument(argument, arg2);
if (!*arg2) {
oset_show_clear_usage(ch);
return;
}
obj = oset_get_target_obj_keyword(ch, arg2);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg2), arg2);
return;
}
argument = one_argument(argument, arg3);
if (!*arg3 || !is_abbrev(arg3, "force")) {
oset_show_clear_usage(ch);
return;
}
if (*argument) {
oset_show_clear_usage(ch);
return;
}
oset_clear_object(obj);
send_to_char(ch, "Object cleared.\r\n");
return;
}
if (is_abbrev(arg1, "validate")) {
char *keyword;
skip_spaces(&argument);
keyword = argument;
if (!*keyword) {
send_to_char(ch, "Provide a keyword of an object to validate.\r\n");
return;
}
obj = oset_get_target_obj_keyword(ch, keyword);
if (!obj) {
send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(keyword), keyword);
return;
}
oset_validate_object(ch, obj);
return;
}
oset_show_usage(ch);
}
static struct obj_data *find_obj_vnum_nearby(struct char_data *ch, obj_vnum vnum)
{
struct obj_data *obj;
if (!ch || IN_ROOM(ch) == NOWHERE)
return NULL;
for (obj = ch->carrying; obj; obj = obj->next_content)
if (CAN_SEE_OBJ(ch, obj) && GET_OBJ_VNUM(obj) == vnum)
return obj;
for (obj = world[IN_ROOM(ch)].contents; obj; obj = obj->next_content)
if (CAN_SEE_OBJ(ch, obj) && GET_OBJ_VNUM(obj) == vnum)
return obj;
return NULL;
}
ACMD(do_mcreate)
{
char arg[MAX_INPUT_LENGTH];
char buf[MAX_STRING_LENGTH];
char namebuf[MAX_NAME_LENGTH];
char timestr[64];
struct char_data *newmob;
struct char_data *mob;
mob_vnum vnum;
zone_rnum znum;
time_t ct;
if (IS_NPC(ch) || ch->desc == NULL) {
send_to_char(ch, "mcreate is only usable by connected players.\r\n");
return;
}
one_argument(argument, arg);
if (!*arg) {
send_to_char(ch,
"Creates a new unfinished NPC which can be configured.\r\n"
"\r\n"
"Usage:\r\n"
" mcreate <vnum>\r\n"
"\r\n"
"Examples:\r\n"
" mcreate 1001\r\n");
return;
}
if (!is_number(arg)) {
send_to_char(ch,
"Creates a new unfinished NPC which can be configured.\r\n"
"\r\n"
"Usage:\r\n"
" mcreate <vnum>\r\n"
"\r\n"
"Examples:\r\n"
" mcreate 1001\r\n");
return;
}
vnum = atoi(arg);
if (vnum < IDXTYPE_MIN || vnum > IDXTYPE_MAX) {
send_to_char(ch, "That mobile VNUM can't exist.\r\n");
return;
}
znum = real_zone_by_thing(vnum);
if (znum == NOWHERE) {
send_to_char(ch, "Sorry, there is no zone for that number!\r\n");
return;
}
if (!can_edit_zone(ch, znum)) {
send_cannot_edit(ch, zone_table[znum].number);
return;
}
if (real_mobile(vnum) != NOBODY) {
mob = read_mobile(vnum, VIRTUAL);
if (mob == NULL) {
send_to_char(ch, "mcreate: failed to instantiate NPC %d.\r\n", vnum);
return;
}
char_to_room(mob, IN_ROOM(ch));
{
const char *keyword = GET_KEYWORDS(mob);
if (!keyword || !*keyword)
keyword = "npc";
if (!strn_cmp(keyword, "unfinished ", 11))
keyword += 11;
act("You form $t from clay.", FALSE, ch, (void *)keyword, 0, TO_CHAR);
act("$n forms $t from clay.", TRUE, ch, (void *)keyword, 0, TO_ROOM);
}
return;
}
CREATE(newmob, struct char_data, 1);
init_mobile(newmob);
GET_LEVEL(newmob) = 1;
GET_NAME(newmob) = strdup("An unfinished NPC");
GET_KEYWORDS(newmob) = strdup("unfinished npc");
strlcpy(namebuf, GET_NAME(ch), sizeof(namebuf));
snprintf(buf, sizeof(buf), "An unfinished NPC made by %.*s",
(int)sizeof(namebuf) - 1, namebuf);
GET_SDESC(newmob) = strdup(buf);
snprintf(buf, sizeof(buf), "An unfinished NPC made by %.*s is here.\r\n",
(int)sizeof(namebuf) - 1, namebuf);
GET_LDESC(newmob) = strdup(buf);
ct = time(0);
strftime(timestr, sizeof(timestr), "%c", localtime(&ct));
snprintf(buf, sizeof(buf),
"An unfinished NPC made by %.*s on %.*s\r\n",
(int)sizeof(namebuf) - 1, namebuf,
(int)sizeof(timestr) - 1, timestr);
GET_DDESC(newmob) = strdup(buf);
GET_BACKGROUND(newmob) = strdup("No background has been recorded.\r\n");
if (add_mobile(newmob, vnum) == NOBODY) {
free_mobile_strings(newmob);
free(newmob);
send_to_char(ch, "mcreate: failed to add NPC %d.\r\n", vnum);
return;
}
if (in_save_list(zone_table[znum].number, SL_MOB))
remove_from_save_list(zone_table[znum].number, SL_MOB);
free_mobile_strings(newmob);
free(newmob);
mob = read_mobile(vnum, VIRTUAL);
if (mob == NULL) {
send_to_char(ch, "mcreate: failed to instantiate NPC %d.\r\n", vnum);
return;
}
char_to_room(mob, IN_ROOM(ch));
{
const char *keyword = GET_KEYWORDS(mob);
if (!keyword || !*keyword)
keyword = "npc";
if (!strn_cmp(keyword, "unfinished ", 11))
keyword += 11;
act("You create an unfinished $t from clay.", FALSE, ch, (void *)keyword, 0, TO_CHAR);
act("$n forms an unfinished $t from clay.", TRUE, ch, (void *)keyword, 0, TO_ROOM);
}
}
ACMD(do_ocreate)
{
char arg[MAX_INPUT_LENGTH];
char buf[MAX_STRING_LENGTH];
char namebuf[MAX_NAME_LENGTH];
char timestr[MAX_STRING_LENGTH];
struct obj_data *newobj;
struct obj_data *obj;
obj_vnum vnum;
zone_rnum znum;
time_t ct;
if (IS_NPC(ch) || ch->desc == NULL) {
send_to_char(ch, "ocreate is only usable by connected players.\r\n");
return;
}
one_argument(argument, arg);
if (!*arg) {
send_to_char(ch,
"Creates a new unfinished object which can be configured.\r\n"
"\r\n"
"Usage:\r\n"
" ocreate <vnum>\r\n"
"\r\n"
"Examples:\r\n"
" ocreate 1001\r\n");
return;
}
if (!is_number(arg)) {
send_to_char(ch,
"Creates a new unfinished object which can be configured.\r\n"
"\r\n"
"Usage:\r\n"
" ocreate <vnum>\r\n"
"\r\n"
"Examples:\r\n"
" ocreate 1001\r\n");
return;
}
vnum = atoi(arg);
if (vnum < IDXTYPE_MIN || vnum > IDXTYPE_MAX) {
send_to_char(ch, "That object VNUM can't exist.\r\n");
return;
}
znum = real_zone_by_thing(vnum);
if (znum == NOWHERE) {
send_to_char(ch, "Sorry, there is no zone for that number!\r\n");
return;
}
if (!can_edit_zone(ch, znum)) {
send_cannot_edit(ch, zone_table[znum].number);
return;
}
if (real_object(vnum) != NOTHING) {
obj = read_object(vnum, VIRTUAL);
if (obj == NULL) {
send_to_char(ch, "ocreate: failed to instantiate object %d.\r\n", vnum);
return;
}
obj_to_char(obj, ch);
{
const char *sdesc = obj->short_description;
if (!sdesc || !*sdesc)
sdesc = obj->name;
if (!sdesc || !*sdesc)
sdesc = "object";
act("You form $t from clay.", FALSE, ch, (void *)sdesc, 0, TO_CHAR);
act("$n forms $t from clay.", TRUE, ch, (void *)sdesc, 0, TO_ROOM);
}
return;
}
CREATE(newobj, struct obj_data, 1);
clear_object(newobj);
newobj->name = strdup("unfinished object");
strlcpy(namebuf, GET_NAME(ch), sizeof(namebuf));
snprintf(buf, sizeof(buf), "unfinished object made by %s", namebuf);
newobj->short_description = strdup(buf);
ct = time(0);
strftime(timestr, sizeof(timestr), "%c", localtime(&ct));
snprintf(buf, sizeof(buf),
"This is an unfinished object created by %s on ", namebuf);
strlcat(buf, timestr, sizeof(buf));
newobj->description = strdup(buf);
SET_BIT_AR(GET_OBJ_WEAR(newobj), ITEM_WEAR_TAKE);
if (add_object(newobj, vnum) == NOTHING) {
free_object_strings(newobj);
free(newobj);
send_to_char(ch, "ocreate: failed to add object %d.\r\n", vnum);
return;
}
if (in_save_list(zone_table[znum].number, SL_OBJ))
remove_from_save_list(zone_table[znum].number, SL_OBJ);
free_object_strings(newobj);
free(newobj);
obj = read_object(vnum, VIRTUAL);
if (obj == NULL) {
send_to_char(ch, "ocreate: failed to instantiate object %d.\r\n", vnum);
return;
}
obj_to_char(obj, ch);
{
const char *sdesc = obj->short_description;
if (!sdesc || !*sdesc)
sdesc = obj->name;
if (!sdesc || !*sdesc)
sdesc = "object";
if (!strn_cmp(sdesc, "an ", 3))
sdesc += 3;
else if (!strn_cmp(sdesc, "a ", 2))
sdesc += 2;
else if (!strn_cmp(sdesc, "the ", 4))
sdesc += 4;
if (!strn_cmp(sdesc, "unfinished ", 11))
sdesc += 11;
act("You create an unfinished $t from clay.", FALSE, ch, (void *)sdesc, 0, TO_CHAR);
act("$n forms an unfinished $t from clay.", TRUE, ch, (void *)sdesc, 0, TO_ROOM);
}
}
ACMD(do_osave)
{
char arg[MAX_INPUT_LENGTH];
struct obj_data *obj;
struct obj_data *proto;
obj_rnum robj_num;
obj_vnum vnum;
zone_rnum znum;
if (IS_NPC(ch) || ch->desc == NULL) {
send_to_char(ch, "osave is only usable by connected players.\r\n");
return;
}
one_argument(argument, arg);
if (!*arg) {
send_to_char(ch,
"Saves an object and its current properties to disk, which will load upon next boot.\r\n"
"\r\n"
"Usage:\r\n"
" osave <vnum>\r\n"
"\r\n"
"Examples:\r\n"
" osave 1001\r\n");
return;
}
if (!is_number(arg)) {
send_to_char(ch,
"Saves an object and its current properties to disk, which will load upon next boot.\r\n"
"\r\n"
"Usage:\r\n"
" osave <vnum>\r\n"
"\r\n"
"Examples:\r\n"
" osave 1001\r\n");
return;
}
vnum = atoi(arg);
if (vnum < IDXTYPE_MIN || vnum > IDXTYPE_MAX) {
send_to_char(ch, "That object VNUM can't exist.\r\n");
return;
}
obj = find_obj_vnum_nearby(ch, vnum);
if (obj == NULL) {
send_to_char(ch,
"osave: object %d is not in your inventory or room.\r\n", vnum);
return;
}
znum = real_zone_by_thing(vnum);
if (znum == NOWHERE) {
send_to_char(ch, "Sorry, there is no zone for that number!\r\n");
return;
}
if (!can_edit_zone(ch, znum)) {
send_cannot_edit(ch, zone_table[znum].number);
return;
}
CREATE(proto, struct obj_data, 1);
clear_object(proto);
copy_object(proto, obj);
proto->in_room = NOWHERE;
proto->carried_by = NULL;
proto->worn_by = NULL;
proto->worn_on = NOWHERE;
proto->in_obj = NULL;
proto->contains = NULL;
proto->next_content = NULL;
proto->next = NULL;
proto->sitting_here = NULL;
proto->events = NULL;
proto->script = NULL;
proto->script_id = 0;
if ((robj_num = add_object(proto, vnum)) == NOTHING) {
free_object_strings(proto);
free(proto);
send_to_char(ch, "osave: failed to update object %d.\r\n", vnum);
return;
}
free_object_strings(proto);
free(proto);
for (obj = object_list; obj; obj = obj->next) {
if (obj->item_number != robj_num)
continue;
if (SCRIPT(obj))
extract_script(obj, OBJ_TRIGGER);
free_proto_script(obj, OBJ_TRIGGER);
copy_proto_script(&obj_proto[robj_num], obj, OBJ_TRIGGER);
assign_triggers(obj, OBJ_TRIGGER);
}
save_objects(znum);
send_to_char(ch, "osave: object %d saved to disk.\r\n", vnum);
}
/* ====== Builder snapshot: save a staged mob's gear as its prototype loadout ====== */
static void msave_loadout_append(struct mob_loadout **head,
struct mob_loadout **tail,
obj_vnum vnum, sh_int wear_pos,
int *eq_count, int *inv_count) {
struct mob_loadout *e;
CREATE(e, struct mob_loadout, 1);
e->vnum = vnum;
e->wear_pos = wear_pos;
e->quantity = 1;
e->next = NULL;
if (*tail)
(*tail)->next = e;
else
*head = e;
*tail = e;
if (wear_pos >= 0)
(*eq_count)++;
else
(*inv_count)++;
}
static void msave_capture_obj_tree(struct mob_loadout **head,
struct mob_loadout **tail,
struct obj_data *obj, sh_int base_wear_pos,
int depth, int *eq_count, int *inv_count) {
sh_int wear_pos;
struct obj_data *cont;
if (!obj || GET_OBJ_VNUM(obj) <= 0)
return;
if (depth <= 0)
wear_pos = base_wear_pos;
else
wear_pos = (sh_int)(-(depth + 1));
msave_loadout_append(head, tail, GET_OBJ_VNUM(obj), wear_pos,
eq_count, inv_count);
for (cont = obj->contains; cont; cont = cont->next_content)
msave_capture_obj_tree(head, tail, cont, base_wear_pos, depth + 1,
eq_count, inv_count);
}
ACMD(do_msave)
{
char a1[MAX_INPUT_LENGTH], a2[MAX_INPUT_LENGTH];
char target[MAX_INPUT_LENGTH] = {0}, flags[MAX_INPUT_LENGTH] = {0};
struct char_data *vict = NULL, *tmp = NULL;
mob_rnum rnum;
int include_inv = 0; /* -all */
int clear_first = 1; /* default replace; -append flips this to 0 */
int equips_added = 0, inv_entries = 0;
int pos;
struct obj_data *o;
struct mob_loadout *lo_head = NULL;
struct mob_loadout *lo_tail = NULL;
two_arguments(argument, a1, a2);
if (*a1 && *a1 == '-') {
/* user wrote: msave -flags <mob> */
strcpy(flags, a1);
strcpy(target, a2);
} else {
/* user wrote: msave <mob> [-flags] */
strcpy(target, a1);
strcpy(flags, a2);
}
/* Parse flags (space-separated, any order) */
if (*flags) {
char buf[MAX_INPUT_LENGTH], *p = flags;
while (*p) {
p = one_argument(p, buf);
if (!*buf) break;
if (!str_cmp(buf, "-all")) include_inv = 1;
else if (!str_cmp(buf, "-append")) clear_first = 0;
else if (!str_cmp(buf, "-clear")) clear_first = 1;
else {
send_to_char(ch, "Unknown flag '%s'. Try -all, -append, or -clear.\r\n", buf);
return;
}
}
}
/* Find target mob in the room */
if (*target)
vict = get_char_vis(ch, target, NULL, FIND_CHAR_ROOM);
else {
/* No name: pick the first NPC only if exactly one exists */
for (tmp = world[IN_ROOM(ch)].people; tmp; tmp = tmp->next_in_room) {
if (IS_NPC(tmp)) {
if (vict) { vict = NULL; break; } /* more than one — force explicit name */
vict = tmp;
}
}
}
if (!vict || !IS_NPC(vict)) {
send_to_char(ch, "Target an NPC in this room: msave <mob> [-all] [-append|-clear]\r\n");
return;
}
/* Resolve prototype and permission to edit its zone */
rnum = GET_MOB_RNUM(vict);
if (rnum < 0) {
send_to_char(ch, "I cant resolve that mob's prototype.\r\n");
return;
}
#ifdef CAN_EDIT_ZONE
if (!can_edit_zone(ch, real_zone_by_thing(GET_MOB_VNUM(vict)))) {
send_to_char(ch, "You dont have permission to modify that mobs zone.\r\n");
return;
}
#endif
/* Build the new loadout into the PROTOTYPE */
if (clear_first) {
loadout_free_list(&mob_proto[rnum].proto_loadout);
mob_proto[rnum].proto_loadout = NULL;
}
lo_head = mob_proto[rnum].proto_loadout;
lo_tail = lo_head;
while (lo_tail && lo_tail->next)
lo_tail = lo_tail->next;
/* Capture equipment: one entry per worn slot */
for (pos = 0; pos < NUM_WEARS; pos++) {
o = GET_EQ(vict, pos);
if (!o) continue;
if (GET_OBJ_VNUM(o) <= 0) continue;
msave_capture_obj_tree(&lo_head, &lo_tail, o, (sh_int)pos, 0,
&equips_added, &inv_entries);
}
/* Capture inventory (with nesting) if requested */
if (include_inv) {
for (o = vict->carrying; o; o = o->next_content) {
if (GET_OBJ_VNUM(o) <= 0) continue;
msave_capture_obj_tree(&lo_head, &lo_tail, o, -1, 0,
&equips_added, &inv_entries);
}
}
mob_proto[rnum].proto_loadout = lo_head;
/* Persist to disk: save the zone owning this mob vnum */
{
zone_rnum zr = real_zone_by_thing(GET_MOB_VNUM(vict));
if (zr == NOWHERE) {
mudlog(CMP, MAX(LVL_GOD, GET_INVIS_LEV(ch)), TRUE,
"msave: could not resolve zone for mob %d", GET_MOB_VNUM(vict));
send_to_char(ch, "Saved in memory, but couldnt resolve zone to write disk.\r\n");
} else {
save_mobiles(zr);
send_to_char(ch,
"Loadout saved for mob [%d]. Equipped: %d, Inventory lines: %d%s\r\n",
GET_MOB_VNUM(vict), equips_added, inv_entries, include_inv ? "" : " (use -all to include inventory)");
mudlog(CMP, MAX(LVL_GOD, GET_INVIS_LEV(ch)), TRUE,
"msave: %s saved loadout for mob %d (eq=%d, inv=%d) in zone %d",
GET_NAME(ch), GET_MOB_VNUM(vict), equips_added, inv_entries,
zone_table[zr].number);
}
}
}
ACMD(do_rsave)
{
room_rnum rnum;
zone_rnum znum;
int ok;
if (IS_NPC(ch)) {
send_to_char(ch, "Mobiles cant use this.\r\n");
return;
}
/* IN_ROOM(ch) is already a room_rnum (index into world[]). Do NOT pass it to real_room(). */
rnum = IN_ROOM(ch);
if (rnum == NOWHERE || rnum < 0 || rnum > top_of_world) {
send_to_char(ch, "You are not in a valid room.\r\n");
return;
}
znum = world[rnum].zone;
if (znum < 0 || znum > top_of_zone_table) {
send_to_char(ch, "This room is not attached to a valid zone.\r\n");
return;
}
/* Optional: permission check */
if (!can_edit_zone(ch, znum)) {
send_to_char(ch, "You dont have permission to modify zone %d.\r\n",
zone_table[znum].number);
return;
}
/* Save the owning zone's .toml file so the room data persists */
ok = save_rooms(znum);
if (ok)
ok = RoomSave_now(rnum);
if (!ok) {
send_to_char(ch, "rsave: failed.\r\n");
mudlog(BRF, GET_LEVEL(ch), TRUE,
"RSAVE FAIL: %s room %d (rnum=%d) zone %d (znum=%d)",
GET_NAME(ch), GET_ROOM_VNUM(rnum), rnum,
zone_table[znum].number, znum);
return;
}
send_to_char(ch, "rsave: room %d saved to world file for zone %d.\r\n",
GET_ROOM_VNUM(rnum), zone_table[znum].number);
mudlog(CMP, GET_LEVEL(ch), TRUE,
"RSAVE OK: %s room %d (rnum=%d) -> world/wld/%d.toml",
GET_NAME(ch), GET_ROOM_VNUM(rnum), rnum, zone_table[znum].number);
}
/* Write saved rooms under lib/world/rsv/<vnum>.toml (like wld/ zon/ obj/). */
#ifndef ROOMSAVE_PREFIX
#define ROOMSAVE_PREFIX LIB_WORLD "rsv/"
#endif
#ifndef ROOMSAVE_EXT
#define ROOMSAVE_EXT ".toml"
#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;
}
static struct obj_data *RS_create_obj_by_vnum(obj_vnum ov);
static struct char_data *RS_create_mob_by_vnum(mob_vnum mv);
static void RS_apply_inventory_loadout(struct char_data *mob);
/* --- RoomSave TOML helpers --- */
static int roomsave_toml_get_int_default(toml_table_t *tab, const char *key, int def)
{
toml_datum_t v = toml_int_in(tab, key);
if (v.ok)
return (int)v.u.i;
return def;
}
static int roomsave_toml_get_int_array(toml_table_t *tab, const char *key, int *out, int out_len)
{
toml_array_t *arr;
int i, n;
if (!tab || !out || out_len <= 0)
return 0;
arr = toml_array_in(tab, key);
if (!arr)
return 0;
n = toml_array_nelem(arr);
if (n > out_len)
n = out_len;
for (i = 0; i < n; i++) {
toml_datum_t v = toml_int_at(arr, i);
if (v.ok)
out[i] = (int)v.u.i;
}
return n;
}
struct roomsave_obj {
int vnum;
int timer;
int weight;
int cost;
int cost_per_day;
int extra_flags[EF_ARRAY_MAX];
int wear_flags[TW_ARRAY_MAX];
int values[NUM_OBJ_VAL_POSITIONS];
int full;
struct roomsave_obj *contents;
struct roomsave_obj *next;
};
struct roomsave_mob_item {
int vnum;
int wear_pos;
struct roomsave_obj *contents;
struct roomsave_mob_item *next;
};
struct roomsave_mob {
int vnum;
struct roomsave_mob_item *equipment;
struct roomsave_mob_item *inventory;
struct roomsave_mob *next;
};
struct roomsave_room {
int vnum;
long saved_at;
struct roomsave_obj *objects;
struct roomsave_mob *mobs;
struct roomsave_room *next;
};
static void roomsave_free_obj_list(struct roomsave_obj *obj)
{
while (obj) {
struct roomsave_obj *next = obj->next;
if (obj->contents)
roomsave_free_obj_list(obj->contents);
free(obj);
obj = next;
}
}
static void roomsave_free_mob_items(struct roomsave_mob_item *item)
{
while (item) {
struct roomsave_mob_item *next = item->next;
if (item->contents)
roomsave_free_obj_list(item->contents);
free(item);
item = next;
}
}
static void roomsave_free_mobs(struct roomsave_mob *mob)
{
while (mob) {
struct roomsave_mob *next = mob->next;
roomsave_free_mob_items(mob->equipment);
roomsave_free_mob_items(mob->inventory);
free(mob);
mob = next;
}
}
static void roomsave_free_rooms(struct roomsave_room *room)
{
while (room) {
struct roomsave_room *next = room->next;
roomsave_free_obj_list(room->objects);
roomsave_free_mobs(room->mobs);
free(room);
room = next;
}
}
static struct roomsave_obj *roomsave_parse_obj_table(toml_table_t *tab, int full)
{
struct roomsave_obj *obj;
toml_array_t *arr;
int i;
if (!tab)
return NULL;
CREATE(obj, struct roomsave_obj, 1);
obj->vnum = roomsave_toml_get_int_default(tab, "vnum", -1);
obj->full = full;
if (obj->vnum <= 0) {
free(obj);
return NULL;
}
if (full) {
obj->timer = roomsave_toml_get_int_default(tab, "timer", 0);
obj->weight = roomsave_toml_get_int_default(tab, "weight", 0);
obj->cost = roomsave_toml_get_int_default(tab, "cost", 0);
obj->cost_per_day = roomsave_toml_get_int_default(tab, "cost_per_day", 0);
for (i = 0; i < EF_ARRAY_MAX; i++)
obj->extra_flags[i] = 0;
for (i = 0; i < TW_ARRAY_MAX; i++)
obj->wear_flags[i] = 0;
for (i = 0; i < NUM_OBJ_VAL_POSITIONS; i++)
obj->values[i] = 0;
roomsave_toml_get_int_array(tab, "extra_flags", obj->extra_flags, EF_ARRAY_MAX);
roomsave_toml_get_int_array(tab, "wear_flags", obj->wear_flags, TW_ARRAY_MAX);
roomsave_toml_get_int_array(tab, "values", obj->values, NUM_OBJ_VAL_POSITIONS);
}
arr = toml_array_in(tab, "contents");
if (arr) {
struct roomsave_obj *tail = NULL;
int n = toml_array_nelem(arr);
for (i = 0; i < n; i++) {
toml_table_t *ctab = toml_table_at(arr, i);
struct roomsave_obj *child = roomsave_parse_obj_table(ctab, full);
if (!child)
continue;
child->next = NULL;
if (!obj->contents) {
obj->contents = tail = child;
} else {
tail->next = child;
tail = child;
}
}
}
return obj;
}
static struct roomsave_mob_item *roomsave_parse_mob_item(toml_table_t *tab, int with_wear)
{
struct roomsave_mob_item *item;
toml_array_t *arr;
int i;
if (!tab)
return NULL;
CREATE(item, struct roomsave_mob_item, 1);
item->vnum = roomsave_toml_get_int_default(tab, "vnum", -1);
item->wear_pos = with_wear ? roomsave_toml_get_int_default(tab, "wear_pos", 0) : -1;
if (item->vnum <= 0) {
free(item);
return NULL;
}
arr = toml_array_in(tab, "contents");
if (arr) {
struct roomsave_obj *tail = NULL;
int n = toml_array_nelem(arr);
for (i = 0; i < n; i++) {
toml_table_t *ctab = toml_table_at(arr, i);
struct roomsave_obj *child = roomsave_parse_obj_table(ctab, 0);
if (!child)
continue;
child->next = NULL;
if (!item->contents) {
item->contents = tail = child;
} else {
tail->next = child;
tail = child;
}
}
}
return item;
}
static struct roomsave_mob *roomsave_parse_mob_table(toml_table_t *tab)
{
struct roomsave_mob *mob;
toml_array_t *arr;
int i;
if (!tab)
return NULL;
CREATE(mob, struct roomsave_mob, 1);
mob->vnum = roomsave_toml_get_int_default(tab, "vnum", -1);
if (mob->vnum <= 0) {
free(mob);
return NULL;
}
arr = toml_array_in(tab, "equipment");
if (arr) {
struct roomsave_mob_item *tail = NULL;
int n = toml_array_nelem(arr);
for (i = 0; i < n; i++) {
toml_table_t *itab = toml_table_at(arr, i);
struct roomsave_mob_item *item = roomsave_parse_mob_item(itab, 1);
if (!item)
continue;
item->next = NULL;
if (!mob->equipment) {
mob->equipment = tail = item;
} else {
tail->next = item;
tail = item;
}
}
}
arr = toml_array_in(tab, "inventory");
if (arr) {
struct roomsave_mob_item *tail = NULL;
int n = toml_array_nelem(arr);
for (i = 0; i < n; i++) {
toml_table_t *itab = toml_table_at(arr, i);
struct roomsave_mob_item *item = roomsave_parse_mob_item(itab, 0);
if (!item)
continue;
item->next = NULL;
if (!mob->inventory) {
mob->inventory = tail = item;
} else {
tail->next = item;
tail = item;
}
}
}
return mob;
}
static struct roomsave_room *roomsave_parse_room_table(toml_table_t *tab)
{
struct roomsave_room *room;
toml_array_t *arr;
int i;
if (!tab)
return NULL;
CREATE(room, struct roomsave_room, 1);
room->vnum = roomsave_toml_get_int_default(tab, "vnum", -1);
room->saved_at = (long)roomsave_toml_get_int_default(tab, "saved_at", 0);
if (room->vnum <= 0) {
free(room);
return NULL;
}
arr = toml_array_in(tab, "object");
if (arr) {
struct roomsave_obj *tail = NULL;
int n = toml_array_nelem(arr);
for (i = 0; i < n; i++) {
toml_table_t *otab = toml_table_at(arr, i);
struct roomsave_obj *obj = roomsave_parse_obj_table(otab, 1);
if (!obj)
continue;
obj->next = NULL;
if (!room->objects) {
room->objects = tail = obj;
} else {
tail->next = obj;
tail = obj;
}
}
}
arr = toml_array_in(tab, "mob");
if (arr) {
struct roomsave_mob *tail = NULL;
int n = toml_array_nelem(arr);
for (i = 0; i < n; i++) {
toml_table_t *mtab = toml_table_at(arr, i);
struct roomsave_mob *mob = roomsave_parse_mob_table(mtab);
if (!mob)
continue;
mob->next = NULL;
if (!room->mobs) {
room->mobs = tail = mob;
} else {
tail->next = mob;
tail = mob;
}
}
}
return room;
}
static struct roomsave_room *roomsave_load_file_toml(const char *path)
{
FILE *fp;
toml_table_t *tab;
toml_array_t *arr;
char errbuf[200];
struct roomsave_room *head = NULL, *tail = NULL;
int i, n;
fp = fopen(path, "r");
if (!fp)
return NULL;
tab = toml_parse_file(fp, errbuf, sizeof(errbuf));
fclose(fp);
if (!tab) {
mudlog(NRM, LVL_IMMORT, TRUE, "RoomSave: parsing %s: %s", path, errbuf);
return NULL;
}
arr = toml_array_in(tab, "room");
if (!arr) {
toml_free(tab);
return NULL;
}
n = toml_array_nelem(arr);
for (i = 0; i < n; i++) {
toml_table_t *rtab = toml_table_at(arr, i);
struct roomsave_room *room = roomsave_parse_room_table(rtab);
if (!room)
continue;
room->next = NULL;
if (!head) {
head = tail = room;
} else {
tail->next = room;
tail = room;
}
}
toml_free(tab);
return head;
}
static void roomsave_write_inline_int_array(FILE *fp, const int *vals, int len)
{
int i;
fputc('[', fp);
for (i = 0; i < len; i++) {
if (i)
fputs(", ", fp);
fprintf(fp, "%d", vals[i]);
}
fputc(']', fp);
}
static void roomsave_write_int_array(FILE *fp, const char *key, const int *vals, int len)
{
fprintf(fp, "%s = ", key);
roomsave_write_inline_int_array(fp, vals, len);
fputc('\n', fp);
}
static void roomsave_write_inline_obj(FILE *fp, struct roomsave_obj *obj)
{
fputs("{ vnum = ", fp);
fprintf(fp, "%d", obj->vnum);
if (obj->full) {
fprintf(fp, ", timer = %d, weight = %d, cost = %d, cost_per_day = %d",
obj->timer, obj->weight, obj->cost, obj->cost_per_day);
fputs(", extra_flags = ", fp);
roomsave_write_inline_int_array(fp, obj->extra_flags, EF_ARRAY_MAX);
fputs(", wear_flags = ", fp);
roomsave_write_inline_int_array(fp, obj->wear_flags, TW_ARRAY_MAX);
fputs(", values = ", fp);
roomsave_write_inline_int_array(fp, obj->values, NUM_OBJ_VAL_POSITIONS);
}
fputs(", contents = [", fp);
if (obj->contents) {
struct roomsave_obj *child;
int first = 1;
for (child = obj->contents; child; child = child->next) {
if (!first)
fputs(", ", fp);
roomsave_write_inline_obj(fp, child);
first = 0;
}
}
fputs("] }", fp);
}
static void roomsave_write_room_objects(FILE *fp, struct roomsave_obj *obj)
{
for (; obj; obj = obj->next) {
fprintf(fp, "\n[[room.object]]\n");
fprintf(fp, "vnum = %d\n", obj->vnum);
fprintf(fp, "timer = %d\n", obj->timer);
fprintf(fp, "weight = %d\n", obj->weight);
fprintf(fp, "cost = %d\n", obj->cost);
fprintf(fp, "cost_per_day = %d\n", obj->cost_per_day);
roomsave_write_int_array(fp, "extra_flags", obj->extra_flags, EF_ARRAY_MAX);
roomsave_write_int_array(fp, "wear_flags", obj->wear_flags, TW_ARRAY_MAX);
roomsave_write_int_array(fp, "values", obj->values, NUM_OBJ_VAL_POSITIONS);
fputs("contents = [", fp);
if (obj->contents) {
struct roomsave_obj *child;
int first = 1;
for (child = obj->contents; child; child = child->next) {
if (!first)
fputs(", ", fp);
roomsave_write_inline_obj(fp, child);
first = 0;
}
}
fputs("]\n", fp);
}
}
static void roomsave_write_mob_items(FILE *fp, const char *table_name, struct roomsave_mob_item *item)
{
for (; item; item = item->next) {
fprintf(fp, "\n[[room.mob.%s]]\n", table_name);
if (!strcmp(table_name, "equipment"))
fprintf(fp, "wear_pos = %d\n", item->wear_pos);
fprintf(fp, "vnum = %d\n", item->vnum);
fputs("contents = [", fp);
if (item->contents) {
struct roomsave_obj *child;
int first = 1;
for (child = item->contents; child; child = child->next) {
if (!first)
fputs(", ", fp);
roomsave_write_inline_obj(fp, child);
first = 0;
}
}
fputs("]\n", fp);
}
}
static void roomsave_write_rooms(FILE *fp, struct roomsave_room *room)
{
if (!room) {
fputs("room = []\n", fp);
return;
}
for (; room; room = room->next) {
fprintf(fp, "[[room]]\n");
fprintf(fp, "vnum = %d\n", room->vnum);
fprintf(fp, "saved_at = %ld\n", room->saved_at);
roomsave_write_room_objects(fp, room->objects);
if (room->mobs) {
struct roomsave_mob *mob;
for (mob = room->mobs; mob; mob = mob->next) {
fprintf(fp, "\n[[room.mob]]\n");
fprintf(fp, "vnum = %d\n", mob->vnum);
roomsave_write_mob_items(fp, "equipment", mob->equipment);
roomsave_write_mob_items(fp, "inventory", mob->inventory);
}
}
fputs("\n", fp);
}
}
static void roomsave_apply_obj_fields(struct obj_data *obj, struct roomsave_obj *rs)
{
int i;
if (!obj || !rs || !rs->full)
return;
GET_OBJ_TIMER(obj) = rs->timer;
GET_OBJ_WEIGHT(obj) = rs->weight;
GET_OBJ_COST(obj) = rs->cost;
GET_OBJ_COST_PER_DAY(obj) = rs->cost_per_day;
#if defined(EF_ARRAY_MAX) && defined(GET_OBJ_EXTRA_AR)
for (i = 0; i < EF_ARRAY_MAX; i++)
GET_OBJ_EXTRA_AR(obj, i) = rs->extra_flags[i];
#elif defined(EF_ARRAY_MAX)
for (i = 0; i < EF_ARRAY_MAX; i++)
GET_OBJ_EXTRA(obj)[i] = rs->extra_flags[i];
#else
GET_OBJ_EXTRA(obj) = rs->extra_flags[0];
#endif
#ifdef TW_ARRAY_MAX
for (i = 0; i < TW_ARRAY_MAX; i++)
GET_OBJ_WEAR(obj)[i] = rs->wear_flags[i];
#else
GET_OBJ_WEAR(obj) = rs->wear_flags[0];
#endif
#ifdef NUM_OBJ_VAL_POSITIONS
for (i = 0; i < NUM_OBJ_VAL_POSITIONS; i++)
GET_OBJ_VAL(obj, i) = rs->values[i];
#else
for (i = 0; i < 6; i++)
GET_OBJ_VAL(obj, i) = rs->values[i];
#endif
}
static void roomsave_restore_obj_contents(struct obj_data *parent, struct roomsave_obj *list)
{
struct roomsave_obj *it;
if (!parent)
return;
for (it = list; it; it = it->next) {
struct obj_data *obj = RS_create_obj_by_vnum((obj_vnum)it->vnum);
if (!obj)
continue;
roomsave_apply_obj_fields(obj, it);
obj_to_obj(obj, parent);
if (it->contents)
roomsave_restore_obj_contents(obj, it->contents);
if (GET_OBJ_TYPE(obj) == ITEM_MONEY)
update_money_obj(obj);
}
}
static int roomsave_restore_room_objects(struct roomsave_room *room, room_rnum rnum)
{
struct roomsave_obj *it;
int count = 0;
for (it = room->objects; it; it = it->next) {
struct obj_data *obj = RS_create_obj_by_vnum((obj_vnum)it->vnum);
if (!obj)
continue;
roomsave_apply_obj_fields(obj, it);
if (it->contents)
roomsave_restore_obj_contents(obj, it->contents);
obj_to_room(obj, rnum);
if (GET_OBJ_TYPE(obj) == ITEM_MONEY)
update_money_obj(obj);
count++;
}
return count;
}
static void roomsave_restore_mob_items(struct char_data *mob, struct roomsave_mob_item *items, int is_equipment)
{
struct roomsave_mob_item *it;
for (it = items; it; it = it->next) {
struct obj_data *obj = RS_create_obj_by_vnum((obj_vnum)it->vnum);
if (!obj)
continue;
if (is_equipment) {
int pos = it->wear_pos;
if (pos < 0 || pos >= NUM_WEARS)
pos = WEAR_HOLD;
equip_char(mob, obj, pos);
} else {
obj_to_char(obj, mob);
}
if (it->contents)
roomsave_restore_obj_contents(obj, it->contents);
}
}
static int roomsave_restore_room_mobs(struct roomsave_room *room, room_rnum rnum)
{
struct roomsave_mob *it;
int count = 0;
for (it = room->mobs; it; it = it->next) {
struct char_data *mob = RS_create_mob_by_vnum((mob_vnum)it->vnum);
int saw_inventory = 0;
if (!mob)
continue;
char_to_room(mob, rnum);
if (IN_ROOM(mob) != rnum)
char_to_room(mob, rnum);
roomsave_restore_mob_items(mob, it->equipment, 1);
roomsave_restore_mob_items(mob, it->inventory, 0);
if (it->inventory)
saw_inventory = 1;
if (!saw_inventory)
RS_apply_inventory_loadout(mob);
count++;
}
return count;
}
static struct roomsave_obj *roomsave_build_obj(struct obj_data *obj, int full)
{
struct roomsave_obj *rs;
int i;
if (!obj || GET_OBJ_VNUM(obj) <= 0)
return NULL;
CREATE(rs, struct roomsave_obj, 1);
rs->vnum = (int)GET_OBJ_VNUM(obj);
rs->full = full;
if (full) {
rs->timer = GET_OBJ_TIMER(obj);
rs->weight = GET_OBJ_WEIGHT(obj);
rs->cost = GET_OBJ_COST(obj);
rs->cost_per_day = GET_OBJ_COST_PER_DAY(obj);
#if defined(EF_ARRAY_MAX) && defined(GET_OBJ_EXTRA_AR)
for (i = 0; i < EF_ARRAY_MAX; i++)
rs->extra_flags[i] = GET_OBJ_EXTRA_AR(obj, i);
#elif defined(EF_ARRAY_MAX)
for (i = 0; i < EF_ARRAY_MAX; i++)
rs->extra_flags[i] = GET_OBJ_EXTRA(obj)[i];
#else
rs->extra_flags[0] = GET_OBJ_EXTRA(obj);
#endif
#ifdef TW_ARRAY_MAX
for (i = 0; i < TW_ARRAY_MAX; i++)
rs->wear_flags[i] = GET_OBJ_WEAR(obj)[i];
#else
rs->wear_flags[0] = GET_OBJ_WEAR(obj);
#endif
#ifdef NUM_OBJ_VAL_POSITIONS
for (i = 0; i < NUM_OBJ_VAL_POSITIONS; i++)
rs->values[i] = GET_OBJ_VAL(obj, i);
#else
for (i = 0; i < 6; i++)
rs->values[i] = GET_OBJ_VAL(obj, i);
#endif
}
if (obj->contains) {
struct roomsave_obj *tail = NULL;
struct obj_data *child;
for (child = obj->contains; child; child = child->next_content) {
struct roomsave_obj *child_rs = roomsave_build_obj(child, full);
if (!child_rs)
continue;
child_rs->next = NULL;
if (!rs->contents) {
rs->contents = tail = child_rs;
} else {
tail->next = child_rs;
tail = child_rs;
}
}
}
return rs;
}
static struct roomsave_mob_item *roomsave_build_mob_item(struct obj_data *obj, int wear_pos)
{
struct roomsave_mob_item *item;
struct obj_data *child;
struct roomsave_obj *tail = NULL;
if (!obj || GET_OBJ_VNUM(obj) <= 0)
return NULL;
CREATE(item, struct roomsave_mob_item, 1);
item->vnum = (int)GET_OBJ_VNUM(obj);
item->wear_pos = wear_pos;
for (child = obj->contains; child; child = child->next_content) {
struct roomsave_obj *child_rs = roomsave_build_obj(child, 0);
if (!child_rs)
continue;
child_rs->next = NULL;
if (!item->contents) {
item->contents = tail = child_rs;
} else {
tail->next = child_rs;
tail = child_rs;
}
}
return item;
}
static struct roomsave_mob *roomsave_build_mob(struct char_data *mob)
{
struct roomsave_mob *rs;
int w;
struct obj_data *obj;
struct roomsave_mob_item *tail;
if (!mob || !IS_NPC(mob) || GET_MOB_VNUM(mob) <= 0)
return NULL;
CREATE(rs, struct roomsave_mob, 1);
rs->vnum = (int)GET_MOB_VNUM(mob);
tail = NULL;
for (w = 0; w < NUM_WEARS; w++) {
obj = GET_EQ(mob, w);
if (!obj)
continue;
{
struct roomsave_mob_item *item = roomsave_build_mob_item(obj, w);
if (!item)
continue;
item->next = NULL;
if (!rs->equipment) {
rs->equipment = tail = item;
} else {
tail->next = item;
tail = item;
}
}
}
tail = NULL;
for (obj = mob->carrying; obj; obj = obj->next_content) {
struct roomsave_mob_item *item = roomsave_build_mob_item(obj, -1);
if (!item)
continue;
item->next = NULL;
if (!rs->inventory) {
rs->inventory = tail = item;
} else {
tail->next = item;
tail = item;
}
}
return rs;
}
static struct roomsave_room *roomsave_build_room(room_rnum rnum)
{
struct roomsave_room *room;
struct roomsave_obj *tail_obj = NULL;
struct roomsave_mob *tail_mob = NULL;
struct obj_data *obj;
struct char_data *mob;
if (rnum == NOWHERE)
return NULL;
CREATE(room, struct roomsave_room, 1);
room->vnum = world[rnum].number;
room->saved_at = time(0);
for (obj = world[rnum].contents; obj; obj = obj->next_content) {
struct roomsave_obj *rs = roomsave_build_obj(obj, 1);
if (!rs)
continue;
rs->next = NULL;
if (!room->objects) {
room->objects = tail_obj = rs;
} else {
tail_obj->next = rs;
tail_obj = rs;
}
}
for (mob = world[rnum].people; mob; mob = mob->next_in_room) {
struct roomsave_mob *rs = roomsave_build_mob(mob);
if (!rs)
continue;
rs->next = NULL;
if (!room->mobs) {
room->mobs = tail_mob = rs;
} else {
tail_mob->next = rs;
tail_mob = rs;
}
}
return 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 || rnum < 0 || rnum > top_of_world) return 0;
zone_rnum znum = world[rnum].zone;
if (znum < 0 || znum > top_of_zone_table) return 0;
return zone_table[znum].number; /* e.g., 1 for rooms 100199, 2 for 200299, etc. */
}
/* lib/world/rsv/<zone>.toml */
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);
}
/* Public: write the entire rooms contents */
int RoomSave_now(room_rnum rnum) {
char path[PATH_MAX], tmp[PATH_MAX];
FILE *out = NULL;
struct roomsave_room *rooms = NULL, *it = NULL, *prev = NULL;
struct roomsave_room *new_room = 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));
{
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;
}
}
rooms = roomsave_load_file_toml(path);
new_room = roomsave_build_room(rnum);
if (!new_room)
return 0;
for (it = rooms; it; it = it->next) {
if (it->vnum == (int)rvnum)
break;
prev = it;
}
if (it) {
roomsave_free_obj_list(it->objects);
roomsave_free_mobs(it->mobs);
it->saved_at = new_room->saved_at;
it->objects = new_room->objects;
it->mobs = new_room->mobs;
free(new_room);
} else {
if (!rooms) {
rooms = new_room;
} else if (prev) {
prev->next = new_room;
}
}
if (!(out = fopen(tmp, "w"))) {
mudlog(NRM, LVL_IMMORT, TRUE,
"SYSERR: RoomSave: fopen(%s) failed: %s",
tmp, strerror(errno));
roomsave_free_rooms(rooms);
return 0;
}
roomsave_write_rooms(out, rooms);
if (fclose(out) != 0) {
mudlog(NRM, LVL_IMMORT, TRUE,
"SYSERR: RoomSave: fclose(%s) failed: %s",
tmp, strerror(errno));
roomsave_free_rooms(rooms);
return 0;
}
if (rename(tmp, path) != 0) {
mudlog(NRM, LVL_IMMORT, TRUE,
"SYSERR: RoomSave: rename(%s -> %s) failed: %s",
tmp, path, strerror(errno));
roomsave_free_rooms(rooms);
return 0;
}
roomsave_free_rooms(rooms);
return 1;
}
static struct obj_data *RS_create_obj_by_vnum(obj_vnum ov) {
obj_rnum ornum;
if (ov <= 0) return NULL;
ornum = real_object(ov);
if (ornum == NOTHING) return NULL;
return read_object(ornum, REAL);
}
static struct char_data *RS_create_mob_by_vnum(mob_vnum mv) {
mob_rnum mrnum;
if (mv <= 0) return NULL;
mrnum = real_mobile(mv);
if (mrnum == NOBODY) return NULL;
return read_mobile(mrnum, REAL);
}
static void RS_apply_inventory_loadout(struct char_data *mob) {
mob_rnum rnum;
const struct mob_loadout *e;
struct obj_data *stack[16];
int i;
if (!mob || !IS_NPC(mob)) return;
rnum = GET_MOB_RNUM(mob);
if (rnum < 0) return;
for (i = 0; i < (int)(sizeof(stack) / sizeof(stack[0])); i++)
stack[i] = NULL;
for (e = mob_proto[rnum].proto_loadout; e; e = e->next) {
int qty, n;
if (e->wear_pos >= 0)
continue;
qty = (e->quantity > 0) ? e->quantity : 1;
for (n = 0; n < qty; n++) {
struct obj_data *obj = RS_create_obj_by_vnum(e->vnum);
if (!obj) {
log("SYSERR: RS_apply_inventory_loadout: bad obj vnum %d on mob %d",
e->vnum, GET_MOB_VNUM(mob));
continue;
}
if (e->wear_pos == -1) {
for (i = 0; i < (int)(sizeof(stack) / sizeof(stack[0])); i++)
stack[i] = NULL;
obj_to_char(obj, mob);
if (obj_is_storage(obj) || GET_OBJ_TYPE(obj) == ITEM_FURNITURE)
stack[0] = obj;
continue;
}
{
int depth = -(e->wear_pos) - 1;
if (depth <= 0 ||
depth >= (int)(sizeof(stack) / sizeof(stack[0])) ||
!stack[depth - 1]) {
obj_to_char(obj, mob);
continue;
}
obj_to_obj(obj, stack[depth - 1]);
if (obj_is_storage(obj) || GET_OBJ_TYPE(obj) == ITEM_FURNITURE) {
stack[depth] = obj;
for (i = depth + 1; i < (int)(sizeof(stack) / sizeof(stack[0])); i++)
stack[i] = NULL;
}
}
}
}
}
/* Optional autosave hook (invoked by limits.c:point_update). */
void RoomSave_autosave_tick(void) {
/* Iterate all rooms; only save flagged ones. */
for (room_rnum rnum = 0; rnum <= top_of_world; ++rnum) {
if (ROOM_FLAGGED(rnum, ROOM_SAVE))
RoomSave_now(rnum);
}
}
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 *.toml", ROOMSAVE_PREFIX);
while ((dp = readdir(dirp))) {
size_t n = strlen(dp->d_name);
size_t extlen = strlen(ROOMSAVE_EXT);
if (n <= extlen) continue; /* skip . and .. */
if (strcmp(dp->d_name + n - extlen, ROOMSAVE_EXT) != 0) continue;
{
char path[PATH_MAX];
struct roomsave_room *rooms;
struct roomsave_room *room;
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;
}
log("RoomSave: reading %s", path);
rooms = roomsave_load_file_toml(path);
if (!rooms)
continue;
int blocks = 0;
int restored_objs_total = 0;
int restored_mobs_total = 0;
for (room = rooms; room; room = room->next) {
room_rnum rnum = real_room((room_vnum)room->vnum);
int count_objs = 0;
int count_mobs = 0;
blocks++;
if (rnum == NOWHERE) {
mudlog(NRM, LVL_IMMORT, FALSE,
"RoomSave: unknown room vnum %d in %s (skipping)",
room->vnum, path);
continue;
}
while (world[rnum].contents)
extract_obj(world[rnum].contents);
count_objs = roomsave_restore_room_objects(room, rnum);
count_mobs = roomsave_restore_room_mobs(room, rnum);
restored_objs_total += count_objs;
restored_mobs_total += count_mobs;
if (count_mobs > 0)
log("RoomSave: room %d <- %d object(s) and %d mob(s)",
room->vnum, count_objs, count_mobs);
else
log("RoomSave: room %d <- %d object(s)", room->vnum, count_objs);
}
log("RoomSave: finished %s (blocks=%d, objects=%d, mobs=%d)",
path, blocks, restored_objs_total, restored_mobs_total);
roomsave_free_rooms(rooms);
}
}
closedir(dirp);
}
/* ======== MOB SAVE: write NPCs and their equipment/inventory ========== */