/*--------------------------------------------------------------------------- * Player command action handling. * *--------------------------------------------------------------------------- * The gamedriver offers a simple but quite effective way of associating * functions with commands, and parsing the user input for these commands. * * Central element are the 'actions': the association of a verb with * a specific function in a specific object. To allow a living (an object * with the O_ENABLE_COMMANDS flag set) to use a verb, the action has * to be added to the living by the object with the efun add_action(). * This usually happens whenever the living enters the vicinity of the * object defining the action, but could happen anytime. The restriction * on this is that living and action-defining object must share the same * vicinity: one must be contained in the other, or share the same * environment. If one of the two leaves the shared vicinity, all actions * are automatically removed from the living. * * When the living gives a command (may it be a player or NPC), the parser * splits the command string into verb and argument, and searches the list * of added actions for a matching action. The search order is the reverse * of the order the actions are added. If an action matches the given * command, the associated function is called with the parsed argument * string as parameter. The function may now decide that it is not the * real function for this command and return with result 0; in that case * the parser continues its search with the next actions, until it finds one * whose function returns a non-zero result. * * If there is all action functions return 0, the parser prints a failure * message. This message can either be set with the efun notify_fail(), or * is provided through the H_NOTIFY_FAIL driver hook. * * Commands usually take the form of '' or ' '. * Additionally, an action may be defined as 'short verb', that is only * the first specified characters of the given command have to match. This * allows commands of the form '', like the famous * "'" say command. A special use is to use an empty string * "" as short verb, which would match every input. * * Internally all added actions are stored in a linear list of * sentence_t attributes (this is actually why the sentences were * introduced in the first place). While the parser is searching for * an action, a special SENT_MARKER sentence is introduced into this * list to mark how far the search has progressed. * * It is possible to stack commands, ie. to execute a command from within * a command. * TODO: Make actions optional on three levels: none, only the sentence * TODO:: management+match_command(), full. *--------------------------------------------------------------------------- */ #include "driver.h" #include "typedefs.h" #include "my-alloca.h" #include #include "actions.h" #include "array.h" #include "backend.h" #include "closure.h" #include "comm.h" #include "dumpstat.h" #include "efuns.h" /* is_wizard_used */ #include "interpret.h" #include "mapping.h" #include "mstrings.h" #include "object.h" #include "stdstrings.h" #include "sent.h" #include "simulate.h" #include "svalue.h" #include "wiz_list.h" #include "xalloc.h" #include "../mudlib/sys/commands.h" #include "../mudlib/sys/driver_hook.h" object_t *command_giver; /* The object for which the current command is executed. * The reference is not counted. */ /*-------------------------------------------------------------------------*/ #ifdef USE_ACTIONS #define COMMAND_FOR_OBJECT_BUFSIZE 1000 /* The maximum length of a command. */ /* --- struct command_context_s: last command context --- * * This structure saves the previous command context on the runtime * context stack for nested commands. * * For the very first command the previous context is all 0. */ struct command_context_s { rt_context_t rt; /* the rt_context superclass */ object_t * this_player; /* the command_giver */ object_t * mark_player; /* the marked command_giver */ action_t * marker; /* the marker sentence */ char * cmd; /* the full command, a stack buffer */ string_t * verb; /* the tabled verb */ string_t * action_verb; /* the tabled action verb */ svalue_t errmsg; /* the error message */ object_t * errobj; /* object which set the error message */ }; /*-------------------------------------------------------------------------*/ /* All the following variables constitute the runtime context for a command, * and are saved during the execution of nested commands. */ static string_t *last_verb = NULL; /* During a command execution, this is the tabled string with the * given command verb. */ static string_t *last_action_verb = NULL; /* During a command execution, this is the tabled string with the * command verb as specified in the action definition. */ static char *last_command = NULL; /* During a command execution, this points to a (stack) buffer with * the full command. */ static svalue_t error_msg = { T_INVALID }; /* The error message to be printed when the command can't be found. */ static object_t * error_obj = NULL; /* The object which set the error message (counted reference). */ static action_t * command_marker = NULL; /* During a command search/execution, when the search reaches the * end of the sentence list, the marker is stored in this variable * instead of in the list. * The 'ob' in this sentence is the current value of rt_context. */ static object_t * marked_command_giver = NULL; /* During a command search/execution, this points to the object * which sentence list is searched (and thus augmented with the * marker sentence). Usually, this value is identical to command_giver. * The reference is not counted. */ p_int alloc_action_sent = 0; /* Statistic: how many action sentences have been allocated. */ /*-------------------------------------------------------------------------*/ void free_action_temporaries (void) /* GC support: Free the global variables which keep references to objects * and svalues. Outside of a command execution these are usually 0, but * unfortunately not always (using notify_fail() outside of a command * execution for example leaves a value behind). */ { if (error_msg.type != T_INVALID) free_svalue(&error_msg); error_msg.type = T_INVALID; if (error_obj) free_object(error_obj, "free_action_temporaries"); error_obj = NULL; if (last_verb) free_mstring(last_verb); last_verb = NULL; if (last_action_verb) free_mstring(last_action_verb); last_action_verb = NULL; } /* free_action_temporaries() */ /*-------------------------------------------------------------------------*/ static INLINE action_t * new_action_sent(void) /* Allocate a new empty action sentence and return it. */ { action_t *p; xallocate(p, sizeof *p, "new action sentence"); alloc_action_sent++; p->verb = NULL; p->function = NULL; p->ob = NULL; #ifdef USE_SHADOWING p->shadow_ob = NULL; #endif return p; } /* new_action_sent() */ /*-------------------------------------------------------------------------*/ static INLINE void _free_action_sent (action_t *p) /* Free the action sentence

