/* Do not remove the headers from this file! see /USAGE for more info. */ #include <mudlib.h> /* General message handling. Inherit it in anything that needs it. * * -Beek */ //:MODULE // The message module. The correct way to compose and send any messages // To users is using this module, as it will automatically get the grammar // right for each person involved. /* More simul conversion fall out */ string punctuate(string); nosave private string vowels = "aeiouAEIOU"; #define A_SHORT(x) (objectp(x) ? x->a_short() : (member_array(x[0], vowels) == -1 ? "a " : "an ") + x) #define SHORT(x) (objectp(x) ? x->short() : x) mapping messages = ([]); string def_message_type; string _a_short(mixed x) { if(objectp(x)) return x->a_short(); // Check for "a"/"an" prefix - if missing, add it if(stringp(x)) if(strsrch(lower_case(x), "a ") != 0) if(strsrch(lower_case(x), "an ") != 0) { if(member_array(x[0],vowels) == -1) return "a " + x; else return "an " + x; } return x; } string _the_short(mixed x) { if(objectp(x)) return x->the_short(); // Check for "the" prefix - if missing, add it if(stringp(x)) if(strsrch(lower_case(x), "the ") != 0) return "the " + x; return x; } void set_def_msgs(string type){ def_message_type=type; } void add_msg(string cls, string msg) { if (!messages) messages = ([]); if (arrayp(messages[cls])) messages[cls] += ({ msg }); else if (messages[cls]) messages[cls]=({ messages[cls], msg }); else messages[cls]=msg; } string query_msg(string which) { return messages[which] || MESSAGES_D->get_messages(def_message_type)[which]; } void set_msgs(string cls, string *msgs) { if (!messages) messages = ([]); if(!msgs || !sizeof(msgs)) { map_delete(messages, cls); return; } messages[cls] = msgs; } void clear_messages(){ messages = ([]); } string *query_msg_types() { return clean_array(keys(messages) + keys(MESSAGES_D->get_messages(def_message_type))); } array handle_obs(array obs, string res, mapping has) { string array ret = ({}); mapping items = ([]); string t_short; if(objectp(obs[0]) && has[obs]) ret = ({"them"}); else { foreach(mixed ob in obs) { t_short = SHORT(ob); if(member_array(t_short, keys(items)) < 0) items += ([ t_short : 1 ]); else items[t_short] ++; } obs = keys(items); if (res[<2..<1]=="a ") { res = res[0..<3]; foreach(mixed ob in obs) { if(items[ob]>1) ret += ({ items[ob] + " " + M_GRAMMAR->pluralize(SHORT(ob)) }); else ret += ({ _a_short(ob) }); } } else if (res[<4..<1] == "the ") { res = res[0..<5]; foreach(mixed ob in obs) { if(items[ob]>1) ret += ({ "the " + items[ob] + " " + M_GRAMMAR->pluralize(SHORT(ob)) }); else ret += ({ _the_short(ob) }); } } else if (res[<2..<1] == "A ") { res = res[0..<3]; foreach(mixed ob in obs) { if(items[ob]>1) ret += ({ items[ob] + " " + M_GRAMMAR->pluralize(SHORT(ob)) }); else ret += ({ capitalize(_a_short(ob)) }); } } else if (res[<4..<1] == "The ") { res = res[0..<5]; foreach(mixed ob in obs) { if(items[ob]>1) ret += ({ "the " + items[ob] + " " + M_GRAMMAR->pluralize(SHORT(ob)) }); else ret += ({ capitalize(_the_short(ob)) }); } } else foreach(mixed ob in obs) { if(items[ob]>1) ret += ({ items[ob] + " " + M_GRAMMAR->pluralize(SHORT(ob)) }); else ret += ({ SHORT(ob) }); } has[obs]++; } return ({ res, ret }); } array handle_ob(mixed ob, string res, mapping has) { string bit; if (objectp(ob) && has[ob]) bit = "it"; else { if (res[<2..<1]=="a ") { res = res[0..<3]; bit = _a_short(ob); } else if (res[<4..<1] == "the ") { res = res[0..<5]; bit = _the_short(ob); } else if (res[<2..<1] == "A ") { res = res[0..<3]; bit = capitalize(_a_short(ob)); } else if (res[<4..<1] == "The ") { res = res[0..<5]; bit = capitalize(_the_short(ob)); } else bit = SHORT(ob); has[ob]++; has[bit]++; } return ({ res, bit }); } //:FUNCTION compose_message //The lowest level message composing function; it is passed the object //for whom the message is wanted, the message string, the array of people //involved, and the objects involved. It returns the appropriate message. //Usually this routine is used through the higher level interfaces. varargs string compose_message(object forwhom, string msg, object *who, array obs...) { mixed ob; array fmt; string res; int i; int c; int num, subj; string str; string bit; mapping has = ([]); mixed tmp; fmt = reg_assoc(msg, ({ "\\$[NnVvTtPpOoRr][a-z0-9]*" }), ({ 1 }) ); fmt = fmt[0]; // ignore the token info for now res = fmt[0]; i=1; while (i<sizeof(fmt)) { c = fmt[i][1]; if (fmt[i][2] && fmt[i][2]<'a') { if (fmt[i][3] && fmt[i][3] < 'a') { subj = fmt[i][2] - '0'; num = fmt[i][3] - '0'; str = fmt[i][4..<0]; } else { subj = 0; num = fmt[i][2] - '0'; str = fmt[i][3..<0]; } } else { subj = 0; num = ((c == 't' || c == 'T') ? 1 : 0); // target defaults to 1, not zero str = fmt[i][2..<0]; } switch (c) { case 'o': case 'O': ob = obs[num]; if (arrayp(ob)) { if(sizeof(ob)) { tmp = handle_obs(ob, res, has); res = tmp[0]; ob = tmp[1]; bit = format_list(ob); } else { tmp = ({ res }); for (int z = 0; z < sizeof(ob); z++) { tmp = handle_ob(ob[z], res, has); ob[z] = tmp[1]; } res = tmp[0]; bit = format_list(ob); } } else { tmp = handle_ob(ob, res, has); res = tmp[0]; bit = tmp[1]; } break; case 't': case 'T': /* Only difference between $n and $t is that $t defaults to $n1o */ /* Fall through */ if (str=="") str = "o"; case 'n': case 'N': if (str=="") str = "s"; if (str != "p") { if(str!="d") { /* Handle reflexification */ if (subj < sizeof(who) && (who[subj] == who[num]) && has[who[subj]]) { // objective: You kick yourself, Beek kicks himself. if (str == "o") { if (forwhom == who[subj]) bit = "yourself"; else bit = who[subj]->query_reflexive(); } // subjective: You prove you are stupid, // Beek proves he is stupid. if (str == "s") { if (forwhom == who[subj]) bit = "you"; else bit = who[subj]->query_subjective(); } break; } /* Other pronouns */ if (who[num]==forwhom) { bit = "you"; has[who[num]]++; break; } if (has[who[num]]) { if (str[0]=='o') bit = who[num]->query_objective(); else bit = who[num]->query_subjective(); break; } has[who[num]]++; bit = who[num]->a_short(); break; } } has[who[num]]++; bit = who[num]->short(); break; case 'R': case 'r': if (forwhom == who[num]) bit = "yourself"; else bit = who[num]->query_reflexive(); break; case 'v': case 'V': /* hack for contractions */ if (i + 1 < sizeof(fmt) && fmt[i+1][0..2] == "'t ") { str += "'t"; fmt[i+1] = fmt[i+1][2..]; } if (num >= sizeof(who) || who[num]!=forwhom) bit = M_GRAMMAR->pluralize(str); else bit = str; break; case 'p': case 'P': if (forwhom == who[num]) { bit = "your"; break; } if (has[who[num]]) { bit = who[num]->query_possessive(); break; } bit = who[num]->query_named_possessive(); has[who[num]]++; break; } // hack to prevent errors. if (!bit) bit = ""; if (c < 'a') bit = capitalize(bit); //### Hack to avoid inheriting a mixin. Better one needed. if (fmt[i+1][0] == '.') res += M_GRAMMAR->punctuate(bit) + fmt[i+1][1..]; else res += bit + fmt[i+1]; i+=2; } if ( strlen(res) > 0 && res[<1] != '\n' ) res += "\n"; return res; } //:FUNCTION action //Make the messages for a given group of people involved. The return //value will have one array per person, as well as one for anyone else. //inform() can be used to send these messages to the right people. //see: inform varargs string *action(object *who, mixed msg, array obs...) { int i; string *res; if (arrayp(msg)) msg = choice(msg); res = allocate(sizeof(who)+1); for (i=0; i<sizeof(who); i++) res[i] = compose_message(who[i], msg, who, obs...); res[sizeof(who)]=compose_message(0, msg, who, obs...); return res; } //### This now always indents continuation lines. Might want a flag at the //### end to enable or disable that. //:FUNCTION inform //Given an array of participants, and an array of messages, and either an //object or array of objects, deliver each message to the appropriate //participant, being careful not to deliver a message twice. //The last arg is either a room, in which that room is told the 'other' //message, or an array of people to recieve the 'other' message. void inform(object *who, string *msgs, mixed others) { int i; mapping done = ([]); for (i=0; i<sizeof(who); i++) { if (done[who[i]]) continue; done[who[i]]++; tell(who[i], msgs[i], MSG_INDENT); } if (arrayp(others)) map_array(others - who, (: tell_from_outside($1, $(msgs[<1]), MSG_INDENT) :)); else if (others) tell_from_inside(others, msgs[sizeof(who)], MSG_INDENT, who); } //:FUNCTION simple_action //Generate and send messages for an action involving the user and possibly //some objects varargs void simple_action(mixed msg, array obs...) { string us; string others; object *who; if( !sizeof( msg )) return; /* faster to only make who once */ who = ({ this_object() }); if (arrayp(msg)) msg = msg[random(sizeof(msg))]; us = compose_message(this_object(), msg, who, obs...); others = compose_message(0, msg, who, obs...); tell(this_object(), us, MSG_INDENT); tell_from_outside(all_inventory(this_object()),others,MSG_INDENT); tell_environment(this_object(), others, MSG_INDENT, who); } //:FUNCTION my_action //Generate and send a message that should only be seen by the person doing it varargs void my_action(mixed msg, array obs...) { string us; object *who; if (!sizeof( msg )) return; who = ({ this_object() }); if (arrayp(msg)) msg = msg[random(sizeof(msg))]; us = compose_message(this_object(), msg, who, obs...); tell(this_object(), us, MSG_INDENT); } //:FUNCTION other_action //Generate and send a message that should only be seen by others varargs void other_action(mixed msg, array obs...) { string others; object *who; if( !sizeof(msg)) return; who = ({ this_object() }); if (arrayp(msg)) msg = msg[random(sizeof(msg))]; others = compose_message(0, msg, who, obs...); tell_from_outside(all_inventory(this_object()),others,MSG_INDENT); tell_environment(this_object(), others, MSG_INDENT, who); } //:FUNCTION targetted_action //Generate and send a message involving the doer and a target (and possibly //other objects) varargs void targetted_action(mixed msg, object target, array obs...) { string us, them, others; object *who; if( !sizeof(msg)) return; who = ({ this_object(), target }); if (arrayp(msg)) msg = msg[random(sizeof(msg))]; us = compose_message(this_object(), msg, who, obs...); them = compose_message(target, msg, who, obs...); others = compose_message(0, msg, who, obs...); tell(this_object(), us, MSG_INDENT); tell(target, them, MSG_INDENT); tell_from_outside(all_inventory(this_object()),others,MSG_INDENT); tell_from_outside(all_inventory(target),others,MSG_INDENT); tell_environment(this_object(), others, MSG_INDENT, who); }