#include <types.h>
#include <stat.h>
#include <stdio.h>
#include <ctype.h>
#include "lint.h"
#include "interpret.h"
#include "object.h"
#include "lnode.h"
#include "sent.h"
#include "config.h"
#include "wiz_list.h"
extern int d_flag;
extern char *xalloc PROT ((int)), *string_copy PROT ((char *));
void remove_swap_file PROT ((struct object *));
extern int atoi ();
extern struct object *previous_ob;
int tot_alloc_object;
/*
* Replace newlines in a string with a carriage return, to make the string
* writeable on one line.
*/
static void
replace_newline (str)
char *str;
{
for (; *str; str++)
{
if (str[0] == '\n')
str[0] = '\r';
}
}
/*
* Replace carriage return in a string with newlines.
*/
static void
restore_newline (str)
char *str;
{
for (; *str; str++)
{
if (str[0] == '\r')
str[0] = '\n';
}
}
/*
* Save an object to a file. Two kinds of saves are allowed.
* First: standard objects from /room and /obj can save anywhere. These are
* "trusted".
* Second: A wizard defined object can only save at the home directory
* of that wizard.
*/
void
save_object (ob, file)
struct object *ob;
char *file;
{
char *name, tmp_name[40];
int len;
FILE *f;
struct lnode_var_def *p;
int failed = 0;
if (ob->wl)
{
if (strncmp (file, "players/", 8) != 0 ||
strncmp (file + 8, ob->wl->name, strlen (ob->wl->name)) != 0 ||
strchr (file, '.'))
{
error ("Illegal save file name %s\n", file);
}
}
else if (strncmp (current_object->name, "obj/", 4) != 0 &&
strncmp (current_object->name, "room/", 5) != 0)
{
error ("Illegal use of save_object()\n");
}
len = strlen (file);
name = xalloc (len + 3);
(void) strcpy (name, file);
(void) strcat (name, ".o");
sprintf (tmp_name, "tmp_file_name_%d", getpid ());
f = fopen (tmp_name, "w");
if (f == 0)
{
fatal ("Could not open %s for a save.\n", tmp_name);
abort ();
}
for (p = ob->status; p; p = p->next)
{
struct svalue *v = &ob->variables[p->num_var];
if (p->is_static)
continue;
if (fprintf (f, "%s ", p->name) == EOF)
failed = 1;
else
failed = emit_value (f, v);
}
(void) unlink (name);
/* if (link (tmp_name, name) == -1)*/
/* No link in VMS, so use rename () (bub) */
if (rename (tmp_name, name) == -1)
{
perror (name);
printf ("Failed to link %s to %s\n", tmp_name, name);
add_message ("Failed to save object !\n");
}
(void) fclose (f);
/*** Since it is rename'ed, we don't have to unlink it. (bub)***/
/* unlink (tmp_name);*/
free (name);
if (failed)
add_message ("Failed to save to file. Disk could be full.\n");
}
/* emit value - returns "failed" status. */
emit_value (f, v)
struct svalue *v;
FILE *f;
{
int failed = 0;
if (v->type == T_NUMBER)
{
if (fprintf (f, "%d\n", v->u.number) == EOF)
failed = 1;
}
else if (v->type == T_STRING)
{
failed = emit_string (f, v->u.string);
}
else if (v->type == T_POINTER)
{
failed = emit_vector (f, v->u.vec);
}
else
failed = (EOF == fprintf (f, "0\n"));
return failed;
}
/* emit string (value, not name). Uses new-style strings - these are preceded
* by a ' character and require no translation.
*/
int
emit_string (f, s)
FILE *f;
char *s;
{
char *c;
int failed = 0;
if (EOF == fprintf (f, "'"))
return 1; /* failed */
c = s;
while (*c)
{
if (*c == '"' || *c == '\\' || *c == '\n')
failed = failed || (putc ('\\', f) == EOF);
failed = failed || (EOF == putc (*c, f));
c++;
}
failed = failed || (EOF == fprintf (f, "\"\n"));
return failed;
}
int
emit_vector (f, v)
FILE *f;
struct vector *v;
{
int x = 0;
if (EOF == fprintf (f, "{ %d\n", v->size))
return 1;
while (x < v->size)
{
if (emit_value (f, &v->item[x]))
return 1;
x++;
}
return (EOF == fprintf (f, " }\n"));
}
int
restore_object (ob, file)
struct object *ob;
char *file;
{
char *name, *buff, *space;
int len;
FILE *f;
struct lnode_var_def *p;
struct object *save = current_object;
struct stat st;
int x;
len = strlen (file);
name = xalloc (len + 3);
(void) strcpy (name, file);
if (name[len - 2] == '.' && name[len - 1] == 'c')
name[len - 1] = 'o';
else
(void) strcat (name, ".o");
f = fopen (name, "r");
if (f == 0)
return 0;
if (fstat (fileno (f), &st) == -1)
{
perror (name);
return 0;
}
if (st.st_size == 0)
return 0;
buff = xalloc (st.st_size + 1);
current_object = ob;
while (1)
{
struct svalue *v;
x = fscanf (f, " %s ", buff);
if (x == EOF)
break;
if (x == 0)
error ("Illegal format when restore %s.\n", name);
p = find_status (buff, 0);
if (p == 0 || p->is_static)
v = 0; /* need this to skip value in file... */
else
{
v = &ob->variables[p->num_var];
assign_svalue (v, &const0); /* ensure it is zeroed out */
}
if (read_value (f, buff, v))
break;
}
current_object = save;
if (d_flag)
debug_message ("Object %s restored from %s.\n", ob->name, name);
free (name);
free (buff);
(void) fclose (f);
return 1;
}
/* read_value reads a value, using buff as scratch pad, placing the value
* in v. V can be a null pointer. Assumes first character is non-space.
*/
read_value (f, buff, v)
FILE *f;
char *buff;
struct svalue *v;
{
char c;
int x, y;
c = getc (f);
if (c == EOF)
return 1;
switch (c)
{
case '{' : /* vector } */
return read_vector (f, buff, v);
case '"' :
return read_old_string (f, buff, v);
case '\'' :
return read_new_string (f, buff, v);
default :
if (isdigit (c) || c == '-')
{
ungetc (c, f);
x = fscanf (f, " %d ", &y);
if (x != 1)
return 1;
if (v)
assign_svalue (v, make_number (y));
return 0;
}
error ("Bad restore file format\n");
return (1);
}
}
read_old_string (f, buff, v)
FILE *f;
char *buff;
struct svalue *v;
{
char *s = buff;
char c = 1;
while (1)
{
*s = '\0';
c = getc (f);
if (!c || c == EOF)
{
error ("Bad old style string in restore\n");
return 1;
}
if (c == '"')
{ /* possible string term */
c = getc (f);
if (c != '\n')
{
ungetc (c, f);
c = '"';
goto okchar;
}
if (v)
{
assign_svalue (v, make_string (buff));
}
return 0;
}
if (c == '\r')
c = '\n';
okchar :
*(s++) = c;
}
}
read_new_string (f, buff, v)
FILE *f;
char *buff;
struct svalue *v;
{
char *s = buff;
char c;
while (1)
{
*s = '\0';
c = getc (f);
if (c == EOF || !c)
{
error ("Bad string in restore\n");
return 1;
}
if (c == '"')
{ /* string term */
if (v)
{
assign_svalue (v, make_string (buff));
}
return 0;
}
if (c == '\\')
{
c = getc (f);
if (!c || c == EOF)
{
error ("Bad string in restore\n");
return 1;
}
}
*(s++) = c;
}
}
read_vector (f, buff, v)
FILE *f;
char *buff;
struct svalue *v;
{
int x, y, num;
x = fscanf (f, " %d ", &num);
if (!v)
return skip_vector (f, buff);
if (x != 1)
{
error ("Bad vector format (num elts) in restore\n");
return 1;
}
assign_svalue (v, allocate_array (num));
for (y = 0; y < num; y++)
{
if (fscanf (f, " ") == EOF)
{
error ("Unbalanced vector in restore\n");
return 1;
}
if (read_value (f, buff, &v->u.vec->item[y]))
return 1;
}
if (fscanf (f, " ") == EOF)
{
error ("Unbalanced vector in restore\n");
return 1;
}
x = getc (f); /* { */
if (x != '}')
{
error ("Expected end of vector in restore\n");
return 1;
}
return 0;
}
skip_vector (f, buff)
FILE *f;
char *buff;
{
char c;
int nest = 1;
int in_string = 0;
while (nest > 0)
{
c = getc (f);
if (c == EOF)
{
error ("Unbalanced vector (was skipping) in restore\n");
return 1;
}
if (c == '\'' && !in_string)
{
in_string = 1;
continue;
}
if (c == '"')
{
in_string = !in_string;
continue;
}
if (c == '\\' && in_string)
{
c = getc (f);
if (c == EOF)
{
error ("Unterminated string in skipped vector\n");
return 1;
}
continue;
}
if (in_string)
continue;
if (c == '{')
{
nest++;
continue;
}
if (c == '}')
{
nest--;
continue;
}
}
return 0;
}
/* This is replaced by hashed names for living objects ! */
/*
* Search for a living object which answers to the id NAME.
* If there are many living objects, this algorithm can become quite
* heavy. It should be hashed in the future !
*/
struct object *
old_find_living (name)
char *name;
{
struct object *ob;
struct value *ret;
struct value thing;
for (ob = obj_list; ob; ob = ob->next_all)
{
if (!ob->enable_commands || ob->super == 0)
continue;
thing.type = T_STRING,
thing.u.string = name;
ret = apply ("id", ob, &thing);
/* Did the object self destruct ? (unlikely, but possible) */
if (ob->destructed)
return 0;
if (ret == 0 || (ret->type == T_NUMBER && ret->u.number == 0))
continue;
return ob;
}
return 0;
}
void
tell_npc (ob, str)
struct object *ob;
char *str;
{
struct value thing;
thing.type = T_STRING;
thing.u.string = str;
(void) apply ("catch_tell", ob, &thing);
}
/*
* Send a message to an object.
* If it is an interactive object, it will go to his
* screen. Otherwise, it will go to a local function
* catch_tell() in that object. This enables communications
* between players and NPC's, and between other NPC's.
*/
void
tell_object (ob, str)
struct object *ob;
char *str;
{
struct object *save_command_giver;
if (ob->interactive)
{
save_command_giver = command_giver;
command_giver = ob;
add_message ("%s", str);
command_giver = save_command_giver;
return;
}
tell_npc (ob, str);
}
void
free_object (ob, from)
struct object *ob;
char *from;
{
struct sentence *s;
void check_ob_ref PROT ((struct object *, char *));
ob->ref--;
if (d_flag)
debug_message ("Subtr ref to ob %s: %d (%s)\n", ob->name,
ob->ref, from);
check_ob_ref (ob, from);
if (ob->ref > 0)
return;
if (!ob->destructed)
{
/* This is fatal, and should never happen. */
fprintf (stderr,
"FATAL: Object 0x%x %s ref count 0, but not destructed (from %s).\n",
ob, ob->name, from);
if (current_object)
fprintf (stderr, "Current object: %s\n", ob->name);
ob->ref = 1; /* Not good, but better than crash the game ! */
return;
}
ob->reset = 0;
if (ob->interactive)
fatal ("Tried to free an interactive object.\n");
/*
* If the program is freed, then we can also free the variable
* declarations.
*/
if (ob->prog)
{
if (free_prog ((struct lnode_def *) ob->prog, ob->block,
ob->swap_size))
{
if (ob->status && !ob->block)
free_sub_part ((struct lnode *) ob->status, 1);
ob->status = 0;
ob->prog = 0;
ob->block = 0;
}
}
if (ob->swap_num != -1)
remove_swap_file (ob);
for (s = ob->sent; s;)
{
struct sentence *next;
next = s->next;
free_sentence (s);
s = next;
}
if (ob->name)
{
if (d_flag)
debug_message ("Free object %s\n", ob->name);
if (lookup_object_hash (ob->name) == ob)
fatal ("Freeing object %s but name still in name table", ob->name);
free (ob->name);
ob->name = 0;
}
#if 0 /* Variables are now allocated together with the struct object */
if (ob->variables)
{
free ((char *) ob->variables);
ob->variables = 0;
}
#endif
tot_alloc_object--;
free ((char *) ob);
}
void
add_ref (ob, from)
struct object *ob;
char *from;
{
ob->ref++;
if (d_flag)
debug_message ("Add reference to object %s: %d (%s)\n", ob->name,
ob->ref, from);
}
struct object *
get_empty_object (num_var)
int num_var;
{
struct object *ob;
int size =
sizeof (struct object) + (num_var - 1) * sizeof (struct svalue);
int i;
tot_alloc_object++;
ob = (struct object *) xalloc (size);
memset ((char *) ob, '\0', size);
ob->ref = 1;
ob->swap_num = -1; /* Object not swapped. */
ob->num_variables = num_var;
for (i = 0; i < num_var; i++)
{
ob->variables[i].type = T_NUMBER;
/* ob->variables[i].u.number = 0; Done with memset above */
}
return ob;
}
void
dump_all_objects ()
{
struct object *ob;
FILE *d;
d = fopen ("OBJ_DUMP", "w");
if (!d)
{
add_message ("Couldn't open dump file.\n");
return;
}
add_message ("Dumping data to 'OBJ_DUMP'... ");
for (ob = obj_list; ob; ob = ob->next_all)
{
fprintf (d, "%s (0x%x) ", ob->name, ob);
if (ob->super)
fprintf (d, "Super %s (0x%x)", ob->super->name, ob->super);
fprintf (d, "\t Ref %2d Rst %d Enbl_cmd %d Clnd %d Hrt_bt %d lang ref %d swp %d hp %x\n",
ob->ref, ob->reset, ob->enable_commands, ob->cloned,
ob->enable_heart_beat, ob->prog == 0 ? 1 : ob->prog->num_ref,
ob->swapped,
ob->heart_beat);
}
fclose (d);
add_message ("DONE\n");
}
/*
* This one is used for extreme emergency.
*/
void
verify (str, where)
char *str, *where;
{
static struct object *harry;
struct object *ob;
int num_ref, i;
int count_value_ref PROT ((struct object *));
if (!harry)
harry = find_living_object (str, 1);
if (!harry)
return;
if (harry->ref == 0)
{
debug_message ("%20s verify %s ref 0\n", where, str);
harry = 0;
}
num_ref = 0;
for (ob = obj_list; ob; ob = ob->next_all)
{
if (ob->num_variables == 0)
continue;
for (i = 0; i < ob->num_variables; i++)
{
if (ob->variables[i].type == T_OBJECT &&
ob->variables[i].u.ob == harry)
{
num_ref++;
debug_message ("var in '%s'\n", ob->name);
}
}
}
i = count_value_ref (harry);
debug_message ("%20s: num ref %2d : %2d+%2d = %2d\n", where, harry->ref,
num_ref, i, i + num_ref);
}
void
remove_all_objects ()
{
struct object *ob;
struct value v;
v.type = T_OBJECT;
while (1)
{
if (obj_list == 0)
break;
ob = obj_list;
ob->inherited = 0; /* Force destruction */
v.u.ob = ob;
destruct_object (&v);
if (ob == obj_list)
break;
}
remove_destructed_objects ();
}
/*
* For debugging purposes.
*/
void
check_ob_ref (ob, from)
struct object *ob;
char *from;
{
struct object *o;
int i;
if (!ob->inherited)
return;
for (o = obj_list, i = 0; o; o = o->next_all)
{
if (o->inherit == ob)
i++;
}
if (i + 1 > ob->ref)
{
fprintf (stderr,
"FATAL too many references to inherited object %s (%d) from %s.\n",
ob->name, ob->ref, from);
if (current_object)
fprintf (stderr, "current_object: %s\n", current_object->name);
for (o = obj_list; o; o = o->next_all)
{
if (o->inherit != ob)
continue;
fprintf (stderr, " %s\n", ob->name);
}
}
}
static struct object *hashed_living[LIVING_HASH_SIZE];
static int num_living_names, num_searches = 1, search_length = 1;
static int
hash_living_name (str)
char *str;
{
unsigned ret = 0;
while (*str)
ret = ret * 2 + *str++;
return ret % LIVING_HASH_SIZE;
}
struct object *
find_living_object (str, player)
char *str;
int player;
{
struct object **obp, *tmp;
struct object **hl;
num_searches++;
hl = &hashed_living[hash_living_name (str)];
for (obp = hl; *obp; obp = &(*obp)->next_hashed_living)
{
search_length++;
if (player && !(*obp)->once_interactive)
continue;
if ((*obp)->enable_commands == 0)
continue;
if (strcmp ((*obp)->living_name, str) == 0)
break;
}
if (*obp == 0) /* return 0 */
{
if (player)
return 0;
else
return old_find_living (str);
} /* temporarily compatibility hack */
/* this slows down the game.. */
/* return 0 when your mudlib is fixed */
/* Move the found ob first. */
if (obp == hl)
return *obp;
tmp = *obp;
*obp = tmp->next_hashed_living;
tmp->next_hashed_living = *hl;
*hl = tmp;
return tmp;
}
void
set_living_name (ob, str)
struct object *ob;
char *str;
{
struct object **hl;
struct value *v;
if (ob->living_name)
{
remove_living_name (ob);
ob->next_hashed_living = 0;
}
else
{
num_living_names++;
}
hl = &hashed_living[hash_living_name (str)];
ob->next_hashed_living = *hl;
*hl = ob;
ob->living_name = make_shared_string (str);
if (ob->interactive)
{
v = apply ("query_level", ob, 0);
if (v && v->type == T_NUMBER && v->u.number >= 21)
ob->is_wizard = 1;
}
return;
}
void
remove_living_name (ob)
struct object *ob;
{
struct object **hl;
num_living_names--;
if (!ob->living_name)
fatal ("remove_living_name: no living name set.\n");
hl = &hashed_living[hash_living_name (ob->living_name)];
while (*hl)
{
if (*hl == ob)
break;
hl = &(*hl)->next_hashed_living;
}
if (*hl == 0)
fatal ("remove_living_name: Object named %s no in hash list.\n",
ob->living_name);
*hl = ob->next_hashed_living;
free_string (ob->living_name);
ob->next_hashed_living = 0;
ob->living_name = 0;
}
void
stat_living_objects ()
{
add_message ("%d named objects, average search length: %4.2f\n",
num_living_names, (double) search_length / num_searches);
}