/** * This is the goal handler for the npcs. It will handle all the goals * and ordering them, figuring out which goal to run. * @author Pinkfish * @started Thu Jul 30 17:06:19 PDT 1998 */ #include <npc/goals.h> // Local inherit stuff. #include "goal_inherit.h" class goal_information { mixed data; int priority; string *events; } class plan_info { int priority; mixed data; } // The index of the mapping is the goal. private nosave mapping _goals; private nosave mapping _events; private nosave mapping _emotions; // The format of this array is: // ({ priority, plan, priority, plan, ... }) private nosave mixed* _plans; private nosave string _current_plan; private class goal_information query_goal_information(string goal); void remove_goal_event(string goal, string event); void add_goal_event(string goal, string event); int query_goal_priority(string goal); int check_current_plan_finished(); void create() { _goals = ([ ]); _events = ([ ]); _plans = ({ }); } /* create() */ /** * This method is *only* to be used for debugging. */ mapping query_goals() { return _goals; } /* query_goals() */ /** * This method is *only* to be used for debugging. */ mapping query_events() { return _events; } /* query_events() */ /** * This method will add a goal to the NPC. * @param goal the goal to add * @param data the data associated with the goal */ void add_goal(string goal, mixed data) { class goal_information frog; if (!goal->invarient(this_object())) { // Cannot add the goal. return ; } if (!_goals[goal]) { frog = new(class goal_information); frog->data = data; frog->priority = goal->query_priority(this_object(), data); frog->events = ({ }); _goals[goal] = frog; goal->initialise(this_object(), goal, data); } else { goal->add_again(this_object(), query_goal_information(goal)->data, data); } } /* add_goal() */ /** * This method removes the goal from the current goal list. * @param goal the goal to remove */ void remove_goal(string goal) { class goal_information frog; string event; frog = query_goal_information(goal); if (frog) { goal->finalise(this_object(), frog->data); foreach (event in frog->events) { remove_goal_event(goal, event); } map_delete(_goals, goal); } } /* remove_goal() */ /** * This method gets the information associated with the goal. * @param goal the goal to get the information for * @return the goal information */ private class goal_information query_goal_information(string goal) { class goal_information frog; frog = _goals[goal]; return frog; } /* query_goal_information() */ /** * This method returns the data associated with this goal. * @param goal the goal to get the data for * @return the data associated with the goal * @see query_goal_priority() * @see set_goal_data() */ mixed query_goal_data(string goal) { class goal_information frog; frog = query_goal_information(goal); if (frog) { return frog->data; } return 0; } /* query_goal_data() */ /** * This method returns the data associated with this goal. * @param goal the goal to get the data for * @param data the new data for the goal * @return 1 on success, 0 on failure * @see query_goal_priority() * @see query_goal_data() */ int set_goal_data(string goal, mixed data) { class goal_information frog; frog = query_goal_information(goal); if (frog) { frog->data = data; return 1; } return 0; } /* set_goal_data() */ /** * This method returns the priotity of the goal. * @param goal the goal to query the priority of * @return the priority of the goal * @see change_goal_priority() */ int query_goal_priority(string goal) { class goal_information frog; frog = query_goal_information(goal); if (frog) { return frog->priority; } return GOAL_INVALID_PRIORITY; } /* query_priority() */ /** * This method changes the priority of the specified goal. It will recall * the query_priority() method on the goal to figure out the new priority. * @param goal the goal whose priority to change * @see add_goal_event() * @see remove_goal_event() * @see query_goal_priority() */ void change_goal_priority(string goal) { class goal_information frog; int new_priority; string evt; frog = query_goal_information(goal); if (frog) { new_priority = goal->query_priority(this_object(), frog->data); if (new_priority != frog->priority) { frog->priority = new_priority; } foreach (evt in frog->events) { /* Put itself back into the event list. */ remove_goal_event(goal, evt); add_goal_event(goal, evt); } } } /* change_goal_priority() */ /** * This method adds an event for a goal to react to. This should only be * done from inside the goal initialisation code, and other parts of the * goal. We will only add things for goals which we know about. * @param goal the goal the event is for * @param event the event name to react to * @see change_priority() * @see remove_goal_event() */ void add_goal_event(string goal, string event) { class goal_information frog; string test_goal; int i; int priority; int found; frog = query_goal_information(goal); if (frog) { if (!_events[event]) { _events[event] = ({ goal }); } else if (member_array(goal, _events[event]) == -1) { // Place the goal into the list in priority order. priority = query_goal_priority(goal); while (i < sizeof(_events[event])) { test_goal = _events[event][i]; if (query_goal_priority(_events[event][i]) < priority) { _events[event] = _events[event][0..i-1] + ({ goal }) + _events[event][i..]; found = 1; break; } i++; } if (!found) { _events[event] += ({ goal }); } } frog->events += ({ event }); } } /* add_goal_event() */ /** * This method removes the goal event for the goal. * @param goal the goal to remove the event for * @param event the event name to not react to * @see add_goal_event() * @see change_priority() */ void remove_goal_event(string goal, string event) { class goal_information frog; frog = query_goal_information(goal); if (frog && _events[event]) { _events[event] -= ({ goal }); if (!sizeof(_events[event])) { map_delete(_events, event); } frog->events -= ({ event }); } } /* remove_goal_event() */ /** * This method is called when an event needs to be notified about. * The event handling routines should try to be short. They should * just modify the priority of something, or add a new goal to the * current list. * @param event the event to tell us about * @param information the information associated with the event */ varargs void notify_npc_event(string event, mixed *information ...) { string goal; tell_creator("pinkfish", "%s %O\n", event, _events[event]); if (_events[event]) { foreach (goal in _events[event]) { // If the event is handled then we break out. if (goal->notify_of_event(this_object(), query_goal_information(goal)->data, event, information)) { return ; } } } if (sizeof(_plans)) { if (_plans[PLAN_PLAN]->notify_of_event(this_object(), ((class plan_info)_plans[PLAN_INFO])->data, event, information)) { check_current_plan_finished(); } } } /* notify_npc_event() */ /** * This activates the plan on the npc. * @param plan the plan to activate * @param goal the goal which is activating the plan */ void activate_plan(string plan, string goal, mixed data) { int priority; int pos; int i; int old; mixed *tmp; class plan_info info; priority = query_goal_priority(goal); /* First see if the plan is already activated. */ pos = member_array(plan, _plans); if (pos != -1) { /* Update the data value */ info = _plans[pos + PLAN_INFO]; info->data = plan->combine_plans(info->data, data); if (info->priority != priority) { /* Priority has changed... Tricky... */ /* First see if we should move up the list. */ old = -1; for (i = pos - PLAN_ARRAY_SIZE; i >= 0; i -= PLAN_ARRAY_SIZE) { if (priority > ((class plan_info)_plans[i + PLAN_INFO])->priority) { old = i; } else { break; } } if (old != -1) { /* We move upwards! */ tmp = _plans[pos..pos + PLAN_ARRAY_SIZE]; _plans = _plans[0..pos - 1] + _plans[pos + PLAN_ARRAY_SIZE..]; _plans = _plans[0..old - 1] + tmp + _plans[old..]; } else { /* Check to see if we need to move down. */ for (i = pos + PLAN_ARRAY_SIZE; i < sizeof(_plans); i += PLAN_ARRAY_SIZE) { if (priority < ((class plan_info)_plans[i + PLAN_INFO])->priority) { old = i; } else { break; } } if (old != -1) { /* We move down... :( */ tmp = _plans[old..old + PLAN_ARRAY_SIZE]; _plans = _plans[0..old - 1] + _plans[old + PLAN_ARRAY_SIZE..]; _plans = _plans[0..pos - 1] + tmp + _plans[pos..]; } } } } else { old = -1; info = new(class plan_info); info->data = data; info->priority = priority; /* Try to find the right spot to put it into the array. */ for (i = 0; i < sizeof(_plans); i += PLAN_ARRAY_SIZE) { if (priority > ((class plan_info)_plans[i + PLAN_INFO])->priority) { /* We go here... */ _plans = _plans[0..i - 1] + ({ plan, info }) + _plans[i..]; old = i + PLAN_ARRAY_SIZE; break; } } if (i >= sizeof(_plans)) { _plans += ({ plan, info }); } } tell_creator("pinkfish", "Activating %O, %O, %O\n", _current_plan, _plans[PLAN_PLAN], _plans[PLAN_INFO]); /* Check to see if any plans should be suspended or activated. */ if (_plans[PLAN_PLAN] != _current_plan) { if (_current_plan) { pos = member_array(_current_plan, _plans); if (pos != -1) { _plans[pos + PLAN_PLAN]->suspend_plan(this_object(), ((class plan_info)_plans[pos + PLAN_INFO])->data); } } _plans[PLAN_PLAN]->activate_plan(this_object(), ((class plan_info)_plans[PLAN_INFO])->data); // Check to see if any of the plans are completed now... while (check_current_plan_finished()); } } /* activate_plan() */ /** * This method checks to see if the top plan is finished or not. * @param plan the plan to finish * @return 1 if the plan has been finished, 0 if not */ int check_current_plan_finished() { if (sizeof(_plans)) { if (_plans[PLAN_PLAN]->finished(this_object(), ((class plan_info)_plans[PLAN_INFO])->data)) { _plans = _plans[PLAN_ARRAY_SIZE..]; if (sizeof(_plans)) { _plans[PLAN_PLAN]->activate_plan(this_object(), ((class plan_info)_plans[PLAN_INFO])->data); } return 1; } } return 0; } /* finish_plan() */ /** * This method changes the data associated with the specified plan. This * should only be called from the plan itself, as the plan will not be * informed of this change. * @param data the new data for the plan */ void set_plan_data(string plan, mixed data) { int pos; pos = member_array(plan, _plans); if (pos != -1) { ((class plan_info)_plans[pos + PLAN_INFO])->data = data; } } /* set_plan_data() */ /** @ignore yes */ mixed *stats() { mixed *ret; string goal; class goal_information frog; int goal_num; int i; goal_num = 0; ret = ({ }); foreach (goal in keys(_goals)) { frog = query_goal_information(goal); ret += ({ ({ "Goal " + goal_num + ") name", goal }) }); ret += ({ ({ "Goal " + goal_num + ") events", query_multiple_short(frog->events) }) }); ret += ({ ({ "Goal " + goal_num + ") priority", frog->priority }) }); goal_num++; } for (i = 0; i < sizeof(_plans); i += PLAN_ARRAY_SIZE) { ret += ({ ({ "Plan " + goal_num + ") name", _plans[i + PLAN_PLAN] }) }); ret += ({ ({ "Plan " + goal_num + ") priority", ((class plan_info)_plans[i + PLAN_INFO])->priority }) }); } return ret; } /* stats() */ /** * This method sets the default data associated with the specified plan. * This should be things like global plan things, the level at which the * npc will run away. Etc. * @param goal the goal to check the data for * @param plan the name of the plan * @param data the data associated with the plan * @see query_default_plan_data() */ void set_goal_plan_data(string goal, string plan, mixed data) { mixed goal_data; goal_data = query_goal_data(goal); goal->set_plan_data(this_object(), goal_data, plan, data); } /* set_default_plan_data() */ /** * This method returns the data associated with the specified plan. * @param goal the foal to query the information for * @param plan the name of the plan to get the data for * @return the plans data * @see set_default_plan_data() */ mixed query_goal_plan_data(string goal, string plan) { mixed goal_data; goal_data = query_goal_data(goal); return goal->query_plan_data(this_object(), goal_data, plan); } /* query_default_plan_data() */