#include "std.h" #include "comm.h" #include "backend.h" #include "add_action.h" #include "eval.h" #ifndef NO_ADD_ACTION #define MAX_VERB_BUFF 100 object_t * hashed_living[CFG_LIVING_HASH_SIZE] = { 0 }; static int num_living_names; static int num_searches = 1; static int search_length = 1; static int illegal_sentence_action; static char * last_verb; static object_t * illegal_sentence_ob; static void notify_no_command (void) { union string_or_func p; svalue_t *v; if (!command_giver || !command_giver->interactive) return; p = command_giver->interactive->default_err_message; if (command_giver->interactive->iflags & NOTIFY_FAIL_FUNC) { save_command_giver(command_giver); v = call_function_pointer(p.f, 0); restore_command_giver(); free_funp(p.f); if (command_giver && command_giver->interactive) { if (v && v->type == T_STRING) { tell_object(command_giver, v->u.string, SVALUE_STRLEN(v)); } command_giver->interactive->iflags &= ~NOTIFY_FAIL_FUNC; command_giver->interactive->default_err_message.s = 0; } } else { if (p.s) { tell_object(command_giver, p.s, strlen(p.s)); free_string(p.s); command_giver->interactive->default_err_message.s = 0; } else { tell_object(command_giver, default_fail_message, strlen(default_fail_message)); } } } void clear_notify (object_t * ob) { union string_or_func dem; interactive_t *ip = ob->interactive; dem = ip->default_err_message; if (ip->iflags & NOTIFY_FAIL_FUNC) { free_funp(dem.f); ip->iflags &= ~NOTIFY_FAIL_FUNC; } else if (dem.s) free_string(dem.s); ip->default_err_message.s = 0; } INLINE_STATIC int hash_living_name (const char *str) { return whashstr(str) & (CFG_LIVING_HASH_SIZE - 1); } object_t *find_living_object (const char* str, int user) { object_t **obp, *tmp; object_t **hl; if (!str) return 0; num_searches++; hl = &hashed_living[hash_living_name(str)]; for (obp = hl; *obp; obp = &(*obp)->next_hashed_living) { search_length++; #ifdef F_SET_HIDE if ((*obp)->flags & O_HIDDEN) { if (!valid_hide(current_object)) continue; } #endif if (user && !((*obp)->flags & O_ONCE_INTERACTIVE)) continue; if (!((*obp)->flags & O_ENABLE_COMMANDS)) continue; if (strcmp((*obp)->living_name, str) == 0) break; } if (*obp == 0) return 0; /* 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 remove_living_name (object_t * ob) { object_t **hl; ob->flags &= ~O_ENABLE_COMMANDS; if (!ob->living_name) return; num_living_names--; DEBUG_CHECK(!ob->living_name, "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; } DEBUG_CHECK1(*hl == 0, "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; } static void set_living_name (object_t * ob, const char *str) { int flags = ob->flags & O_ENABLE_COMMANDS; object_t **hl; if (ob->flags & O_DESTRUCTED) return; remove_living_name(ob); num_living_names++; hl = &hashed_living[hash_living_name(str)]; ob->next_hashed_living = *hl; *hl = ob; ob->living_name = make_shared_string(str); ob->flags |= flags; } void stat_living_objects (outbuffer_t * out) { outbuf_add(out, "Hash table of living objects:\n"); outbuf_add(out, "-----------------------------\n"); outbuf_addv(out, "%d living named objects, average search length: %4.2f\n\n", num_living_names, (double) search_length / num_searches); } void setup_new_commands (object_t * dest, object_t * item) { object_t *next_ob, *ob; /* * Setup the new commands. The order is very important, as commands in * the room should override commands defined by the room. Beware that * init() in the room may have moved 'item' ! * * The call of init() should really be done by the object itself (except in * the -o mode). It might be too slow, though :-( */ if (item->flags & O_ENABLE_COMMANDS) { save_command_giver(item); (void) apply(APPLY_INIT, dest, 0, ORIGIN_DRIVER); restore_command_giver(); if (item->super != dest) return; } /* * Run init of the item once for every present user, and for the * environment (which can be a user). */ for (ob = dest->contains; ob; ob = next_ob) { next_ob = ob->next_inv; if (ob == item) continue; if (ob->flags & O_DESTRUCTED) error("An object was destructed at call of " APPLY_INIT "()\n"); if (ob->flags & O_ENABLE_COMMANDS) { save_command_giver(ob); (void) apply(APPLY_INIT, item, 0, ORIGIN_DRIVER); restore_command_giver(); if (dest != item->super) return; } if (item->flags & O_DESTRUCTED) /* marion */ error("The object to be moved was destructed at call of " APPLY_INIT "()\n"); if (ob->flags & O_DESTRUCTED) /* Alaron */ error("An object was destructed at call of " APPLY_INIT "()\n"); if (item->flags & O_ENABLE_COMMANDS) { save_command_giver(item); (void) apply(APPLY_INIT, ob, 0, ORIGIN_DRIVER); restore_command_giver(); if (dest != item->super) return; } } if (dest->flags & O_DESTRUCTED) /* marion */ error("The destination to move to was destructed at call of " APPLY_INIT "()\n"); if (item->flags & O_DESTRUCTED) /* Alaron */ error("The object to be moved was destructed at call of " APPLY_INIT "()\n"); if (dest->flags & O_ENABLE_COMMANDS) { save_command_giver(dest); (void) apply(APPLY_INIT, item, 0, ORIGIN_DRIVER); restore_command_giver(); } } /* * This will enable an object to use commands normally only * accessible by interactive users. * Also check if the user is a wizard. Wizards must not affect the * value of the wizlist ranking. */ static void enable_commands (int num) { #ifndef NO_ENVIRONMENT object_t *pp; #endif if (current_object->flags & O_DESTRUCTED) return; debug(d_flag, ("Enable commands /%s (ref %d)", current_object->obname, current_object->ref)); if (num) { current_object->flags |= O_ENABLE_COMMANDS; set_command_giver(current_object); } else { #ifndef NO_ENVIRONMENT /* Remove all sentences defined for the object */ if (current_object->flags & O_ENABLE_COMMANDS) { if (current_object->super) { remove_sent(current_object->super, current_object); for (pp = current_object->super->contains; pp; pp = pp->next_inv) remove_sent(pp, current_object); } for (pp = current_object->contains; pp; pp = pp->next_inv) remove_sent(pp, current_object); } #endif current_object->flags &= ~O_ENABLE_COMMANDS; if (current_object == command_giver) set_command_giver(0); } } /* * Find the sentence for a command from the user. * Return success status. */ static int user_parser (char * buff) { char verb_buff[MAX_VERB_BUFF]; sentence_t *s; char *p; int length; char *user_verb = 0; int where; int save_illegal_sentence_action; debug(d_flag, ("cmd [/%s]: %s\n", command_giver->obname, buff)); /* strip trailing spaces. */ for (p = buff + strlen(buff) - 1; p >= buff; p--) { if (*p != ' ') break; *p = '\0'; } if (buff[0] == '\0') return 0; length = p - buff + 1; p = strchr(buff, ' '); if (p == 0) { user_verb = findstring(buff); } else { *p = '\0'; user_verb = findstring(buff); *p = ' '; length = p - buff; } if (!user_verb) { /* either an xverb or a verb without a specific add_action */ user_verb = buff; } /* * copy user_verb into a static character buffer to be pointed to by * last_verb. */ strncpy(verb_buff, user_verb, MAX_VERB_BUFF - 1); if (p) { int pos; pos = p - buff; if (pos < MAX_VERB_BUFF) { verb_buff[pos] = '\0'; } } save_illegal_sentence_action = illegal_sentence_action; illegal_sentence_action = 0; for (s = command_giver->sent; s; s = s->next) { svalue_t *ret; object_t *command_object; if (s->flags & (V_NOSPACE | V_SHORT)) { if (strncmp(buff, s->verb, strlen(s->verb)) != 0) continue; } else { /* note: if was add_action(blah, "") then accept it */ if (s->verb[0] && (user_verb != s->verb)) continue; } /* * Now we have found a special sentence ! */ if (!(s->flags & V_FUNCTION)) debug(d_flag, ("Local command %s on /%s", s->function.s, s->ob->obname)); if (s->flags & V_NOSPACE) { int l1 = strlen(s->verb); int l2 = strlen(verb_buff); if (l1 < l2) last_verb = verb_buff + l1; else last_verb = ""; } else { if (!s->verb[0] || (s->flags & V_SHORT)) last_verb = verb_buff; else last_verb = s->verb; } /* * If the function is static and not defined by current object, then * it will fail. If this is called directly from user input, then * the origin is the driver and it will be allowed. */ where = (current_object ? ORIGIN_EFUN : ORIGIN_DRIVER); /* * Remember the object, to update moves. */ command_object = s->ob; save_command_giver(command_giver); if (s->flags & V_NOSPACE) { copy_and_push_string(&buff[strlen(s->verb)]); } else if (buff[length] == ' ') { copy_and_push_string(&buff[length + 1]); } else { push_undefined(); } if (s->flags & V_FUNCTION) { ret = call_function_pointer(s->function.f, 1); } else { if (s->function.s[0] == APPLY___INIT_SPECIAL_CHAR) error("Illegal function name.\n"); ret = apply(s->function.s, s->ob, 1, where); } /* s may be dangling at this point */ restore_command_giver(); last_verb = 0; /* was this the right verb? */ if (ret == 0) { /* is it still around? Otherwise, ignore this ... it moved somewhere or dested itself */ if (s == command_giver->sent) { char buf[256]; if (s->flags & V_FUNCTION) { sprintf(buf, "Verb '%s' bound to uncallable function pointer.\n", s->verb); error(buf); } else { sprintf(buf, "Function for verb '%s' not found.\n", s->verb); error(buf); } } } if (ret && (ret->type != T_NUMBER || ret->u.number != 0)) { #ifdef PACKAGE_MUDLIB_STATS if (command_giver && command_giver->interactive #ifndef NO_WIZARDS && !(command_giver->flags & O_IS_WIZARD) #endif ) add_moves(&command_object->stats, 1); #endif if (!illegal_sentence_action) illegal_sentence_action = save_illegal_sentence_action; return 1; } if (illegal_sentence_action) { switch (illegal_sentence_action) { case 1: error("Illegal to call remove_action() [caller was /%s] from a verb returning zero.\n", illegal_sentence_ob->obname); case 2: error("Illegal to move or destruct an object (/%s) defining actions from a verb function which returns zero.\n", illegal_sentence_ob->obname); } } } notify_no_command(); illegal_sentence_action = save_illegal_sentence_action; return 0; } /* * Take a user command and parse it. * The command can also come from a NPC. * Beware that 'str' can be modified and extended ! */ int parse_command (char * str, object_t * ob) { int res; /* disallow users to issue commands containing ansi escape codes */ #if defined(NO_ANSI) && !defined(STRIP_BEFORE_PROCESS_INPUT) char *c; for (c = str; *c; c++) { if (*c == 27) { *c = ' '; /* replace ESC with ' ' */ } } #endif save_command_giver(ob); res = user_parser(str); restore_command_giver(); return res; } /* * Associate a command with function in this object. * * The optinal third argument is a flag that will state that the verb should * only match against leading characters. * * The object must be near the command giver, so that we ensure that the * sentence is removed when the command giver leaves. * * If the call is from a shadow, make it look like it is really from * the shadowed object. */ static void add_action (svalue_t * str, const char *cmd, int flag) { sentence_t *p; object_t *ob; if (current_object->flags & O_DESTRUCTED) return; ob = current_object; #ifndef NO_SHADOWS while (ob->shadowing) { ob = ob->shadowing; } /* don't allow add_actions of a static function from a shadowing object */ if ((ob != current_object) && str->type == T_STRING && is_static(str->u.string, ob)) { return; } #endif if (command_giver == 0 || (command_giver->flags & O_DESTRUCTED)) return; if (ob != command_giver #ifndef NO_ENVIRONMENT && ob->super != command_giver && ob->super != command_giver->super && ob != command_giver->super #endif ) return; /* No need for an error, they know what they * did wrong. */ p = alloc_sentence(); if (str->type == T_STRING) { debug(d_flag, ("--Add action %s", str->u.string)); p->function.s = make_shared_string(str->u.string); p->flags = flag; } else { debug(d_flag, ("--Add action ")); p->function.f = str->u.fp; str->u.fp->hdr.ref++; p->flags = flag | V_FUNCTION; } p->ob = ob; p->verb = make_shared_string(cmd); /* This is ok; adding to the top of the list doesn't harm anything */ p->next = command_giver->sent; command_giver->sent = p; } /* * Remove sentence with specified verb and action. Return 1 * if success. If command_giver, remove his action, otherwise * remove current_object's action. */ static int remove_action (const char *act, const char *verb) { object_t *ob; sentence_t **s; if (command_giver) ob = command_giver; else ob = current_object; if (ob) { for (s = &ob->sent; *s; s = &((*s)->next)) { sentence_t *tmp; if (((*s)->ob == current_object) && (!((*s)->flags & V_FUNCTION)) && !strcmp((*s)->function.s, act) && !strcmp((*s)->verb, verb)) { tmp = *s; *s = tmp->next; free_sentence(tmp); illegal_sentence_action = 1; illegal_sentence_ob = current_object; return 1; } } } return 0; } /* * Remove all commands (sentences) defined by object 'ob' in object * 'user' */ #ifndef NO_ENVIRONMENT void remove_sent (object_t * ob, object_t * user) { sentence_t **s; if (!(user->flags & O_ENABLE_COMMANDS)) return; for (s = &user->sent; *s;) { sentence_t *tmp; if ((*s)->ob == ob) { #ifdef DEBUG if (!((*s)->flags & V_FUNCTION)) debug(d_flag, ("--Unlinking sentence %s\n", (*s)->function.s)); #endif tmp = *s; *s = tmp->next; free_sentence(tmp); illegal_sentence_action = 2; illegal_sentence_ob = ob; } else s = &((*s)->next); } } #endif #ifdef F_ADD_ACTION void f_add_action (void) { long flag; if (st_num_arg == 3) { flag = (sp--)->u.number; } else flag = 0; if (sp->type == T_ARRAY) { int i, n = sp->u.arr->size; svalue_t *sv = sp->u.arr->item; for (i = 0; i < n; i++) { if (sv[i].type == T_STRING) { add_action(sp-1, sv[i].u.string, flag & 3); } } free_array((sp--)->u.arr); } else { add_action((sp-1), sp->u.string, flag & 3); free_string_svalue(sp--); } pop_stack(); } #endif #ifdef F_COMMAND /* * Execute a command for an object. Copy the command into a * new buffer, because 'parse_command()' can modify the command. * If the object is not current object, static functions will not * be executed. This will prevent forcing users to do illegal things. * * Return cost of the command executed if success (> 0). * When failure, return 0. */ void f_command (void) { long rc = 0; if (current_object && !(current_object->flags & O_DESTRUCTED)) { char buff[1000]; int save_eval_cost = get_eval(); if (SVALUE_STRLEN(sp) > sizeof(buff) - 1) error("Too long command.\n"); strncpy(buff, sp->u.string, sizeof(buff)); buff[sizeof(buff) - 1] = 0; if (parse_command(buff, current_object)) #ifndef WIN32 rc = save_eval_cost - get_eval(); #else rc = 1; #endif } free_string_svalue(sp); put_number(rc); } #endif #ifdef F_COMMANDS void f_commands (void) { push_refed_array(commands(current_object)); } #endif #ifdef F_DISABLE_COMMANDS void f_disable_commands (void) { enable_commands(0); } #endif #ifdef F_ENABLE_COMMANDS void f_enable_commands (void) { enable_commands(1); } #endif #ifdef F_FIND_LIVING void f_find_living (void) { object_t *ob; ob = find_living_object(sp->u.string, 0); free_string_svalue(sp); /* safe b/c destructed objects have had their living names removed */ if (ob) { put_unrefed_undested_object(ob, "find_living"); } else { *sp = const0; } } #endif #ifdef F_FIND_PLAYER void f_find_player (void) { object_t *ob; ob = find_living_object(sp->u.string, 1); free_string_svalue(sp); /* safe b/c destructed objects have had their living names removed */ if (ob) { put_unrefed_undested_object(ob, "find_living"); } else { *sp = const0; } } #endif #ifdef F_LIVING void f_living (void) { if (sp->u.ob->flags & O_ENABLE_COMMANDS) { free_object(&sp->u.ob, "f_living:1"); *sp = const1; } else { free_object(&sp->u.ob, "f_living:2"); *sp = const0; } } #endif #ifdef F_LIVINGS void f_livings (void) { push_refed_array(livings()); } #endif #ifdef F_NOTIFY_FAIL void f_notify_fail (void) { if (command_giver && command_giver->interactive) { clear_notify(command_giver); if (sp->type == T_STRING) { command_giver->interactive->default_err_message.s = make_shared_string(sp->u.string); } else { command_giver->interactive->iflags |= NOTIFY_FAIL_FUNC; command_giver->interactive->default_err_message.f = sp->u.fp; sp->u.fp->hdr.ref++; } } pop_stack(); } #endif #ifdef F_QUERY_VERB void f_query_verb (void) { if (!last_verb) { push_number(0); return; } share_and_push_string(last_verb); } #endif #ifdef F_REMOVE_ACTION void f_remove_action (void) { long success; success = remove_action((sp - 1)->u.string, sp->u.string); free_string_svalue(sp--); free_string_svalue(sp); put_number(success); } #endif #ifdef F_SET_LIVING_NAME void f_set_living_name (void) { set_living_name(current_object, sp->u.string); free_string_svalue(sp--); } #endif #endif /* ! NO_ADD_ACTION */