#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); }