and all data held by it. */ { #ifdef DEBUG if (SENT_IS_INTERNAL(p->sent.type) && SENT_MARKER != p->sent.type) fatal("free_action_sent() received internal sent %d\n", p->sent.type); #endif if (p->function) free_mstring(p->function); if (p->verb) free_mstring(p->verb); xfree(p); alloc_action_sent--; } /* _free_action_sent() */ void free_action_sent (action_t *p) { _free_action_sent(p); } #define free_action_sent(p) _free_action_sent(p) /*-------------------------------------------------------------------------*/ static INLINE void save_command_context (struct command_context_s * context) /* Save the current command context into (but don't put it * onto the context stack). The saved global variables are zeroed out. */ { context->rt.type = COMMAND_CONTEXT; context->verb = last_verb; context->action_verb = last_action_verb; context->cmd = last_command; context->mark_player = marked_command_giver; if (marked_command_giver) ref_object(marked_command_giver, "save_command_context"); context->this_player = command_giver; if (command_giver) ref_object(command_giver, "save_command_context"); context->marker = command_marker; transfer_svalue_no_free(&(context->errmsg), &error_msg); context->errobj = error_obj; command_giver = NULL; last_verb = NULL; last_action_verb = NULL; last_command = NULL; marked_command_giver = NULL; command_marker = NULL; error_msg.type = T_INVALID; error_obj = NULL; } /* save_command_context() */ /*-------------------------------------------------------------------------*/ static INLINE void _restore_command_context (struct command_context_s * context) /* Restore the last command context from . The global vars * are properly freed before they are overwritten. */ { /* Clear up the current context */ if (last_verb) free_mstring(last_verb); if (last_action_verb) free_mstring(last_action_verb); if (command_marker) free_action_sent(command_marker); else if (marked_command_giver && !(O_DESTRUCTED & marked_command_giver->flags)) remove_action_sent((object_t *)context, marked_command_giver); /* Restore the previous context */ last_verb = context->verb; last_action_verb = context->action_verb; last_command = context->cmd; command_giver = check_object(context->this_player); if (context->this_player) free_object(context->this_player, "restore_command_context"); marked_command_giver = check_object(context->mark_player); if (context->mark_player) free_object(context->mark_player, "restore_command_context"); command_marker = context->marker; transfer_svalue(&error_msg, &(context->errmsg)); if (error_obj) free_object(error_obj, "_restore_command_context"); error_obj = context->errobj; } /* _restore_command_context() */ void restore_command_context (rt_context_t *context) { _restore_command_context((struct command_context_s *)context); } #define restore_command_context(c) _restore_command_context(c) /*-------------------------------------------------------------------------*/ void remove_action_sent (object_t *ob, object_t *player) /* Remove all actions defined by and attached to . */ { sentence_t **s; /* A simple list walk */ for (s = &player->sent; *s;) { action_t *tmp; tmp = (action_t *)*s; if (tmp->ob == ob) { #ifdef DEBUG if (d_flag > 1) { if (tmp->function && tmp->verb) debug_message("%s --Unlinking sentence fun='%s', verb='%s'\n" , time_stamp(), get_txt(tmp->function) , get_txt(tmp->verb)); else if (tmp->function) debug_message("%s --Unlinking sentence fun='%s', verb=0\n" , time_stamp(), get_txt(tmp->function)); else if (tmp->verb) debug_message("%s --Unlinking sentence fun=0, verb='%s'\n" , time_stamp(), get_txt(tmp->verb)); else debug_message("%s --Unlinking sentence fun=0, verb=0\n" , time_stamp()); } #endif #ifdef CHECK_OBJECT_REF if (s == &player->sent) update_object_sent(player, tmp->sent.next); else *s = tmp->sent.next; #else *s = tmp->sent.next; #endif /* CHECK_OBJECT_REF */ free_action_sent(tmp); } else s = &((*s)->next); } } /* remove_action_sent() */ /*-------------------------------------------------------------------------*/ #ifdef USE_SHADOWING void remove_shadow_action_sent (object_t *ob, object_t *player) /* Remove all actions defined by and attached to . */ { sentence_t **s; /* A simple list walk */ for (s = &player->sent; *s;) { action_t *tmp; tmp = (action_t *)*s; if (tmp->shadow_ob == ob) { #ifdef DEBUG if (d_flag > 1) { if (tmp->function && tmp->verb) debug_message("%s --Unlinking sentence fun='%s', verb='%s'\n" , time_stamp(), get_txt(tmp->function) , get_txt(tmp->verb)); else if (tmp->function) debug_message("%s --Unlinking sentence fun='%s', verb=0\n" , time_stamp(), get_txt(tmp->function)); else if (tmp->verb) debug_message("%s --Unlinking sentence fun=0, verb='%s'\n" , time_stamp(), get_txt(tmp->verb)); else debug_message("%s --Unlinking sentence fun=0, verb=0\n" , time_stamp()); } #endif #ifdef CHECK_OBJECT_REF if (s == &player->sent) update_object_sent(player, tmp->sent.next); else *s = tmp->sent.next; #else *s = tmp->sent.next; #endif /* CHECK_OBJECT_REF */ free_action_sent(tmp); } else s = &((*s)->next); } } /* remove_shadow_action_sent() */ #endif /*-------------------------------------------------------------------------*/ #ifdef USE_INVENTORIES void remove_environment_sent (object_t *player) /* Remove all actions on defined by objects with the same * environment (which includes the environment object). */ { sentence_t **p; action_t *s; object_t *super, *ob; super = player->super; p= &player->sent; if ( NULL != (s = (action_t *)*p) ) for(;;) { ob = s->ob; if (!SENT_IS_INTERNAL(s->sent.type) && ((ob->super == super && ob != player) || ob == super ) ) { do { action_t *tmp; #ifdef DEBUG if (d_flag > 1) debug_message("%s --Unlinking sentence %s\n" , time_stamp(), get_txt(s->function)); #endif tmp = s; s = (action_t *)s->sent.next; free_action_sent(tmp); if (!s) { #ifdef CHECK_OBJECT_REF if (p == &player->sent) update_object_sent(player, NULL); else *p = NULL; #else *p = NULL; #endif /* CHECK_OBJECT_REF */ return; } } while (s->ob == ob); #ifdef CHECK_OBJECT_REF if (p == &player->sent) update_object_sent(player, (sentence_t *)s); else *p = (sentence_t *)s; #else *p = (sentence_t *)s; #endif /* CHECK_OBJECT_REF */ } else { do { p = &s->sent.next; if (!(s = (action_t *)*p)) return; } while (s->ob == ob); } } } /* remove_environment_sent() */ #endif /*-------------------------------------------------------------------------*/ #ifdef USE_SHADOWING void remove_shadow_actions (object_t *shadow, object_t *target) /* Remove all shadow actions defined by and attached to or * an object in 's vicinity. */ { #ifdef USE_INVENTORIES object_t *item; #endif remove_shadow_action_sent(shadow, target); #ifdef USE_INVENTORIES for (item = target->contains; item; item = item->next_inv) { if (shadow != item) remove_shadow_action_sent(shadow, item); } if (target->super) { remove_shadow_action_sent(shadow, target->super); for (item = target->super->contains; item; item = item->next_inv) { if (shadow != item && target != item) remove_shadow_action_sent(shadow, item); } } #endif } /* remove_shadow_actions() */ #endif /*-------------------------------------------------------------------------*/ static Bool call_modify_command (char *buff) /* Call the modify_command function/hook for the given command in * and replace the text in with the new command (if any). * * Return FALSE if everything is ok, and TRUE if something happened * (like the command_giver selfdestructed or the hook already finished * executing the command). */ { interactive_t *ip; svalue_t *svp; svp = NULL; if (O_SET_INTERACTIVE(ip, command_giver) && ip->modify_command ) { object_t *ob = ip->modify_command; if (ob->flags & O_DESTRUCTED) { ip->modify_command = 0; free_object(ob, "modify_command"); } else if (driver_hook[H_MODIFY_COMMAND_FNAME].type == T_STRING) { push_c_string(inter_sp, buff); svp = sapply(driver_hook[H_MODIFY_COMMAND_FNAME].u.str, ob, 1); /* !command_giver means that the command_giver has been dested. */ if (!command_giver) return MY_TRUE; } } else { if (driver_hook[H_MODIFY_COMMAND].type == T_CLOSURE) { lambda_t *l; l = driver_hook[H_MODIFY_COMMAND].u.lambda; if (driver_hook[H_MODIFY_COMMAND].x.closure_type == CLOSURE_LAMBDA) { free_object(l->ob, "call_modify_command"); l->ob = ref_object(command_giver, "call_modify_command"); } push_c_string(inter_sp, buff); push_ref_object(inter_sp, command_giver, "call_modify_command"); call_lambda(&driver_hook[H_MODIFY_COMMAND], 2); transfer_svalue(svp = &apply_return_value, inter_sp--); if (!command_giver) return MY_TRUE; } else if (driver_hook[H_MODIFY_COMMAND].type == T_STRING && !(O_DESTRUCTED & command_giver->flags)) { push_c_string(inter_sp, buff); svp = sapply(driver_hook[H_MODIFY_COMMAND].u.str, command_giver, 1); if (!command_giver) return MY_TRUE; } else if (driver_hook[H_MODIFY_COMMAND].type == T_MAPPING) { svalue_t sv; string_t * str; if ( NULL != (str = find_tabled_str(buff)) ) { put_string(&sv, str); svp = get_map_value(driver_hook[H_MODIFY_COMMAND].u.map, &sv); if (svp->type == T_CLOSURE) { push_ref_string(inter_sp, sv.u.str); push_ref_object(inter_sp, command_giver, "call_modify_command"); call_lambda(svp, 2); transfer_svalue(svp = &apply_return_value, inter_sp--); if (!command_giver) return MY_TRUE; } } } } /* If svp is not NULL, it contains the new, modified command. */ if (svp) { if (svp->type == T_STRING) { extract_cstr(buff, svp->u.str, (size_t)COMMAND_FOR_OBJECT_BUFSIZE); } else if (svp->type == T_NUMBER && svp->u.number) { return MY_TRUE; } } return MY_FALSE; } /* call_modify_command() */ /*-------------------------------------------------------------------------*/ static int special_parse (char *buff) /* Implement a few hardcoded commands. Return TRUE if such a command * was given. * TODO: Remove this feature. */ { #ifdef USE_SET_IS_WIZARD if (!is_wizard_used || command_giver->flags & O_IS_WIZARD) #endif { Bool no_curobj = MY_FALSE; if (strcmp(buff, "malloc") == 0) { strbuf_t sbuf; status_parse(&sbuf, "malloc"); strbuf_send(&sbuf); return 1; } if (strcmp(buff, "malloc extstats") == 0) { strbuf_t sbuf; status_parse(&sbuf, "malloc extstats"); strbuf_send(&sbuf); return 1; } if (strcmp(buff, "dumpallobj") == 0) { if (!current_object) { current_object = ref_object(command_giver, "dumpallobj"); no_curobj = MY_TRUE; } add_message("Dumping to /OBJ_DUMP ... "); dumpstat(STR_OBJDUMP_FNAME); dumpstat_dest(STR_DESTOBJDUMP_FNAME); add_message("done\n"); if (no_curobj) { free_object(current_object, "dumpallobj"); current_object = NULL; } return 1; } #ifdef OPCPROF /* amylaar */ if (strcmp(buff, "opcdump") == 0) { if (!current_object) { current_object = ref_object(command_giver, "opcdump"); no_curobj = MY_TRUE; } opcdump(STR_OPCDUMP_FNAME); if (no_curobj) { free_object(current_object, "opcdump"); current_object = NULL; } return 1; } #endif if (strncmp(buff, "status", (size_t)6) == 0) { Bool rc; strbuf_t sbuf; rc = status_parse(&sbuf, buff+6+(buff[6]==' ')); if (rc) strbuf_send(&sbuf); else strbuf_free(&sbuf); return rc; } } /* end of wizard-only special parse commands */ return 0; } /* special_parse() */ #endif /*-------------------------------------------------------------------------*/ static void notify_no_command (char *command, object_t *save_command_giver) /* No action could be found for , thus print a failure notice * to the command_giver. is the object for which * the command was received, usually it is identical to command_giver. * * Called by the command parser, this function evaluates the H_NOTIFY_FAIL * hook to do its job. If the hook is not set, the default_err_message * is printed. */ { #ifdef USE_ACTIONS svalue_t *svp; #endif Bool useHook; useHook = ( driver_hook[H_SEND_NOTIFY_FAIL].type == T_CLOSURE || driver_hook[H_SEND_NOTIFY_FAIL].type == T_STRING ); #ifdef USE_ACTIONS svp = &error_msg; if (svp->type == T_STRING) { if (!useHook) tell_object(command_giver, svp->u.str); else push_svalue(svp); } else if (svp->type == T_CLOSURE) { push_ref_valid_object(inter_sp, save_command_giver, "notify_no_command"); call_lambda(svp, 1); /* add_message might cause an error, thus, we free the closure first. */ if (inter_sp->type == T_STRING) { if (!useHook) { tell_object(command_giver, inter_sp->u.str); pop_stack(); } } else { pop_stack(); useHook = MY_FALSE; } } else #endif if (driver_hook[H_NOTIFY_FAIL].type == T_STRING) { if (!useHook) tell_object(command_giver, driver_hook[H_NOTIFY_FAIL].u.str); else push_svalue(&driver_hook[H_NOTIFY_FAIL]); } else if (driver_hook[H_NOTIFY_FAIL].type == T_CLOSURE) { if (driver_hook[H_NOTIFY_FAIL].x.closure_type == CLOSURE_LAMBDA) { free_object(driver_hook[H_NOTIFY_FAIL].u.lambda->ob, "notify_no_command"); driver_hook[H_NOTIFY_FAIL].u.lambda->ob = ref_object(command_giver, "notify_no_command"); } push_c_string(inter_sp, command); push_ref_valid_object(inter_sp, save_command_giver, "notify_no_command"); call_lambda(&driver_hook[H_NOTIFY_FAIL], 2); if (inter_sp->type == T_STRING) { if (!useHook) { tell_object(command_giver, inter_sp->u.str); pop_stack(); } } else { pop_stack(); useHook = MY_FALSE; } } #ifdef USE_ACTIONS else /* No H_NOTIFY_FAIL hook set, and no notify_fail() given */ { free_svalue(svp); /* remember: this is &error_msg */ svp->type = T_INVALID; if (error_obj) free_object(error_obj, "notify_no_command"); error_obj = NULL; errorf("Missing H_NOTIFY_FAIL hook, and no notify_fail() given.\n"); /* NOTREACHED */ useHook = MY_FALSE; } #endif /* If the output has to go through a hook, push the remaining * arguments and call the hook. */ if (useHook) { #ifdef USE_ACTIONS if (error_obj != NULL) push_ref_valid_object(inter_sp, error_obj, "notify-fail error_obj"); else #endif push_number(inter_sp, 0); push_ref_valid_object(inter_sp, save_command_giver, "notify-fail save_command_giver"); if (driver_hook[H_SEND_NOTIFY_FAIL].type == T_STRING) { (void)sapply_ign_prot( driver_hook[H_SEND_NOTIFY_FAIL].u.str , command_giver, 3); } else { if (driver_hook[H_SEND_NOTIFY_FAIL].x.closure_type == CLOSURE_LAMBDA) { free_object(driver_hook[H_SEND_NOTIFY_FAIL].u.lambda->ob, "notify_no_command"); driver_hook[H_SEND_NOTIFY_FAIL].u.lambda->ob = ref_object(command_giver, "notify_no_command"); } call_lambda(&driver_hook[H_SEND_NOTIFY_FAIL], 3); pop_stack(); } } #ifdef USE_ACTIONS free_svalue(svp); /* remember: this is &error_msg */ svp->type = T_INVALID; if (error_obj) free_object(error_obj, "notify_no_command"); error_obj = NULL; #endif } /* notify_no_command() */ /*-------------------------------------------------------------------------*/ static Bool parse_command (char *buff, Bool from_efun) /* Take the command in and execute it. * The command_giver and marked_command_giver are set, last_verb, * last_action_verb and last_command may be set (and will be overwritten). * * The command will be searched in the list of marked_command_giver. * * Return FALSE on failure (command not found), and TRUE on success. * * The function distinguishes two calling modes: * - !from_efun: the driver does the traditional command parsing, * that means that modify_command and notify_fail are to * be handled by the driver. * - from_efun: the mudlib handles the command parsing, and this * function must not call modify_command or notify_fail. * Also, marked_command_giver is already set and correct. * * Since this function is called from execute_command() (directly * or indirectly through f_execute_command()), it is not necessary * to clean up the globals here. */ { #ifdef USE_ACTIONS char *p; /* handy string pointer */ sentence_t *s; /* handy sentence pointer */ action_t *marker_sent; /* the marker sentence */ ptrdiff_t length; /* length of the verb */ object_t *save_current_object = current_object; object_t *save_command_giver = command_giver; #ifdef DEBUG if (d_flag > 1) debug_message("%s cmd [%s]: %s\n", time_stamp() , get_txt(command_giver->name), buff); #endif /* Strip trailing spaces. * Afterwards, p will point at the last non-space character. */ for (p = buff + strlen(buff) - 1; p >= buff; p--) { if (*p != ' ') break; *p = '\0'; } if (buff[0] == '\0') return MY_FALSE; /* Call the modify-command function * This may clobber command_giver and/or current_object. */ if (!from_efun && call_modify_command(buff)) return MY_TRUE; /* Parse for special commands */ if (!from_efun && special_parse(buff)) return MY_TRUE; /* Determine the verb and set last_verb and last_command */ if (last_verb) free_mstring(last_verb); length = p - buff; p = strchr(buff, ' '); if (p == NULL) { length += 1; last_verb = new_tabled(buff); } else { *p = '\0'; last_verb = new_tabled(buff); *p = ' '; length = p - buff; } last_command = buff; /* Get the empty marker sentence */ marker_sent = new_action_sent(); marker_sent->sent.type = SENT_MARKER; /* Scan the list of sentences for the saved command giver */ for (s = marked_command_giver->sent; s; s = s->next) { svalue_t *ret; object_t *command_object; action_t *sa; /* (action_t *)s */ unsigned char type; /* s->type */ sentence_t *next; /* used only as flag */ sentence_t *insert; /* insertion point */ sa = (action_t *)s; /* Test the current sentence */ if ((type = s->type) == SENT_PLAIN) { if (sa->verb != last_verb) continue; } else if (type == SENT_SHORT_VERB) { /* The verb may be shortened to a few leading characters, * but not shorter than .short_verb. */ size_t len; if (sa->short_verb) { len = mstrsize(last_verb); if (len < sa->short_verb || len > mstrsize(sa->verb) || ( sa->verb != last_verb && strncmp(get_txt(sa->verb), get_txt(last_verb), len) != 0)) continue; } else { len = mstrsize(sa->verb); if (strncmp(buff, get_txt(sa->verb), len) != 0) continue; } } else if (type == SENT_OLD_NO_SPACE || type == SENT_NO_SPACE) { /* The arguments may follow the verb without space, * that means we just have to check if buff[] begins * with sa->verb. */ size_t len; len = mstrsize(sa->verb); if (strncmp(buff, get_txt(sa->verb), len) != 0) continue; } else { /* SENT_MARKER ... due to recursion. Or another SENT_IS_INTERNAL */ continue; } /* * Now we have found a special sentence! */ if (last_action_verb) free_mstring(last_action_verb); last_action_verb = ref_mstring(sa->verb); #ifdef DEBUG if (d_flag > 1) debug_message("%s Local command %s on %s\n", time_stamp() , get_txt(sa->function) , get_txt(sa->ob->name)); #endif /* If the function is static and not defined by current object, * then the call will fail. * * If this command is called directly from player input (and not * from the command() efun), then we can allow static functions. * For this, we set current_object (which is NULL in this case) * to the object defining the function. * * current_object is reset just after the call to apply(). */ if (current_object == NULL) current_object = sa->ob; /* Remember the object, to update score. */ command_object = sa->ob; /* If the next sentence(s) are of type SENT_MARKER themselves, * skip them. */ for (insert = s, next = s->next ; next && next->type == SENT_MARKER ; insert = next, next = next->next ) NOOP; /* Place the marker_sent in the objects sentence list */ if (!next) { /* We are at the end of the sentence list: the marker * is stored in the global command_marker. * And since new commands are always added at the start, * the end will remain the end. */ marker_sent->sent.next = NULL; command_marker = marker_sent; } else { /* Place the marker, so we can continue the search, no matter what * the object does. But beware, if the command_giver is destructed, * the marker will be freed. * Take care not to alter marker addresses. */ insert->next = (sentence_t *)marker_sent; marker_sent->ob = (object_t *)rt_context; marker_sent->sent.next = next; marker_sent->sent.type = SENT_MARKER; } /* Clear the other struct elements - after all, this might be * a reused command sentence. */ marker_sent->sent.type = SENT_MARKER; marker_sent->verb = NULL; marker_sent->function = NULL; #ifdef USE_SHADOWING marker_sent->shadow_ob = NULL; #endif /* Push the argument and call the command function. */ if (s->type == SENT_OLD_NO_SPACE) { if (strlen(buff) > mstrsize(sa->verb)) { push_c_string(inter_sp, &buff[mstrsize(sa->verb)]); ret = sapply(sa->function, sa->ob, 1); } else { ret = sapply(sa->function, sa->ob, 0); } } else if (s->type == SENT_NO_SPACE) { if (strlen(buff) > mstrsize(sa->verb)) { /* We need to cut off the verb right where the * arguments start. On the other hand, we can't modify * the last_verb permanently, as this sentence might * fail and other sentences want the full one. */ char ch; size_t len = mstrsize(sa->verb); push_string(inter_sp, last_verb); ch = buff[len]; buff[len] = '\0'; last_verb = new_tabled(buff); buff[len] = ch; push_c_string(inter_sp, &buff[len]); ret = sapply(sa->function, sa->ob, 1); free_mstring(last_verb); last_verb = inter_sp->u.str; inter_sp--; } else { ret = sapply(sa->function, sa->ob, 0); } } else if (buff[length] == ' ') { push_c_string(inter_sp, &buff[length+1]); ret = sapply(sa->function, sa->ob, 1); } else { ret = sapply(sa->function, sa->ob, 0); } if (ret == 0) { errorf("function %s not found.\n", get_txt(sa->function)); } /* Restore the old current_object and command_giver */ current_object = save_current_object; command_giver = save_command_giver; /* If the command_giver was destructed, clean up and exit. * Note that s might be a dangling pointer right now. */ if (command_giver->flags & O_DESTRUCTED) { /* the caller (execute_command()) will do the clean up */ return MY_TRUE; } /* Remove the marker from the sentence chain, and make s->next valid */ if ( NULL != (s = marker_sent->sent.next) && s->type != SENT_MARKER) { /* The following sentence is a non-SENT_MARKER: the data from * that sentence is copied into the place of the SENT_MARKER; the * storage of the sentence will then be reused for the new * SENT_MARKER. */ *marker_sent = *((action_t *)s); s->next = (sentence_t *)marker_sent; marker_sent = (action_t *)s; } else { if (next) { /* !s : there have been trailing sentences before, but all * have been removed. * s->type == SENT_MARKER : There was a delimiter sentence * between the two markers, which has been removed. */ sentence_t **pp; for (pp = &marked_command_giver->sent ; (s = *pp) != (sentence_t *)marker_sent; ) pp = &s->next; *pp = s->next; } s = (sentence_t *)marker_sent; } /* If we get fail from the call, it was wrong second argument. */ if (ret->type == T_NUMBER && ret->u.number == 0) { continue; } /* Command was found */ if (O_IS_INTERACTIVE(command_giver) #ifdef USE_SET_IS_WIZARD && !(command_giver->flags & O_IS_WIZARD) #endif ) { command_object->user->score++; } break; } /* for() */ /* At this point, the marker_sent is not part of the sentence * list anymore. Make sure it will be freed. */ marker_sent->sent.type = SENT_MARKER; marker_sent->verb = NULL; marker_sent->function = NULL; #ifdef USE_SHADOWING marker_sent->shadow_ob = NULL; #endif command_marker = marker_sent; /* If the command was not found, notify the failure */ if (s == 0) { if (!from_efun) notify_no_command(buff, marked_command_giver); return MY_FALSE; } #else notify_no_command(buff, command_giver); #endif return MY_TRUE; } /* parse_command() */ /*-------------------------------------------------------------------------*/ Bool execute_command (char *str, object_t *ob) /* Parse and execute the command for object as command_giver. * may be an interactive player or a NPC. * * Return TRUE if the command was recognized, and FALSE if not. * Return the result from the player_parser(). * * The current command execution context (which is all 0 when called * for an interactive command) is saved on the runtime stack. * * This is the main entry point for driver based command parsing. * For interactive commands, this is called from the backend loop; for other * commands this is called from v_command(). * * Note that the buffer of may be modified and/or extended by this * call. */ { Bool res; #ifdef USE_ACTIONS struct command_context_s context; /* Save the current context */ save_command_context(&context); context.rt.last = rt_context; rt_context = (rt_context_t *)&context; /* Default settings */ command_giver = ob; marked_command_giver = ob; last_command = str; #endif /* Execute the command */ if (driver_hook[H_COMMAND].type == T_STRING) { svalue_t *svp; push_c_string(inter_sp, str); svp = sapply_ign_prot(driver_hook[H_COMMAND].u.str, ob, 1); if (!svp) { errorf("Can't find H_COMMAND lfun '%s' in object '%s'.\n" , get_txt(driver_hook[H_COMMAND].u.str), get_txt(ob->name) ); res = 0; } else res = (svp->type != T_NUMBER) || (svp->u.number != 0); } else if (driver_hook[H_COMMAND].type == T_CLOSURE) { lambda_t *l; l = driver_hook[H_COMMAND].u.lambda; if (driver_hook[H_COMMAND].x.closure_type == CLOSURE_LAMBDA) { free_object(l->ob, "execute_command"); l->ob = ref_object(ob, "execute_command"); } push_c_string(inter_sp, str); push_ref_object(inter_sp, ob, "execute_command"); call_lambda(&driver_hook[H_COMMAND], 2); res = (inter_sp->type != T_NUMBER) || (inter_sp->u.number != 0); free_svalue(inter_sp); inter_sp--; } else res = parse_command(str, MY_FALSE); #ifdef USE_ACTIONS /* Restore the previous context */ rt_context = context.rt.last; restore_command_context(&context); #endif return res; } /* execute_command() */ #ifdef USE_ACTIONS /*-------------------------------------------------------------------------*/ static Bool e_add_action (svalue_t *func, svalue_t *cmd, p_int flag) /* Implementation of the efun add_action(). * * This function returns TRUE if an error occured, or FALSE if the * action was successfull. */ { action_t *p; object_t *ob; #ifdef USE_SHADOWING object_t *shadow_ob; #endif string_t *str; /* Can't take actions from destructed objects */ if (current_object->flags & O_DESTRUCTED) return MY_TRUE; #ifdef USE_SHADOWING shadow_ob = NULL; #endif ob = current_object; #ifdef USE_SHADOWING /* Check if the call comes from a shadow of the current object */ if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing) { shadow_ob = ob; str = find_tabled(func->u.str); do { ob = O_GET_SHADOW(ob)->shadowing; if (find_function(str, ob->prog) >= 0) { if (!privilege_violation4( STR_SHADOW_ADD_ACTION, ob, str, 0, inter_sp) ) return MY_TRUE; } } while(O_GET_SHADOW(ob)->shadowing); } #endif /* We must have a valid commandgiver to succeed */ if (command_giver == 0 || (command_giver->flags & O_DESTRUCTED)) { return MY_TRUE; } #ifdef USE_INVENTORIES /* And the commandgiver must be in the vicinity */ if (ob != command_giver && ob->super != command_giver && (ob->super == NULL || ob->super != command_giver->super) /* above condition includes the check command_giver->super == NULL */ && ob != command_giver->super) errorf("add_action from object '%s' that was not present to '%s'.\n" , get_txt(ob->name), get_txt(command_giver->name)); #endif #ifdef DEBUG if (d_flag > 1) debug_message("%s --Add action %s\n", time_stamp(), get_txt(func->u.str)); #endif /* Sanity checks */ if (get_txt(func->u.str)[0] == ':') errorf("Illegal function name: %s\n", get_txt(func->u.str)); if (compat_mode) { char *s; s = get_txt(func->u.str); if (*s++=='e' && *s++=='x' && *s++=='i' && *s++=='t' && (!*s || mstrsize(func->u.str) == 4)) { errorf("Illegal to define a command to the exit() function.\n"); /* NOTREACHED */ return MY_TRUE; } } /* Allocate and initialise a new sentence */ p = new_action_sent(); /* Set ->function to the function name, made tabled */ p->function = make_tabled(func->u.str); func->type = T_NUMBER; p->ob = ob; #ifdef USE_SHADOWING p->shadow_ob = shadow_ob; #endif /* Set ->verb to the command verb, made tabled */ p->verb = make_tabled(cmd->u.str); cmd->type = T_NUMBER; p->sent.type = SENT_PLAIN; p->short_verb = 0; if (flag) { if (flag == AA_SHORT) { p->sent.type = SENT_SHORT_VERB; } else if (flag == AA_NOSPACE) { p->sent.type = SENT_OLD_NO_SPACE; } else if (flag == AA_IMM_ARGS) { p->sent.type = SENT_NO_SPACE; } else if (flag < AA_VERB) { if ((size_t)(-flag) >= mstrsize(p->verb)) { free_action_sent(p); errorf("Bad arg 3 to add_action(): value %ld larger than verb '%s'.\n" , (long)flag, get_txt(p->verb)); /* NOTREACHED */ return MY_TRUE; } else { p->sent.type = SENT_SHORT_VERB; p->short_verb = 0 - (unsigned short)flag; } } else { free_action_sent(p); errorf("Bad arg 3 to add_action(): value %ld too big.\n" , (long)flag); /* NOTREACHED */ return MY_TRUE; } } #ifdef USE_SHADOWING /* Now chain in the sentence */ if (command_giver->flags & O_SHADOW) { sentence_t *previous = command_giver->sent; p->sent.next = previous->next; previous->next = (sentence_t *)p; } else #endif { p->sent.next = command_giver->sent; #ifdef CHECK_OBJECT_REF update_object_sent(command_giver, (sentence_t *)p); #else command_giver->sent = (sentence_t *)p; #endif /* CHECK_OBJECT_REF */ } return MY_FALSE; } /* e_add_action() */ /*-------------------------------------------------------------------------*/ svalue_t * v_add_action (svalue_t *sp, int num_arg) /* EFUN add_action() * * void add_action(string fun, string cmd [, int flag]) * * Add an action (verb + function) to the commandgiver. * * Attempting to add an action from a shadow causes a privilege violation * ("shadow_add_action", shadow, func). * * TODO: In the long run, get rid of actions. */ { svalue_t *arg; svalue_t *verb; arg = sp - num_arg + 1; verb = &arg[1]; if (e_add_action(&arg[0], verb , num_arg > 2 ? arg[2].u.number : 0)) { /* silent error condition, deallocate strings by hand */ sp = pop_n_elems(num_arg, sp); } else { /* add_action has reused the strings or freed it */ sp -= num_arg; } return sp; } /* v_add_action() */ /*-------------------------------------------------------------------------*/ svalue_t * v_command (svalue_t *sp, int num_arg) /* EFUN command() * * int command(string str) * int command(string str, object ob) * * Execute str as a command given directly by the user. Any * effects of the command will apply to object (defaults to * the current object if not given). * * Return value is 0 for failure. Otherwise a numeric value is * returned which tells the evaluation cost. Bigger number means * higher cost. The evaluation cost is approximately the number * of LPC machine code instructions executed. * * If command() is called on another object, it is not possible * to call static functions in this way, to give some protection * against illegal forces. * * TODO: With add_action(), this should go in the long run. */ { int rc; svalue_t *arg; object_t *ob; char buff[COMMAND_FOR_OBJECT_BUFSIZE]; int save_eval_cost = eval_cost - 1000; interactive_t *ip; arg = sp - num_arg + 1; if (num_arg == 1) ob = current_object; else ob = arg[1].u.ob; rc = 0; if (!(current_object->flags & O_DESTRUCTED) && !(ob->flags & O_DESTRUCTED)) { size_t len; /* Make a copy of the given command as the parser might change it */ len = mstrsize(arg->u.str); if (len >= sizeof(buff) - 1) errorf("Command too long: '%.200s...'\n", get_txt(arg->u.str)); strncpy(buff, get_txt(arg[0].u.str), len); buff[len] = '\0'; if (O_SET_INTERACTIVE(ip, ob)) trace_level |= ip->trace_level; if (execute_command(buff, ob)) rc = eval_cost - save_eval_cost; } if (num_arg > 1) free_svalue(sp--); free_svalue(sp); put_number(sp, rc); return sp; } /* v_command() */ /*-------------------------------------------------------------------------*/ svalue_t * f_execute_command (svalue_t *sp) /* EFUN execute_command() * * int execute_command (string command, object origin, object player) * * Low-level access to the command parser: take the , parse * it into verb and argument and call the appropriate action added * to (read: is the object 'issuing' the command). For the * execution of the function(s), this_player() is set to . * * The efun raises a privilege violation ("execute_command", this_object(), * origin, command). * * Note that this function does not use the H_MODIFY_COMMAND * and H_NOTIFY_FAIL hooks; the notify_fail() efun can be used, * but must be evaluated by the caller. * * Return TRUE if the command was found, and FALSE if not. */ { svalue_t *argp; object_t *origin, *player; char buf[COMMAND_FOR_OBJECT_BUFSIZE]; size_t len; Bool res; /* Test and get the arguments from the stack */ argp = sp - 2; /* Make a copy of the given command as the parser might change it */ len = mstrsize(argp->u.str); if (len >= sizeof(buf) - 1) errorf("Command too long: '%.200s...'\n", get_txt(argp->u.str)); strncpy(buf, get_txt(argp->u.str), len); buf[len+1] = '\0'; origin = check_object(argp[1].u.ob); if (!origin) errorf("origin '%s' destructed.\n", get_txt(argp[1].u.ob->name)); if (!(O_ENABLE_COMMANDS & origin->flags)) errorf("origin '%s' not a living.\n", get_txt(origin->name)); player = check_object(argp[2].u.ob); if (!player) errorf("player '%s' destructed.\n", get_txt(argp[2].u.ob->name)); if (!(O_ENABLE_COMMANDS & player->flags)) errorf("player '%s' not a living.\n", get_txt(player->name)); res = MY_FALSE; /* default result */ /* Test if we are allowed to use this function */ if (privilege_violation4(STR_EXECUTE_COMMAND, origin, argp->u.str, 0, sp)) { marked_command_giver = origin; command_giver = player; res = parse_command(buf, MY_TRUE); } /* Clean up the stack and push the result. */ free_svalue(argp+2); free_svalue(argp+1); free_svalue(argp); put_number(argp, res ? 1 : 0); return argp; } /* f_execute_command() */ /*-------------------------------------------------------------------------*/ svalue_t * f_match_command(svalue_t * sp) /* EFUN match_command() * * mixed * execute_command (string command, object origin) * * Take the command , parse it, and return an array of all * matching actions added to (read: is the object * 'issuing' the command). * * Each entry in the result array is itself an array of: * * string [CMDM_VERB]: The matched verb. * string [CMDM_ARG]: The argument string remaining, or 0 if none. * object [CMDM_OBJECT]: The object defining the action. * string [CMDM_FUN]: The name of the function to call in CMDM_OBJECT, * which may be static. * * The efun is useful for both debugging, and for implementing your * own H_COMMAND handling. * * TODO: Export the basic data gathering into a separate function which * TODO:: can then be co-used by parse_command(), removing the need * TODO:: for a SENT_MARKER. It might also remove the need for copying * TODO:: the command buffer. */ { object_t *origin; string_t *cmd; /* The given command */ char *cmdbuf; /* The given command text buffer */ string_t *verb; /* The verb from the command, tabled */ char *p; /* handy string pointer */ sentence_t *s; /* handy sentence pointer */ size_t cmd_length; /* length of command w/o trailing spaces */ size_t verb_length; /* length of the verb */ vector_t *rc; /* Result array */ /* The found matching actions are kept in a list of these * structures. References to strings and objects are counted. */ struct cmd_s { struct cmd_s *next; string_t *verb; /* The verb */ string_t *arg; /* The arg string, or NULL */ string_t *fun; /* The function to call */ object_t *ob; /* The object to call */ sentence_t *s; /* The sentence */ }; struct cmd_s *first, *last; struct cmd_s *pcmd; int num_cmd; /* Number of matches found */ int i; first = last = NULL; /* Get the arguments */ origin = sp->u.ob; cmd = sp[-1].u.str; cmdbuf = get_txt(cmd); /* Strip trailing spaces. */ for (p = cmdbuf + mstrsize(cmd) - 1; p >= cmdbuf; p--) { if (*p != ' ') break; } if (p < cmdbuf) { free_svalue(sp); sp--; free_svalue(sp); put_array(sp, allocate_array(0)); return sp; } cmd_length = p - cmdbuf + 1; /* Determine verb length */ p = strchr(cmdbuf, ' '); if (p == NULL) verb_length = cmd_length; else verb_length = (size_t)(p - cmdbuf); verb = new_n_tabled(cmdbuf, verb_length); /* Scan the list of sentences for the saved command giver * and collect the found matches in a list. */ num_cmd = 0; for (s = origin->sent; s; s = s->next) { struct cmd_s * new_cmd; action_t *sa; /* (action_t *)s */ unsigned char type; /* s->type */ sa = (action_t *)s; /* Test the current sentence */ if ((type = s->type) == SENT_PLAIN) { if (!mstreq(sa->verb, verb)) continue; } else if (type == SENT_SHORT_VERB) { /* The verb may be shortened to a few leading characters, * but not shorter than .short_verb. */ size_t len; if (sa->short_verb) { len = mstrsize(verb); if (len < sa->short_verb || len > mstrsize(sa->verb) || ( !mstreq(sa->verb, verb) && strncmp(get_txt(sa->verb), get_txt(verb), len) != 0)) continue; } else { len = mstrsize(sa->verb); if (strncmp(cmdbuf, get_txt(sa->verb), len) != 0) continue; } } else if (type == SENT_OLD_NO_SPACE || type == SENT_NO_SPACE) { /* The arguments may follow the verb without space, * that means we just have to check if buff[] begins * with sa->verb. */ size_t len; len = mstrsize(sa->verb); if (strncmp(cmdbuf, get_txt(sa->verb), len) != 0) continue; } else { /* SENT_MARKER ... due to recursion. Or another SENT_IS_INTERNAL */ continue; } /* * Now we have found a matching sentence! */ num_cmd++; memsafe(new_cmd = alloca(sizeof(*new_cmd)), sizeof(*new_cmd) , "temporary buffer"); new_cmd->next = NULL; new_cmd->s = s; new_cmd->ob = ref_object(sa->ob, "match_command"); new_cmd->fun = ref_mstring(sa->function); /* Fill in the verb and arg information of the cmd_s structure. */ if (s->type == SENT_OLD_NO_SPACE) { new_cmd->verb = ref_mstring(verb); if (cmd_length > mstrsize(sa->verb)) { new_cmd->arg = mstr_extract(cmd, mstrsize(sa->verb), cmd_length-1); } else { new_cmd->arg = NULL; } } else if (s->type == SENT_NO_SPACE) { if (cmd_length > mstrsize(sa->verb)) { /* We need to cut off the verb right where the * arguments start. */ size_t len = mstrsize(sa->verb); new_cmd->verb = mstr_extract(cmd, 0, len-1); new_cmd->arg = mstr_extract(cmd, len, cmd_length-1); } else { new_cmd->verb = ref_mstring(verb); new_cmd->arg = NULL; } } else if (cmdbuf[verb_length] == ' ') { new_cmd->verb = ref_mstring(verb); /* Try to find an earlier action which uses the same * argument and just reference that one. */ for (pcmd = first; pcmd != NULL; pcmd = pcmd->next) { if (pcmd->s->type != SENT_OLD_NO_SPACE && pcmd->s->type != SENT_NO_SPACE && pcmd->arg != NULL ) { new_cmd->arg = ref_mstring(pcmd->arg); break; } } if (pcmd == NULL) { /* First time this arg is used */ new_cmd->arg = mstr_extract(cmd, verb_length+1, cmd_length-1); } } else { new_cmd->verb = ref_mstring(verb); new_cmd->arg = NULL; } /* Insert the command info into the list */ if (first == NULL) { first = last = new_cmd; } else { last->next = new_cmd; last = new_cmd; } } /* for(sentences) */ /* We got the matched commands, now transfer the information * into the result array. */ rc = allocate_array(num_cmd); for ( i = 0, pcmd = first ; i < num_cmd && pcmd != NULL ; i++, pcmd = pcmd->next) { vector_t *sub = allocate_array(CMDM_SIZE); put_string(&(sub->item[CMDM_VERB]), pcmd->verb); if (pcmd->arg) put_string(&(sub->item[CMDM_ARG]), pcmd->arg); /* else: entry is svalue-0 already */ put_string(&(sub->item[CMDM_FUN]), pcmd->fun); put_object(&(sub->item[CMDM_OBJECT]), pcmd->ob); put_array(&(rc->item[i]), sub); } /* Clean up */ free_mstring(verb); /* No need to clean up the references from the list as they * have been transferred into the result array. * And the list itself lives on the stack and is cleaned up * automatically. */ /* Put the result onto the stack */ free_svalue(sp); sp--; free_svalue(sp); put_array(sp, rc); return sp; } /* f_match_command() */ /*-------------------------------------------------------------------------*/ svalue_t * f_remove_action (svalue_t *sp) /* EFUN: remove_action() * * int remove_action(string verb, object ob) * * Remove the first action defined by the current object with command verb * from (default is this_player()). * Return 1 if the action was found and removed, and 0 else. * * int remove_action(int flag, object ob) * * if is non-0, remove all actions defined by the current object from * (default is this_player()). * Return the number of actions removed. */ { object_t *ob; string_t *verb; sentence_t **sentp; action_t *s; int rc; /* Get and test the arguments */ ob = sp->u.ob; verb = NULL; if (sp[-1].type == T_STRING) { verb = find_tabled(sp[-1].u.str); if (!verb) verb = (string_t *)f_remove_action; /* won't be found */ } else if (sp[-1].type == T_NUMBER) { if (sp[-1].u.number != 0) verb = NULL; /* finds all */ else verb = (string_t *)f_remove_action; /* won't be found */ } else { efun_gen_arg_error(1, sp[-1].type, sp); /* NOTREACHED */ } /* Now search and remove the sentence */ rc = 0; sentp = &ob->sent; ob = current_object; while ( NULL != (s = (action_t *)*sentp) ) { if (!SENT_IS_INTERNAL((*sentp)->type) && s->ob == ob && (!verb || s->verb == verb)) { #ifdef CHECK_OBJECT_REF if (sentp == &ob->sent) update_object_sent(ob, s->sent.next); else *sentp = s->sent.next; #else *sentp = s->sent.next; #endif /* CHECK_OBJECT_REF */ free_action_sent(s); rc++; if (verb != NULL) break; } else { sentp = &s->sent.next; } } /* Clean up the stack and push the result */ free_object_svalue(sp); sp--; free_svalue(sp); put_number(sp, rc); return sp; } /* f_remove_action() */ /*-------------------------------------------------------------------------*/ static vector_t * e_get_action (object_t *ob, string_t *verb) /* EFUN query_actions() * * mixed *query_actions (object ob, string verb) * * Return information about the attached to ject, or 0 if * there is no such verb. * * See f_query_actions() for a long explanation. */ { vector_t *v; sentence_t *s; svalue_t *p; if ( !(verb = find_tabled(verb)) ) return NULL; for (s = ob->sent; s; s = s->next) { action_t *sa; if (SENT_IS_INTERNAL(s->type)) continue; sa = (action_t *)s; if (verb != sa->verb) continue; /* verb will be 0 for SENT_MARKER */ v = allocate_array(4); p = v->item; put_number(p, s->type); p++; put_number(p, s->type != SENT_PLAIN ? sa->short_verb : 0); p++; put_ref_object(p, sa->ob, "get_action"); p++; put_ref_string(p, sa->function); return v; } /* not found */ return NULL; } /* e_get_action() */ /*-------------------------------------------------------------------------*/ static vector_t * e_get_all_actions (object_t *ob, int mask) /* EFUN query_actions() * * mixed *query_actions (object ob, int mask) * * Return information about all verbs attached to ject which match * the . * * See f_query_actions() for a long explanation. */ { vector_t *v; sentence_t *s; int num; svalue_t *p; int nqueries; /* Set nqueries to the number of set bit in mask */ nqueries = ((mask>>1) & 0x55) + (mask & 0x55); nqueries = ((nqueries>>2) & 0x33) + (nqueries & 0x33); nqueries = ((nqueries>>4) & 0x0f) + (nqueries & 0x0f); num = 0; /* Count the number of actions */ for (s = ob->sent; s; s = s->next) { if (SENT_IS_INTERNAL(s->type)) continue; num += nqueries; } /* Allocate and fill the result array */ v = allocate_array(num); p = v->item; for (s = ob->sent; s; s = s->next) { action_t * sa; if (SENT_IS_INTERNAL(s->type)) continue; sa = (action_t *)s; if (mask & QA_VERB) { string_t * str; if ( NULL != (str = sa->verb) ) { put_ref_string(p, str); } p++; } if (mask & QA_TYPE) { p->u.number = s->type; p++; } if (mask & QA_SHORT_VERB) { p->u.number = sa->short_verb; p++; } if (mask & QA_OBJECT) { put_ref_object(p, sa->ob, "get_action"); p++; } if (mask & QA_FUNCTION) { put_ref_string(p, sa->function); p++; } } /* Done */ return v; } /* e_get_all_actions() */ /*-------------------------------------------------------------------------*/ static vector_t * e_get_object_actions (object_t *ob1, object_t *ob2) /* EFUN query_actions() * * mixed *query_actions (object ob, object from) * * Return information about all verbs attached to ject which are * defined by object . * * See f_query_actions() for a long explanation. */ { vector_t *v; sentence_t *s; int num; svalue_t *p; /* Count the number of actions */ num = 0; for (s = ob1->sent; s; s = s->next) if (!SENT_IS_INTERNAL(s->type) && ((action_t *)s)->ob == ob2) num += 2; /* Allocate and fill in the result array */ v = allocate_array(num); p = v->item; for (s = ob1->sent; s; s = s->next) { action_t *sa; if (SENT_IS_INTERNAL(s->type)) continue; sa = (action_t *)s; if (sa->ob == ob2) { put_ref_string(p, sa->verb); p++; put_ref_string(p, sa->function); p++; } } /* Return the result */ return v; } /* e_get_object_actions() */ /*-------------------------------------------------------------------------*/ svalue_t * f_query_actions (svalue_t *sp) /* EFUN query_actions() * * mixed *query_actions(object ob, mixed mask_or_verb) * * query_actions takes either an object or a filename as first * argument and a bitmask (int) or string as a second argument. * If the second argument is a string, query_actions() will return * an array containing information (see below) on the verb or * zero if the living object "ob" cannot use the verb. If the * second argument is a bitmask, query_actions() will return a * flat array containing information on all verbs added to ob. * The second argument is optional (default is QA_VERB). * QA_VERB ( 1): the verb * QA_TYPE ( 2): type * QA_SHORT_VERB ( 4): short_verb * QA_OBJECT ( 8): object * QA_FUNCTION (16): function * * "type" is one of the values defined in (/sys/sent.h) * (which is provided with the parser source). * * SENT_PLAIN added with add_action (fun, cmd); * SENT_SHORT_VERB added with add_action (fun, cmd, 1); * SENT_NO_SPACE added with add_action (fun); add_xverb (cmd); * SENT_MARKER internal, won't be in the returned array */ { vector_t *v; svalue_t *arg; object_t *ob; arg = sp - 1; /* Get the arguments */ if (arg[0].type == T_OBJECT) ob = arg[0].u.ob; else { if (arg->type != T_STRING) efun_arg_error(1, T_STRING, arg->type, sp); ob = get_object(arg[0].u.str); if (!ob) errorf("query_actions() failed\n"); } /* Get the actions */ if (arg[1].type == T_STRING) v = e_get_action(ob, arg[1].u.str); else if (arg[1].type == T_NUMBER) v = e_get_all_actions(ob, arg[1].u.number); else { if (arg[1].type != T_OBJECT) efun_arg_error(2, T_OBJECT, arg[1].type, sp); v = e_get_object_actions(ob, arg[1].u.ob); } /* Clean up the stack and push the result */ free_svalue(sp--); free_svalue(sp); if (v) { put_array(sp, v); } else put_number(sp, 0); return sp; } /* f_query_actions() */ /*-------------------------------------------------------------------------*/ svalue_t * f_disable_commands (svalue_t *sp) /* EFUN disable_commands() * * void disable_commands() * * Disable this object to use commands normally accessible to * users. */ { if (current_object->flags & O_DESTRUCTED) return sp; if (d_flag > 1) { debug_message("%s Disable commands %s (ref %ld)\n" , time_stamp(), get_txt(current_object->name) , current_object->ref); } current_object->flags &= ~O_ENABLE_COMMANDS; command_giver = NULL; return sp; } /* f_disable_commands() */ /*-------------------------------------------------------------------------*/ svalue_t * f_enable_commands (svalue_t *sp) /* EFUN enable_commands() * * void enable_commands() * * Enable this object to use commands normally accessible to * users. */ { interactive_t *ip; if (current_object->flags & O_DESTRUCTED) return sp; if (d_flag > 1) { debug_message("%s Enable commands %s (ref %ld)\n" , time_stamp(), get_txt(current_object->name) , current_object->ref); } current_object->flags |= O_ENABLE_COMMANDS; command_giver = current_object; if (O_SET_INTERACTIVE(ip, command_giver)) { trace_level |= ip->trace_level; } return sp; } /* f_enable_commands() */ /*-------------------------------------------------------------------------*/ svalue_t * f_notify_fail (svalue_t *sp) /* EFUN notify_fail() * * int notify_fail(string str) * int notify_fail(closure cl) * * Store str as the error message given instead of the default * message ``What ?''. The result is always 0. * * If a closure is given, it is executed to return the error * message string, but not before all attempts to execute the * commandline failed (read: not at the time of the call to * notify_fail()). * * If notify_fail() is called more than once, only the last call * will be used. */ { if (command_giver && !(command_giver->flags & O_DESTRUCTED)) { transfer_svalue(&error_msg, sp); if (error_obj) free_object(error_obj, "notify_fail"); error_obj = ref_object(current_object, "notify_fail"); } else free_svalue(sp); put_number(sp, 0); return sp; } /* f_notify_fail() */ /*-------------------------------------------------------------------------*/ svalue_t * f_query_verb (svalue_t *sp) /* EFUN query_verb() * * string query_verb(void) * string query_verb(int flag) * * Return the verb of the current command, of 0 if not executing from * a command. If is 0 or not given, the verb as given by the user * is returned; if is non-0, the verb as specified in the * add_action() statement is returned. * * This efun allows add_action() of several commands * to the same function. query_verb() returns 0 when invoked by a * function which was started by a call_out or the heart beat. * Also when a user logs in query_verb() returns 0. */ { p_int flag = sp->u.number; free_svalue(sp); if (flag == 0) { if (!last_verb) put_number(sp, 0); else put_ref_string(sp, last_verb); } else { if (!last_action_verb) put_number(sp, 0); else put_ref_string(sp, last_action_verb); } return sp; } /* f_query_verb() */ /*-------------------------------------------------------------------------*/ svalue_t * f_query_command (svalue_t *sp) /* EFUN query_command() * * string query_command(void) * * Return the full command string, or 0 if not executing from * a command. * * The string returned is the string as seen by the parser: * after any modify_command handling and after stripping * trailing spaces. */ { if (!last_command) push_number(sp, 0); else push_c_string(sp, last_command); return sp; } /* f_query_command() */ /*-------------------------------------------------------------------------*/ svalue_t * f_query_notify_fail (svalue_t *sp) /* EFUN query_notify_fail() * * mixed query_notify_fail() * mixed query_notify_fail(int flag = 0) * * If no flag is given, or flag is 0: return the last error message * resp. closure set with notify_fail(). * If flag is non-zero, return the object which executed the last notify_fail(). * * If nothing was set yet, return 0. */ { p_int flag; flag = sp->u.number; free_svalue(sp); if (flag) { if (error_obj && !(error_obj->flags & O_DESTRUCTED)) put_ref_object(sp, error_obj, "query_notify_fail"); else put_number(sp, 0); } else { if (error_msg.type == T_STRING || error_msg.type == T_CLOSURE) assign_svalue_no_free(sp, &error_msg); else put_number(sp, 0); } return sp; } /* f_query_notify_fail() */ /*-------------------------------------------------------------------------*/ svalue_t * f_set_modify_command (svalue_t *sp) /* EFUN set_modify_command() * * object set_modify_command(object) * object set_modify_command(string) * object set_modify_command(int) * * All commands for the current object (that must obviously be interactive) * will be passed to ob->modify_command() before actually being executed. The * argument can be passed an object or a file_name. * * When set_modify_command() was called, the parser won't expand the standard * abbreviations n,e,s,w,nw,sw,ne,se for that user anymore, nor use any hook * set for this. * * 0 as argument will stop the command modification and reinstall * the standard abbreviations. * -1 as argument will just return the object previously set. * * The return value is the object that was previously set with * set_modify_command(), if any. */ { object_t *old, *new; interactive_t *ip; inter_sp = sp; /* Make sure the current_object is interactive */ if (!(O_SET_INTERACTIVE(ip, current_object)) || ip->closing) { errorf("set_modify_command in non-interactive object\n"); } /* Get the old setting */ old = ip->modify_command; if (old && old->flags & O_DESTRUCTED) { free_object(old, "set_modify_command"); old = NULL; ip->modify_command = NULL; } /* Set the new setting */ new = sp->u.ob; switch(sp->type) { default: efun_gen_arg_error(1, sp->type, sp); break; case T_STRING: new = get_object(sp->u.str); if (!new) errorf("Object '%s' not found.\n", get_txt(sp->u.str)); /* FALLTHROUGH */ case T_OBJECT: ip->modify_command = ref_object(new, "set_modify_command"); break; case T_NUMBER: if (sp->u.number == 0 ) { /* ref count of old is reused below, so don't free now */ ip->modify_command = NULL; } else { if (sp->u.number != -1) errorf("Bad num arg 1 to set_modify_command(): got %ld, " "expected 0 or -1\n", sp->u.number); if (old) ref_object(old, "set_modify_command"); } } free_svalue(sp); /* Return the old setting */ if (old) put_object(sp, old); /* reuse ref count */ else put_number(sp, 0); return sp; } /* f_set_modify_command() */ /*-------------------------------------------------------------------------*/ svalue_t * f_set_this_player (svalue_t *sp) /* EFUN set_this_player() * * void set_this_player(object ob) * * Change the current command giver to . may be 0 if you want to * 'deactivate' the current command giver. * * This efun is not privileged, therefore it should be redefined by a nomask * simul_efun which then either completely disables the efun or at least * performs some security checks. It is easy to undermine a mudlibs security * using this efun. */ { object_t *ob; interactive_t *ip; /* Special case, can happen if a function tries to restore * an old this_player() setting which happens to be 0. */ if (sp->type == T_NUMBER && !sp->u.number) { command_giver = NULL; return sp - 1; } if (sp->type != T_OBJECT) efun_arg_error(1, T_OBJECT, sp->type, sp); ob = sp->u.ob; command_giver = ob; if (O_SET_INTERACTIVE(ip, ob)) { trace_level |= ip->trace_level; } free_object(ob, "set_this_player"); return sp - 1; } /* f_set_this_player() */ /*-------------------------------------------------------------------------*/ svalue_t * f_command_stack_depth (svalue_t *sp) /* EFUN command_stack_depth() * * int command_stack_depth(void) * * Return the depth of the command stack, that is the number of nested * commands. */ { rt_context_t * context; int num; /* Simply count the number of COMMAND_CONTEXT entries on * the context stack. */ for (num = 0, context = rt_context; context; context = context->last) if (context->type == COMMAND_CONTEXT) num++; push_number(sp, num); return sp; } /* f_command_stack_depth() */ /*-------------------------------------------------------------------------*/ svalue_t * f_command_stack (svalue_t *sp) /* EFUN command_stack() * * mixed * command_stack(void) * * Return an array describing the current command stack. The array has * command_stack_depth() entries, the first describing the top-level * command, and the last describing the current one. * * Each entry is an array itself with these entries: * * string [CMD_VERB]: the verb of this command * string [CMD_TEXT]: the full command text * object [CMD_ORIGIN]: the original command giver * object [CMD_PLAYER]: the current command giver * mixed [CMD_FAIL]: the notify_fail setting * mixed [CMD_FAILOBJ]: the object which set the notify_fail */ { rt_context_t * context; vector_t * result; svalue_t * entry; int num, i; /* Count the number of COMMAND_CONTEXT entries on * the context stack. */ for (num = 0, context = rt_context; context; context = context->last) if (context->type == COMMAND_CONTEXT) num++; /* Get the array */ result = allocate_uninit_array(num); if (!result) errorf("(command_stack) Out of memory: array[%d] for result.\n", num); for ( i = num-1, entry = result->item + num - 1, context = rt_context ; i >= 0 ; i--, entry-- ) { vector_t * sub; svalue_t * svp; string_t * t_verb; /* current verb */ char * t_cmd; /* current command */ object_t * t_player, * t_mplayer; /* current command givers */ svalue_t * t_errmsg; /* current error message */ object_t * t_errobj; /* current error message giver */ /* Create the entry array */ sub = allocate_array(CMD_SIZE); if (!sub) errorf("(command_stack) Out of memory: array[%d] for entry.\n" , CMD_SIZE); put_array(entry, sub); svp = sub->item; if (i == num-1) { /* Get the active environment */ t_verb = last_verb; t_cmd = last_command; t_player = check_object(command_giver); t_mplayer = check_object(marked_command_giver); t_errmsg = &error_msg; t_errobj = check_object(error_obj); } else { struct command_context_s *cmd; /* Find the next command context */ while (context->type != COMMAND_CONTEXT) context = context->last; cmd = (struct command_context_s *)context; context = context->last; t_verb = cmd->verb; t_cmd = cmd->cmd; t_player = check_object(cmd->this_player); t_mplayer = check_object(cmd->mark_player); t_errmsg = &(cmd->errmsg); t_errobj = check_object(cmd->errobj); } /* Now put the data into the array */ if (t_verb) put_ref_string(svp+CMD_VERB, t_verb); if (t_cmd) put_c_string(svp+CMD_TEXT, t_cmd); if (t_mplayer) put_ref_object(svp+CMD_ORIGIN, t_mplayer, "command_stack"); if (t_player) put_ref_object(svp+CMD_PLAYER, t_player, "command_stack"); if (t_errmsg->type == T_STRING || t_errmsg->type == T_CLOSURE) assign_svalue_no_free(svp+CMD_FAIL, t_errmsg); if (t_errobj) put_ref_object(svp+CMD_FAILOBJ, t_errobj, "command_stack"); } /* Put the result onto the stack */ push_array(sp, result); return sp; } /* f_command_stack() */ /*-------------------------------------------------------------------------*/ svalue_t * f_living (svalue_t *sp) /* EFUN living() * * int living(object ob) * * Return true if ob is a living object (that is, * enable_commands() has been called from inside the ob). * ob may be 0, in which case the result is obviously 0, too. */ { int i; if (sp->type == T_NUMBER && !sp->u.number) return sp; if (sp->type != T_OBJECT) { efun_arg_error(1, T_OBJECT, sp->type, sp); return sp; } i = (sp->u.ob->flags & O_ENABLE_COMMANDS) != 0; free_object_svalue(sp); put_number(sp, i); return sp; } /* f_living() */ /***************************************************************************/ #endif /* USE_ACTIONS */