//***************************************************************************** // // action.h // // this is the interface for the action handler. Actions are temporally // delayed events (e.g. preparing a spell, swinging a sword, etc). Players // may only be taking 1 action at a time, and any time a new action for a // character is added, the previous one is terminated. // // Dec 15/04 // * expanded actions to support actions for different places (e.g mental, // feet, left/right hands). // // Nov 28/04 // * There are a couple places where actions can be improved on. First, it // would probably be good to store actions as a binary map instead of as // a hash map. Iterating over the hash map is probably going to be // inefficient in the long run. The second place it can use some improvement // is in the action loop; it would be nice if we had a way to prevent // players from re-entering the loop as it is going around (perhaps a poll // that is only active when the action loop is active), and also if we could // ensure that actions are run in order of their remaining delay; currently, // if the action loop is run with a pulse of 2, and there are two people // taking actions, one with a delay of 2 left and one with a delay of 1 left, // there is a chance that the one with a delay of 2 left will perform an // action first. Not that serious, but definitely more of a bug than a // feature. // //***************************************************************************** #include "mud.h" #include "utils.h" #include "character.h" #include "action.h" #include "hooks.h" #ifdef MODULE_FACULTY #include "faculty/faculty.h" #endif typedef struct action_data ACTION_DATA; MAP *actors = NULL; struct action_data { void (* on_complete)(void *ch, void *data, bitvector_t where, char *arg); void (* on_interrupt)(void *ch, void *data, bitvector_t where, char *arg); bitvector_t where; // which bodyparts are participating in the action? int delay; // how much longer until the action is complete? void *data; // data for the action (e.g. spell data, char mining state) char *arg; // an argument supplied to an action (e.g. the target of a kick) }; //***************************************************************************** // A small test for delayed actions ... proof of concept //***************************************************************************** void do_dsay(CHAR_DATA *ch, void *data, bitvector_t where, char *arg) { communicate(ch, arg, COMM_LOCAL); } void dsay_interrupt(CHAR_DATA *ch, void *data, bitvector_t where, char *arg) { send_to_char(ch, "Your delayed say was interrupted.\r\n"); } COMMAND(cmd_dsay) { if(!*arg) send_to_char(ch, "What did you want to delay-say?\r\n"); else { send_to_char(ch, "You start a delayed say.\r\n"); start_action(ch, 3 SECOND, 1, do_dsay, dsay_interrupt, NULL, arg); } } //***************************************************************************** // single action handling //***************************************************************************** ACTION_DATA *newAction(int delay, bitvector_t where, void *on_complete, void *on_interrupt, void *data, const char *arg) { struct action_data *action = malloc(sizeof(ACTION_DATA)); action->on_complete = on_complete; action->on_interrupt = on_interrupt; action->delay = delay; action->data = data; action->arg = strdupsafe(arg); action->where = where; return action; } void deleteAction(ACTION_DATA *action) { if(action->arg) free(action->arg); free(action); } void run_action(void *ch, ACTION_DATA *action) { if(action->on_complete) action->on_complete(ch, action->data, action->where, action->arg); } // used to kill all of a character's actions on death void stop_all_actions(CHAR_DATA *ch) { #ifdef MODULE_FACULTY interrupt_action(ch, FACULTY_ALL); #else interrupt_action(ch, 1); #endif } // allows stop_all_actions to run as a hook void stop_actions_hook(const char *info) { CHAR_DATA *ch = NULL; hookParseInfo(info, &ch); stop_all_actions(ch); } //***************************************************************************** // actor list handling //***************************************************************************** void init_actions() { // use the standard pointer hasher and comparator actors = newMap(NULL, NULL); // add in our example delayed action add_cmd("dsay", NULL, cmd_dsay, "admin", FALSE); // make sure the character does not continue actions after being extracted hookAdd("char_from_game", stop_actions_hook); } bool is_acting(void *ch, bitvector_t where) { LIST *actions = mapGet(actors, ch); if(actions == NULL || listSize(actions) == 0) return FALSE; bool action_found = FALSE; LIST_ITERATOR *act_i = newListIterator(actions); ACTION_DATA *action = NULL; // iterate across all of our current actions and see if any // involve the faculties of "where" ITERATE_LIST(action, act_i) { if(IS_SET(action->where, where)) { action_found = TRUE; break; } } deleteListIterator(act_i); return action_found; } void interrupt_action(void *ch, bitvector_t where) { // get the list of all the actions we're performing LIST *actions = mapGet(actors, ch); if(actions == NULL) return; // if we have actions, go through and stop 'em all if(listSize(actions) > 0) { LIST_ITERATOR *act_i = newListIterator(actions); ACTION_DATA *action = NULL; ITERATE_LIST(action, act_i) { // check to see if we've found an action that needs interruption if(IS_SET(action->where, where)) { if(action->on_interrupt) action->on_interrupt(ch, action->data, action->where, action->arg); listRemove(actions, action); deleteAction(action); } } deleteListIterator(act_i); } // if all of the actions are gone, delete the list if(listSize(actions) == 0) { mapRemove(actors, ch); deleteList(actions); } } void start_action(void *ch, int delay, bitvector_t where, void *on_complete, void *on_interrupt, void *data, const char *arg) { interrupt_action(ch, where); ACTION_DATA *newact = newAction(delay, where, on_complete, on_interrupt, data, arg); // get the current list LIST *curr_acts = mapGet(actors, ch); // if it's null, make a new one and add it to the map of actions if(curr_acts == NULL) { curr_acts = newList(); mapPut(actors, ch, curr_acts); } listPut(curr_acts, newact); } void pulse_actions(int time) { MAP_ITERATOR *ch_i = newMapIterator(actors); ACTION_DATA *action = NULL; LIST_ITERATOR *act_i = NULL; LIST *actions = NULL; const CHAR_DATA *map_actor = NULL; CHAR_DATA *ch = NULL; ITERATE_MAP(map_actor, actions, ch_i) { actions = mapIteratorCurrentVal(ch_i); // if we have actions, then go through 'em all if(listSize(actions) > 0) { // eek... small problem. The key for maps is constant, but we need // a non-constant character to send to run_action. Let's get the char's // UID, and re-look him up in the player table ch = propertyTableGet(mob_table, charGetUID(map_actor)); act_i = newListIterator(actions); ITERATE_LIST(action, act_i) { // decrement the delay action->delay -= time; // pop the action from the list, and run it if(action->delay <= 0) { listRemove(actions, action); run_action(ch, action); deleteAction(action); } } deleteListIterator(act_i); } } deleteMapIterator(ch_i); }