Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
752 lines (679 sloc) 21.7 KB
/**
* This is the object matching code.
* It is based on some code by Scatter at Dawn Whispers, although this is
* both cut down and extended from what scatter had written.
* @changes 2000-04-20 Scatter File created.
* @changes 2000-05-20 Pinkfish Adapted to Discworld
*/
/**** This documentation should go into a man page? ****
Object matching
---------------
Singular objects are matched if the text is a combination of an optional
ordinal, zero or more adjectives and a noun (e.g. 1st red womble.)
Plural objects are matched if the text is a combination of an optional
counter, zero or more adjectives and a plural noun (4 red wombles.) Plural
object tokens can also match combinations of singular and plural object
matches seperated by " & " or ", " or " and " strings. For example,
red womble, 3 purple wombles and the second green cabbage.
Possesives (his/her/its) are handled as if they were adjectives, since
their use matches adjectives. Objectives (him/her/it) are handled as
if they were nouns. A parsing context is used to maintain information
about which object these should apply to.
Matching specific objects to a text string is carried out using the
parse_match() apply with the objects, rather than within this parser -
the above behaviour is the intended one. It is down to the parse_match()
apply to carry out the above requirements (see below for apply details.)
*/
#include <obj_parser.h>
#if !efun_defined(query_multiple_short)
inherit "/secure/simul_efun/multiple_short";
#endif
#include <playtesters.h>
#ifdef DEBUG
#define TRACE(ARG) tell_creator("pinkfish", ARG + "\n")
#else
#define TRACE(ARG)
#endif
#define EVERY_NUM 18729487
private nosave mapping _ordinals;
private nosave mapping _counters;
private nosave mapping _fractions;
//
// These are defines from elesewhere in the simul_efun inherit tree.
// don't remove the living() stuff, it's needed on the clone!
void tell_creator(string player, string text, mixed arg ...);
#if !efun_defined(living)
int living (object);
#endif
//
// This is a predefinition for functions in here.
//
class obj_match match_objects_in_environments( string input, mixed env_list,
int type, object player);
void create()
{
_ordinals = ([
"any" : 1,
"a" : 1,
"an" : 1,
"the" : 1,
"1st" : 1, "first" : 1,
"2nd" : 2, "second" : 2,
"3rd" : 3, "third" : 3,
"4th" : 4, "fourth" : 4,
"5th" : 5, "fifth" : 5,
"6th" : 6, "sixth" : 6,
"7th" : 7, "seventh" : 7,
"8th" : 8, "eighth" : 8,
"9th" : 9, "ninth" : 9,
"10th" : 10, "tenth" : 10,
"last" : -1
]);
_counters = ([
"1" : 1, "one" : 1,
"2" : 2, "two" : 2,
"3" : 3, "three" : 3,
"4" : 4, "four" : 4,
"5" : 5, "five" : 5,
"6" : 6, "six" : 6,
"7" : 7, "seven" : 7,
"8" : 8, "eight" : 8,
"9" : 9, "nine" : 9,
"10" : 10, "ten" : 10,
"11" : 11, "eleven" : 11,
"12" : 12, "twelve" : 12,
"13" : 13, "thirteen" : 13,
"14" : 14, "fourteen" : 14,
"15" : 15, "fifteen" : 15,
"16" : 16, "sixteen" : 16,
"17" : 17, "seventeen" : 17,
"18" : 18, "eighteen" : 18,
"19" : 19, "nineteen" : 19,
"20" : 20, "twenty" : 20,
"many" : 20,
"every" : EVERY_NUM,
]);
_fractions = ([
"half" : ({ 1, 2 }),
"quarter" : ({ 1, 4 }),
"some" : ({ 1, 50 })
]);
}
private void fixup_context(object player,
object* objects,
class obj_match_context context) {
if (!player || !sizeof(objects)) {
return ;
}
//
// Whoo! Successful, setup the new context.
//
if (sizeof(objects) > 1) {
context->plural = objects;
} else if (living(objects[0]) && objects[0] != player) {
if (objects[0]->query_male()) {
context->him = objects[0];
} else if (objects[0]->query_female() != 0) {
context->her = objects[0];
} else {
context->it = objects[0];
}
} else {
context->it = objects[0];
}
player->set_it_them(context);
} /* fixup_context() */
/*
* Match objects to words...
*/
/**
* Find an object in the given array that matches the given words.
* This will only return a correct match if the entire string
* matches. If it gets a partial match then nothing will be
* returned. The return array is of the format:<br>
* <pre>({ flag, class obj_match info })
* </pre>
* The flag can be one of:
* <dd>
* <dt>OBJ_PARSER_SUCCESS
* <dd>Successfuly matched the objects. the objects part of the class will
* contain the matched objects.
* <dt>OBJ_PARSER_NO_MATCH
* <dd>No successful match. The text bit of the omatch class will contain
* the text that didn't match
* <dt>OBJ_PARSER_AMBIGUOUS
* <dd>An ambigous match is returned, this means 'frog' was referenced
* when there was more than one frog. The objects part of the class
* has all the objects there were matched
* <dt>OBJ_PARSER_BAD_FRACTION
* <dd>The specified fracition was bad, the text bit of the class contains
* the bad fraction.
* <dt>OBJ_PARSER_FRACTION
* <dd>Means that a fraction was attempted to be applied to multiple
* objects.
* <dt>OBJ_PARSER_TOO_DARK
* <dd>Unable to match the specified object because it is too dark.
* </dl>
* @param input the input string to match
* @param ob_list the object to list to match in
* @param type we restrict the matching to certain groups of objects
* @param player the person doing the lookup
* @return an array of the format ({ flag, class obj_match info })
*/
class obj_match match_object_in_array( string input,
object *ob_list,
int type,
object player)
{
object ob;
object *singular_objects;
object *plural_objects;
object thing;
string first_word;
string rest;
string *bits;
string bit;
string inside_match;
string nick;
int n;
int ord;
int random_item;
int count;
int *fraction;
mixed *obj_info;
class obj_match result;
class obj_match omatch;
class obj_match_context context;
if (!player) {
player = this_player();
}
input = lower_case(input);
//
// This is used for determining things like 'it', 'them' and 'him'
// etc references.
//
if (player) {
context = player->query_it_them();
}
if (!classp(context) || sizeof(context) != 9) {
context = new(class obj_match_context);
context->plural = ({ });
if (player) {
player->set_it_them(context);
}
}
/* if we are working on a plural, then all the various ways of
* combining things (e.g. "1st red womble and 4 elephants, 3 tins & pot" )
* have to be standardised and then split and matched seperately.
*/
//if( strsrch( input, " and " ) != -1 ) {
//input = replace_string( input, " and ", ", " );
//}
TRACE( " Processed input: " + input );
omatch = new( class obj_match );
omatch->text = input;
omatch->objects = ({ });
if( strsrch( input, "&" ) != -1 ) {
//
// This allows one, the other or both to match.
//
TRACE( " Splitting input" );
foreach( bit in explode(input, "&") - ({ "" }) )
{
result = match_object_in_array( bit, ob_list,
type, player);
if( result->result == OBJ_PARSER_SUCCESS ) {
omatch->objects |= result->objects;
}
}
if (!sizeof(omatch->objects)) {
omatch->text = input;
omatch->result = OBJ_PARSER_NO_MATCH;
return omatch;
}
fixup_context(player, omatch->objects, context);
omatch->result = OBJ_PARSER_SUCCESS;
return omatch;
}
if (!(type & OBJ_PARSER_TYPE_EXISTENCE) &&
player && !player->query_property(OBJ_PARSER_USE_AND_AS_BREAK_PROP)) {
input = replace_string(input, " and ", ",");
}
if( strsrch( input, "," ) != -1 )
{
TRACE( " Splitting input" );
foreach( bit in explode(input, ",") - ({ "" }) )
{
result = match_object_in_array( bit, ob_list,
type, player);
if( result->result == OBJ_PARSER_SUCCESS ) {
omatch->objects |= result->objects;
} else if (!(type & OBJ_PARSER_TYPE_EXISTENCE)) {
return result;
}
}
fixup_context(player, omatch->objects, context);
omatch->result = OBJ_PARSER_SUCCESS;
return omatch;
}
//
// Do a nickname lookup.
//
if (player) {
nick = player->expand_nickname(input);
if (nick && nick!="") {
input = nick;
}
}
//
// xx in yy syntax.
//
if (!(type & OBJ_PARSER_TYPE_NO_NESTED)) {
n = strsrch(input, " in ", -1);
if (n == -1) {
n = strsrch(input, " on ", -1);
}
if (n != -1) {
inside_match = input[0..n - 1];
input = input[n + 4..];
} else {
inside_match = 0;
}
}
/* if we got to here, input is already split as necessary and
* we are working on a fragment
*/
n = strsrch( input, " " );
ord = 0;
if( n != -1 )
{
first_word = input[ 0 .. n - 1 ];
rest = input[ n + 1 .. ];
//
// check for fractions.
//
fraction = _fractions[ first_word ];
if (!fraction) {
if (sscanf(first_word, "%d/%d", n, count) == 2) {
if (n > count || n < 0 || count <= 0) {
omatch = new( class obj_match );
omatch->text = input;
omatch->objects = ({ });
omatch->result = OBJ_PARSER_BAD_FRACTION;
return omatch;
}
fraction = ({ n, count });
}
count = 0;
}
if (fraction) {
input = rest;
if (input[0..2] == "of ") {
input = input[3..];
}
}
n = strsrch(input, " ");
}
if( n != -1 )
{
/* we have more than one word to match. The first word could be an ordinal
* or a counter depending whether we are looking for a plural or a type.
* This must be converted to a number to use later, and the word stripped
* from the text to match. Example counter: "four coins", example ordinal:
* "2nd coin"
*/
first_word = input[ 0 .. n - 1 ];
rest = input[ n + 1 .. ];
ord = _ordinals[ first_word ];
if( ord > 0 ) {
input = rest;
}
if( !ord )
{
count = _counters[ first_word ];
if( !count ) {
sscanf( first_word, "%d", count );
}
if( count > 0 ) {
input = rest;
}
if (!count) {
//
// Check for a number at the end...
//
n = strsrch( input, " ", -1);
if (n != -1) {
if (sscanf(input[n + 1..], "%d", ord) == 1) {
input = input[0..n-1];
}
}
}
}
n = strsrch(input, " ");
}
if (n != -1 && input[0 .. n - 1] == "random") {
if (ord) {
random_item = ord;
} else {
random_item = 1;
}
count = EVERY_NUM;
ord = 0;
input = input[n + 1..];
}
omatch = new( class obj_match );
omatch->text = input;
omatch->objects = ({ });
bits = explode(input, " ");
if (!sizeof(bits)) {
omatch->result = OBJ_PARSER_NO_MATCH;
}
context->ordinal = ord;
context->number_included = count;
context->ignore_rest = 0;
context->fraction = fraction;
/* if we are looking for a plural, try to match the segment to a
* plural id first (even if we are looking for a plural, the
* particular segment may be singular, consider "blue flowers and 1st ball")
*/
singular_objects = ({ });
plural_objects = ({ });
/* check each object in the list, asking it how it matches the
* text segment.
* The parse_match method is assumed to handle noting the ordinal
* and count stuff.
*/
if (player) {
ob_list = filter(ob_list, (: $1->query_visible($(player)) :));
}
foreach( ob in ob_list )
{
if (!inside_match && (type & OBJ_PARSER_TYPE_LIVING)) {
if (!living(ob)) {
continue;
}
}
if (!inside_match && (type & OBJ_PARSER_TYPE_PLAYER)) {
if (!userp(ob)) {
continue;
}
}
obj_info = ob->parse_match_object( bits, player, context );
if (obj_info) {
if (obj_info[OBJ_PARSER_MATCH_TYPE] & OBJ_PARSER_MATCH_PLURAL)
{
plural_objects += obj_info[OBJ_PARSER_OBJECTS];
}
if (obj_info[OBJ_PARSER_MATCH_TYPE] & OBJ_PARSER_MATCH_SINGULAR)
{
singular_objects += obj_info[OBJ_PARSER_OBJECTS];
}
}
if (context->ignore_rest) {
break;
}
}
//
// If we are looking for a singular, or we matched multiple singulars
// and we did not specify which one.
//
if (sizeof(singular_objects) > 1 &&
(!ord && !count)) {
if (player && !player->query_property(OBJ_PARSER_AMBIGUOUS_PROP)) {
omatch->objects = singular_objects;
omatch->result = OBJ_PARSER_AMBIGUOUS;
return omatch;
}
//
// Strip it back to just one object.
//
if (!random_item) {
singular_objects = singular_objects[0..0];
} else {
n = random(sizeof(singular_objects));
singular_objects = singular_objects[n..n];
}
}
if( !sizeof( singular_objects ) &&
!sizeof( plural_objects))
{
/* nothing has matched the text. */
TRACE( " No matches" );
omatch->result = OBJ_PARSER_NO_MATCH;
return omatch;
}
if (type & OBJ_PARSER_TYPE_SLOPPY_MATCHING) {
omatch->objects = singular_objects & plural_objects;
} else if (random_item) {
if (sizeof(plural_objects)) {
omatch->objects = plural_objects;
} else {
omatch->objects = singular_objects;
}
n = random(sizeof(omatch->objects));
omatch->objects = omatch->objects[n..n];
} else if (ord || count == 1 || count == EVERY_NUM) {
if (ord == -1) {
omatch->objects = singular_objects[<1..<1];
} else {
omatch->objects = singular_objects;
}
} else if (count) {
omatch->objects = plural_objects;
} else {
if (sizeof(plural_objects)) {
omatch->objects = plural_objects;
} else {
omatch->objects = singular_objects;
}
}
if (sizeof(omatch->objects) > 1 && fraction) {
omatch->result = OBJ_PARSER_FRACTION;
return omatch;
}
//
// Check and see if we didn't match enough objects.
//
if (context->number_included && count != EVERY_NUM) {
omatch->result = OBJ_PARSER_NOT_ENOUGH;
return omatch;
}
//
// If we are doing inside matches... Try it here...
//
if (inside_match) {
foreach (thing in omatch->objects) {
if (!thing->can_find_match_recurse_into(player)) {
omatch->objects -= ({ thing });
}
}
if (sizeof(omatch->objects)) {
result = match_objects_in_environments(inside_match,
omatch->objects,
type,
player);
omatch->objects = ({ });
if (result->result == OBJ_PARSER_SUCCESS) {
foreach (thing in result->objects) {
if (environment(thing) &&
environment(thing)->can_find_match_reference_inside_object(thing, player)) {
omatch->objects += ({ thing });
}
}
} else {
result->text = inside_match + " in " + input;
return result;
}
}
}
if( sizeof( omatch->objects ) == 0 )
{
/* matches have been removed */
TRACE( " No matches (living/visible elimination)" );
omatch->result = OBJ_PARSER_NO_MATCH;
return omatch;
}
fixup_context(player, omatch->objects, context);
omatch->result = OBJ_PARSER_SUCCESS;
return omatch;
} /* match_object_in_array() */
/**
* Find an object in the given environments that match the given words.
* This will only return a correct match if the entire string
* matches. If it gets a partial match then nothing will be
* returned. The return array is of the format:<br>
* <pre>({ flag, class obj_match info })
* </pre>
* The flag can be one of:
* <dd>
* <dt>OBJ_PARSER_SUCCESS
* <dd>Successfuly matched the objects. the objects part of the class will
* contain the matched objects.
* <dt>OBJ_PARSER_NO_MATCH
* <dd>No successful match. The text bit of the omatch class will contain
* the text that didn't match
* <dt>OBJ_PARSER_AMBIGUOUS
* <dd>An ambigous match is returned, this means 'frog' was referenced
* when there was more than one frog. The objects part of the class
* has all the objects there were matched
* <dt>OBJ_PARSER_BAD_FRACTION
* <dd>The specified fracition was bad, the text bit of the class contains
* the bad fraction.
* <dt>OBJ_PARSER_FRACTION
* <dd>Means that a fraction was attempted to be applied to multiple
* objects.
* <dt>OBJ_PARSER_TOO_DARK
* <dd>Unable to match the specified object because it is too dark.
* </dl>
* @param input the input string to match
* @param env_list the environments to get the objects from
* @param singular force a singular match
* @param player the person doing the lookup
* @return an array of the format ({ flag, class obj_match info })
*/
class obj_match match_objects_in_environments( string input, mixed env_list,
int type, object player)
{
object* tmp_expanded;
object* stuff;
object ob;
class obj_match omatch;
if (!player) {
player = this_player();
}
/*
if (!player || !PLAYTESTER_HAND->query_tester(player)) {
stuff = find_match(input, env_list, player);
omatch = new(class obj_match);
omatch->text = input;
omatch->objects = stuff;
if (!sizeof(stuff)) {
omatch->result = OBJ_PARSER_NO_MATCH;
} else {
omatch->result = OBJ_PARSER_SUCCESS;
}
return omatch;
}
*/
if (!pointerp(env_list)) {
if (input == "all" &&
env_list->query_is_room() &&
player->check_dark(env_list->query_light())) {
omatch = new( class obj_match );
omatch->text = input;
omatch->objects = ({ });
omatch->result = OBJ_PARSER_TOO_DARK;
return omatch;
}
tmp_expanded = env_list->find_inv_match(input,player);
} else {
tmp_expanded = ({ });
foreach (ob in env_list) {
if (!ob) {
continue;
}
if (input == "all" &&
ob->query_is_room() &&
player->check_dark(ob->query_light())) {
continue;
}
stuff = ob->find_inv_match(input,player);
if (stuff && sizeof(stuff)) {
tmp_expanded += stuff;
}
}
}
if (!sizeof(tmp_expanded)) {
omatch = new( class obj_match );
omatch->text = input;
omatch->objects = ({ });
omatch->result = OBJ_PARSER_NO_MATCH;
return omatch;
}
return match_object_in_array(input, tmp_expanded, type,
player);
} /* match_objects_in_environment() */
/**
* This method checks for existance of the specified objects. It will
* return any ambiguous matches as well as real matches. This should only
* be used in cases in the code where it is not nessessary to distinguish
* between different objects of the same type.
* @param input the input string to check
* @param env_list the environments to check in
* @param player the player to check with
* @return the array of objects
*/
object* match_objects_for_existence(string input,
object* env_list,
object player) {
class obj_match stuff;
stuff = match_objects_in_environments(input, env_list,
OBJ_PARSER_TYPE_EXISTENCE, player);
switch (stuff->result) {
case OBJ_PARSER_SUCCESS :
case OBJ_PARSER_AMBIGUOUS :
return stuff->objects;
default :
return ({ });
}
} /* match_objects_for_existence() */
/**
* This method returns the failed message for the specified
* failed match string.
* @param failed_match the return result from match_objects_*
* @return the message to print when it gets an error
*/
string match_objects_failed_mess(class obj_match failed_match) {
switch (failed_match->result) {
case OBJ_PARSER_BAD_ENVIRONMENT :
return "Cannot find \""+ failed_match->text +
"\" here, access is not allowed.\n";
case OBJ_PARSER_NOT_LIVING :
return "The objects \""+
query_multiple_short(failed_match->objects) +
"\" are not living.\n";
case OBJ_PARSER_TOO_DARK :
return "Cannot find \""+ failed_match->text +
"\", it is too dark.\n";
case OBJ_PARSER_NO_MATCH :
return "Cannot find \""+ failed_match->text +
"\", no match.\n";
case OBJ_PARSER_BAD_FRACTION :
return "The fraction \""+ failed_match->text +
"\" is incorrectly specified.\n";
case OBJ_PARSER_FRACTION :
return "Can only reference a single object with a "
"fraction, matched " +
query_multiple_short(failed_match->objects) +
" please be more specific.\n";
case OBJ_PARSER_AMBIGUOUS :
return "There are multiple matches for \"" + failed_match->text +
"\". See 'help parser' for more information on how to "
"be more specific.\n";
case OBJ_PARSER_NOT_ENOUGH :
return "There are not enough \"" + failed_match->text +
"\" to match as specified.\n";
default :
return "Unknow parser errror " + failed_match->result + ".\n";
}
} /* setup_failed_mess() */