/**************************************************************************/ // language.cpp - dawn language system, completely rewritten 16 march 2003 /*************************************************************************** * The Dawn of Time v1.69r (c)1997-2004 Michael Garratt * * >> A number of people have contributed to the Dawn codebase, with the * * majority of code written by Michael Garratt - www.dawnoftime.org * * >> To use this source code, you must fully comply with the dawn license * * in licenses.txt... In particular, you may not remove this copyright * * notice. * **************************************************************************/ #include "include.h" #include "language.h" language_data *languages; /**************************************************************************/ /* // Language lookups are performed using a tree, which goes 'across' and 'down' The words "Attention, Attentive and Attractive" are stored as follows: [A]->[B]->[C]->[D] . T . T . E -> R . . N A . . T C . . I [T] . . O -> V I . . . [N] [E] V . [E] Where: -> represents the next possible character instead of the one to the left . represents the next character in the string creating a word [ ] represents a node with a stored_word set The following output demonstrates the structure using the builtin dump features ===elven -> ' ' a -> 'a' s -> 've' h -> 'lith' gain -> 'en' nger -> 'ruth' byss -> 'ia' horrence -> 'deloth' ominable -> 'saur' ide -> 'mar' road -> 'palan' xes -> 'baruk' utumn -> 'yavie' we -> 'gaya' akening -> 'coire' ir -> 'wilya' ll -> 'ilya' as -> 'ai' one -> 'er' ... w -> 'x' ay -> 'pata' ll -> 'ramba' nder -> 'raen' er -> 'randir' ter -> 'nen' fall -> 'lanthir' ch -> 'tir' ve -> 'falma' right -> 'dan' hole -> 'iluve' ite -> 'glos' ing -> 'rama' ter -> 'hrive' d -> 'sul' ow -> 'henneth' sdom -> 'noldo' thout -> 'ar' ll -> 'uva' ow -> 'tasare' eapon -> 'saalah' rewolf -> 'nguar' ek -> 'enquie' st -> 'numen' ord -> 'quetta' lf -> 'draug' ods -> 'taure' This structures makes it very efficient to perform lookups, by searching for a letter at a time in the word tree... while also making it possible to extend the system dynamically. */ /**************************************************************************/ // this is a custom GIO function, which makes it so languages flagged // as a system language aren't saved within the list GIO saves int language_data_gio_dontsavetest(gio_type *gio_table, int tableIndex, void *data, FILE *fp) { language_data * language; language= (language_data *) data; if(IS_SET(language->flags, LANGFLAG_SYSTEM_LANGUAGE)){ // system languages aren't saved into the index return true; } return false; } /**************************************************************************/ GIO_START(language_data) GIO_CUSTOM_DONT_SAVE_RECORD(language_data_gio_dontsavetest) GIO_STR(name) GIO_STR(skillname) GIO_STR(commandname) GIO_WFLAGH(flags, "langflags ", language_flags) GIO_FINISH_STRDUP_EMPTY /**************************************************************************/ GIO_START(wordmapping_data) GIO_STR(from) GIO_STR(to) GIO_FINISH_STRDUP_EMPTY /**************************************************************************/ void languages_save(char_data *ch, char *argument) { language_data *language; log_string("Saving language wordmaps..."); for(language=languages; language; language=language->next){ if(IS_SET(language->flags, LANGFLAG_SYSTEM_LANGUAGE)){ // system languages aren't saved into the index continue; } if(str_cmp(argument, "all")){ if(!IS_SET(language->flags, LANGFLAG_CHANGED)){ // only save changed languages continue; } } // mark it as saved, so we dont write this flag to disk REMOVE_BIT( language->flags, LANGFLAG_CHANGED); // create the wordlist for saving, save it, then deallocate the wordlist. language->words_recreate_list(); ch->printlnf("Saving %s language wordmap to %s", language->name, FORMATF(LANGUAGES_DIR "%s.txt", language->name)); GIOSAVE_LIST(language->words, wordmapping_data, FORMATF(LANGUAGES_DIR "%s.txt", language->name), true); language->words_deallocate_list(); } log_string("Language wordmaps saved."); log_string("Saving index of languages..."); // save our list of languages GIOSAVE_LIST(languages, language_data, LANGUAGES_INDEX_FILE, true); log_string("Languages index saved."); ch->printlnf("Saving index of languages to %s", LANGUAGES_INDEX_FILE); ch->println("Language save completed."); } /**************************************************************************/ void do_write_languages(char_data *ch, char *argument) { languages_save(ch, argument); } /**************************************************************************/ void languages_create_system_languages() { log_string("Creating system languages:"); language_data *ld; // the languages are created in the oposite order so we have // 'unknown' first in the list of languages // language_reverse log_string("- language reverse"); language_reverse=new language_data; ld=language_reverse; ld->gsn=-1; ld->name=str_dup("reverse"); ld->skillname=str_dup("reverse"); ld->commandname=str_dup("reverse"); ld->flags=LANGFLAG_SYSTEM_LANGUAGE|LANGFLAG_NO_LANGUAGE_NAME |LANGFLAG_NO_ORDER|LANGFLAG_REVERSE_TEXT; ld->words=NULL; ld->initialise_tree(); // deallocate the memory used to load in the words, since they aren't // typically used once we have constructed the word tree ld->words_deallocate_list(); ld->next=languages; languages=ld; // language_alwaysunderstood log_string("- language alwaysunderstood"); language_alwaysunderstood=new language_data; ld=language_alwaysunderstood; ld->gsn=-1; ld->name=str_dup("alwaysunderstood"); ld->skillname=str_dup(""); ld->commandname=str_dup("-"); ld->flags=LANGFLAG_SYSTEM_LANGUAGE|LANGFLAG_NO_LANGUAGE_NAME |LANGFLAG_NO_ORDER|LANGFLAG_NO_COMMAND_ACCESS; ld->words=NULL; ld->initialise_tree(); // deallocate the memory used to load in the words, since they aren't // typically used once we have constructed the word tree ld->words_deallocate_list(); ld->next=languages; languages=ld; // language_native log_string("- language native"); language_native=new language_data; ld=language_native; ld->gsn=-1; ld->name=str_dup("native"); ld->skillname=str_dup("native"); ld->commandname=str_dup("native"); ld->flags=LANGFLAG_SYSTEM_LANGUAGE | LANGFLAG_NO_ORDER; ld->words=NULL; ld->initialise_tree(); // deallocate the memory used to load in the words, since they aren't // typically used once we have constructed the word tree ld->words_deallocate_list(); ld->next=languages; languages=ld; // language_unknown log_string("- language unknown"); language_unknown=new language_data; ld=language_unknown; ld->gsn=-1; ld->name=str_dup("unknown"); ld->skillname=str_dup(""); ld->commandname=str_dup("-"); ld->flags=LANGFLAG_SYSTEM_LANGUAGE | LANGFLAG_NO_ORDER | LANGFLAG_NO_COMMAND_ACCESS; ld->words=NULL; ld->initialise_tree(); // deallocate the memory used to load in the words, since they aren't // typically used once we have constructed the word tree ld->words_deallocate_list(); ld->next=languages; languages=ld; log_string("System languages created."); } /**************************************************************************/ void languages_load_and_initialise() { log_string("Loading Languages..."); // load our list of languages languages=NULL; language_data *language; GIOLOAD_LIST(languages, language_data, LANGUAGES_INDEX_FILE); // remove any language with a name of: // "unknown", "native", "alwaysunderstood" or "reverse" // at the same time check to ensure there are no spaces or // single quotes in the language names language_data *previous_language=NULL; for(language=languages; language; language=language->next){ if(is_exact_name(language->name, "unknown native alwaysunderstood reverse")){ bugf("Found system language '%s' in %s - removing it from the list.", language->name, LANGUAGES_INDEX_FILE); if(previous_language){ previous_language->next=language->next; }else{ languages=language->next; } }else{ previous_language=language; } if(has_space(language->name)){ logf("language '%s' has a space in its name - this is not supported, manually fix then restart.", language->name); exit_error( 1 , "languages_load_and_initialise", "invalid language name - has space"); } if(count_char(language->name, '\'')!=0){ logf("language '%s' has a single quote character in the name - this is not supported, manually fix then restart.", language->name); exit_error( 1 , "languages_load_and_initialise", "invalid language name - has single quote"); } } // load each individual languages worddata for(language=languages; language; language=language->next){ GIOLOAD_LIST(language->words, wordmapping_data, FORMATF(LANGUAGES_DIR "%s.txt", language->name)); language->words_remove_unsupported_basewords(); language->words_initialise_words_last(); logf("Initialising '%s' lookup tree.", language->name); language->initialise_tree(); // deallocate the memory used to load in the words, since they aren't // typically used once we have constructed the word tree language->words_deallocate_list(); // setup the skill name and gsn if(IS_NULLSTR(language->skillname)){ language->skillname=str_dup(language->name); } language->gsn=-1; // gets initialised later after the skills have been loaded } // loop thru all languages, printing the wordmaps in each one // wordmapping_data *word; // for(language=languages; language; language=language->next){ // logf("======%10s\n", language->name); // for(word=language->words; word; word=word->next){ // logf("%20s -> %s\n", word->from, word->to); // } // } log_string("Language load complete."); // create system languages languages_create_system_languages(); } /**************************************************************************/ void language_init_gsn_and_unique_id() { language_data *language; log_string("Initialising language gsn values"); // load each individual languages worddata for(language=languages; language; language=language->next){ language->gsn=skill_exact_lookup(language->skillname); if(IS_NULLSTR(language->commandname)){ language->commandname=str_dup(language->name); } if(IS_NULLSTR(language->skillname)){ continue; } if(language->gsn<0){ bugf("can't find a language skill called '%s' for '%s'", language->skillname, language->name); } } log_string("Initialising language unique ids"); // unique id's are used by items of type parchment, to record the // language the parchment is written in int id=0; for(language=languages; language; language=language->next){ language->unique_id=++id; } } /**************************************************************************/ // the data stored in 'words' is only used to load and save the wordlist void language_data::words_deallocate_list() { wordmapping_data *word, *worc_next; for(word=words; word; word=worc_next){ // backup the next pointer worc_next=word->next; // deallocate the memory used in this node free_string(word->from); free_string(word->to); word->next=NULL; delete word; } words=NULL; words_last=NULL; } /**************************************************************************/ void language_data::words_initialise_words_last() { wordmapping_data *word; words_last=NULL; for(word=words; word; word=word->next){ words_last=word; } } /**************************************************************************/ void language_data::words_remove_unsupported_basewords() { wordmapping_data *word, *prev=NULL; for(word=words; word; word=word->next){ if(str_len(word->from)<2 && !is_alnum( *(word->from) ) ){ if(prev){ prev->next=word->next; }else{ words=word->next; } logf("words_remove_unsupported_basewords(): removed '%s' -> '%s'", word->from, word->to); // aren't worried about freeing memory here, since this will // only happen when they have invalid characters in the // base wordmap - not normal. continue; } prev=word; } } /**************************************************************************/ // append the word mapping into the chain of words void language_data::words_add_word_mapping(const char *wordfrom, const char *wordto) { wordmapping_data *node; // sanity check the input assert(!IS_NULLSTR(wordfrom)); node = new wordmapping_data; node->from=str_dup(wordfrom); node->to=str_dup(wordto); node->next=NULL; // append to the list of words if(words_last){ words_last->next=node; words_last=node; }else{ assert(!words); words=node; words_last=node; } } /**************************************************************************/ // the data stored in 'words' is only used to load and save the wordlist // we are most likely just about to save the language to disk void language_data::words_recreate_list() { char buf[MSL]; buf[0]='\0'; // first confirm there isn't any existing list words_deallocate_list(); // now get the tree to add each word to the table for(int i=0; i<256; i++){ if(tree[i]){ tree[i]->add_subtree_to_wordlist(0, buf, this); } } } /**************************************************************************/ int language_data::find(const char *characters, languagetree_data **tail) { return tree[(unsigned char)*characters]->find(characters, tail); } /**************************************************************************/ void language_data::add_wordmap_to_tree(char *from, char *to) { //logf("%20s -> %s\n", from, to); if(tree[(unsigned char)*from]){ tree[(unsigned char)*from]->add(NULL, from, to); }else{ tree[(unsigned char)*from]=new languagetree_data(NULL, from, to); } } /**************************************************************************/ // from a loaded wordlist, create the word tree void language_data::initialise_tree() { // start with an empty tree memset(tree, 0, sizeof(tree)); // start off with an empty tree // loop thru all wordmaps, adding them into the tree wordmapping_data *word; for(word=words; word; word=word->next){ add_wordmap_to_tree(word->from, word->to); } // dump_tree(); // fgetc(stdin); } /**************************************************************************/ // reinitialise the tree from a loaded wordlist // deallocating the existing tree first // used by langedit delword. void language_data::reinitialise_tree() { int i; // deallocate all nodes coming off the existing tree array for(i=0; i<256; i++){ if(tree[i]){ delete tree[i]; } tree[i]=NULL; } // we have an empty tree array, readd everything wordmapping_data *word; // loop thru all wordmaps, adding them into the tree for(word=words; word; word=word->next){ add_wordmap_to_tree(word->from, word->to); } } /**************************************************************************/ languagetree_data * languagetree_data::find_node_with_character(char lookfor) { if(!this){ // if we are NULL, return NULL return NULL; } // check if we hold the matching character, return ourselves if(lookfor==character){ return this; } // otherwise recursively ask the across node if they match return across->find_node_with_character(lookfor); } /**************************************************************************/ languagetree_data::languagetree_data(languagetree_data *theparent, char *characters, char *data) { // this is a new node, fill out the data character=LOWER(*characters); across=NULL; parent=theparent; stored_word=NULL; // check if there are more characters which follow, if so work recursively if(*(characters+1)){ // because we know this is a new node, we just create more down the chain down = new languagetree_data(this, characters+1, data); }else{ // we are a leaf node stored_word=str_dup(data); down=NULL; } } /**************************************************************************/ languagetree_data::~languagetree_data() { if(down){ delete down; } if(across){ delete across; } } /**************************************************************************/ void languagetree_data::add(languagetree_data *theparent, char *characters, char *data) { // - we are adding to something that the start of already exists, // - we first must trace our path down what has already been created, when // it differs from the character sequence we have to start creating // - if we reach the end of our characters, but we are in an existing node // we have to populate the stored_word... an example of this would be // if we had the word 'action' already in the tree, then the word 'act' // was added. // first confirm the character which our node represents is us if(character == LOWER(*characters)){ // we are the node responsible for storing this data // decide if we want to hand it to a downstream node, or this is // the end of the characters and we need to update or storage if(*(characters+1)){ // there are more characters to follow // first check if we have a downstream if(down){ // downstream exists, chop off the front character and pass it down down->add(this, characters+1, data); }else{ // no downstream, use the constructor to create the required subtree down = new languagetree_data(this, characters+1, data); } }else{ // we are the last in the character sequence, store the data here stored_word=str_dup(data); } }else{ // the character sequence isn't actually for us, so find the possible peer and add it languagetree_data *peernode=find_node_with_character(*characters); if(peernode){ // we have found a peer, hand the data over to the peer and let it handle its downstream creation peernode->add(theparent, characters, data); }else{ // there is no node using this value, so use the constructor to create the chain // then we merge the subtree of nodes into our across chain peernode=new languagetree_data(theparent, characters, data); // chain it into the across chain peernode->across=across; across=peernode; } } } /**************************************************************************/ // - if we are the matching node for this letter, // check recursively if below us matches the next letter and has a payload. // - if below matches and has a payload, it will return a non 0 result for // the number of characters lower that have been matched. // we then return the resulting number + 1 to above. // - if it doesn't it we check if we have a payload, if we do, we set the // the tail to ourself and return a 1. // - if we aren't the matching node, check for a matching node across, // if none found return a 0, if one is found call that node with our // parameters and return its result. int languagetree_data::find(const char *characters, languagetree_data **tail) { if(!this){ // we don't exist so return 0 return 0; } int result; // first confirm the character which our node represents is us if(character == LOWER(*characters)){ // we are the node responsible containing this data // try passing it downstream if possible if(down && *(characters+1)){ result=down->find(characters+1, tail); if(result){ return result+1; // +1 because we absorbed a character in the process } } // downstream didn't match // since we match, check if we have a stored_word payload if(!IS_NULLSTR(stored_word)){ // we have a stored_word *tail=this; return 1; } // we don't match for our payload, so return 0, and the upper layers can match return 0; }else{ // the character sequence isn't actually for us, so find the possible peer and add it languagetree_data *peernode=find_node_with_character(*characters); if(peernode){ // we have found a peer, hand the data over to the peer and return its result return peernode->find(characters, tail); }else{ // there is no node using this value, so we return a 0. return 0; } } } /**************************************************************************/ // Diagnostic code originally written to confirm the tree creation // algorthim was correct... the creation algorithms was actually correct // first compile but the display algorithm took a few tweaks to get right // - goes to show commenting code as you go along is a good thing. // - Kal, March 02. void languagetree_data::dump_tree(int depth) { logf("%c", character); if(!IS_NULLSTR(stored_word)){ logf("%*c -> '%s'", 15-depth, ' ', stored_word); }else if(!down && !across){ logf("%*c -> ''", 15-depth, ' '); } if(down){ if(!IS_NULLSTR(stored_word)){ logf("\n%*c", depth+1, ' '); } down->dump_tree(depth+1); } if(across){ logf("\n%*c", depth, ' '); across->dump_tree(depth); } } /**************************************************************************/ // move thru the subtree, adding all word mappings into the wordlist of the language data void languagetree_data::add_subtree_to_wordlist(int depth, char *working, language_data *ld) { assertp(ld); working[depth]=character; working[depth+1]='\0'; if(!IS_NULLSTR(stored_word)){ ld->words_add_word_mapping( working, stored_word); // logf("%-15s -> '%s'\n", working, stored_word); }else if(!down && !across){ ld->words_add_word_mapping( working, stored_word); // logf("%-15s -> '%s'\n", working, stored_word); } if(down){ down->add_subtree_to_wordlist(depth+1, working, ld); } if(across){ across->add_subtree_to_wordlist(depth, working, ld); } } /**************************************************************************/ // do a tree dump for each variable void language_data::dump_tree() { logf("===%s", name); for(int i=0; i<256; i++){ if(tree[i]){ logf("\n"); tree[i]->dump_tree(0); logf("\n"); } } } /**************************************************************************/ // # code used to create the language text files from // # the original hardcoded language table. // # left here because someone may find it useful when upgrading. // creates a linked list structure representing the language table // then save it to disk using GIO // - Kal, March 03 /* GIO_PROTOTYPE(language_data) GIO_PROTOTYPE(wordmapping_data) void language_convert_from_table() { int i; int lang; language_data *language; language_data *languages=NULL; wordmapping_data *word; // create the link list structures storing all table based language information // #define MAX_LANGUAGE (22) for(lang = 0; lang < MAX_LANGUAGE; lang++){ language=new language_data; language->name=str_dup(language_table[lang]); language->words=NULL; for(i=0; !IS_NULLSTR(trans_table[lang][i].old); i++){ word = new wordmapping_data; word->from=str_dup(trans_table[lang][i].old); word->to=str_dup(trans_table[lang][i].nw); word->next=language->words; language->words=word; } language->next=languages; languages=language; } // write the link lists to disk GIOSAVE_LIST(languages, language_data, LANGUAGES_INDEX_FILE, true); for(language=languages; language; language=language->next){ logf("======%10s\n", language->name); for(word=language->words; word; word=word->next){ // logf("%20s -> %s\n", word->from, word->to); } GIOSAVE_LIST(language->words, wordmapping_data, FORMATF(LANGUAGES_DIR "%s.txt", language->name), true); } } */ /**************************************************************************/ language_data *language_exact_lookup(const char *name) { language_data *result; if(IS_NULLSTR(name)){ log_string("BUG: language_lookup: was past a NULL string!"); return NULL; } // loop thru the list of languages for(result=languages; result; result=result->next){ if(!str_cmp(name,result->name)){ return result; } } return NULL; } /**************************************************************************/ language_data *language_lookup(const char *name) { language_data *result; if(IS_NULLSTR(name)){ log_string("BUG: language_lookup: was past a NULL string!"); return NULL; } // try an exact match first result=language_exact_lookup(name); if(result){ return result; } // now try a prefix match for(result=languages; result; result=result->next){ if(!str_prefix(name,result->name)){ return result; } } return NULL; } /**************************************************************************/ // will return "unknown" language if it can't find a language with the name requested language_data *language_safe_lookup(const char *name) { language_data *result=language_lookup(name); if(!result){ result=language_lookup("unknown"); assertp(result); // there should always be an unknown language } return result; } /**************************************************************************/ language_data *language_lookup_by_id(int id) { language_data *result; // loop thru the list of languages for(result=languages; result; result=result->next){ if(result->unique_id==id){ return result; } } return NULL; } /**************************************************************************/ // will return "unknown" language if it can't find a language with the id requested language_data *language_safe_lookup_by_id(int id) { language_data *result=language_lookup_by_id(id); if(!result){ result=language_lookup("unknown"); assertp(result); // there should always be an unknown language } return result; } /**************************************************************************/ // by Imi - reverses the string char * strreverse(char * string) { int low = 0; int high = 0 ; char lowc; high = ( str_len(string) - 1); while(low <= high) { lowc = string[low]; string[low] = string[high]; string[high] = lowc; low++; high--; } return string; } /**************************************************************************/ void translate_language(language_data *language, bool display_language, char_data * speaker, char_data * listener, const char *message, char *output) { bool unconditionally_understood=false; char buf [MSL]; char buf2 [MSL]; if(GAMESETTING3(GAMESET3_LANGUAGE_NOT_SCRAMBLED)){ // if language is disabled, just copy everything straight across strcpy(output,message); return; } int ss, ls; // skills for the speaker and listener if(language->gsn<0){ // if there is no skill, at this stage they get it at 100% ss=100; ls=100; }else{ // get the skills of each char in the language + 10% ss=get_skill(TRUE_CH(speaker),language->gsn)+10; ls=get_skill(TRUE_CH(listener),language->gsn)+10; } // 100% ability in the language for non controlled mobs if (IS_NPC(speaker) && !IS_CONTROLLED(speaker)){ ss=100; } // support speaking in a players native language if(language==language_native){ unconditionally_understood=true; language=race_table[listener->race]->language; if(!language){ language=language_alwaysunderstood; unconditionally_understood=true; } } if(// checks for straight translation ( // holyspeech, unless disabled for the given language !IS_SET(language->flags,LANGFLAG_NO_HOLYSPEECH) && (HAS_HOLYSPEECH(speaker) || HAS_HOLYSPEECH(listener)) ) || ( // no scramble flag for language IS_SET(language->flags,LANGFLAG_NO_SCRAMBLE) ) || ( // ooc rooms default to no language scrambling IS_OOC(listener) && !IS_SET(language->flags,LANGFLAG_SCRAMBLE_IN_OOC) ) || ( // cases like native and alwaysunderstood unconditionally_understood ) ) { strcpy(buf, message); }else{ // do some language scrambling buf[0]='\0'; char dcb[2]={'\0','\0'}; // direct copy buffer int length; char *payload; bool force_upper; languagetree_data *tail; for(const char *pstr=message; *pstr; pstr+=length){ // find a matching word in the language table length=language->find(pstr, &tail); if(length){ payload=tail->stored_word; force_upper=length>1?false:IS_UPPER(*pstr); // decide if the listener understood if( number_percent()>ss || number_percent()>ls ){ // not understood, copy translated strcat(buf, force_upper?uppercase(payload): payload); }else{ strncat(buf, force_upper?uppercase(pstr): pstr, length); } }else{ // handle non matches length=1; // copy the character not in the tables straight across dcb[0]=*pstr; strcat( buf, dcb); // if colour code '`' then copy the following character also if(*pstr=='`'){ dcb[0]=*(pstr+1); strcat( buf, dcb); length++; // check for custom colour code if(*(pstr+1)=='='){ dcb[0]=*(pstr+2); strcat( buf, dcb); length++; } } } } } // apply the reverse - even in ooc if(IS_SET(language->flags,LANGFLAG_REVERSE_TEXT) && !unconditionally_understood && (number_percent()>ss || number_percent()>ls)){ strreverse(buf); } // if they recognise the language - not in ooc rooms if( display_language && !IS_SET(language->flags,LANGFLAG_NO_LANGUAGE_NAME) && !IS_OOC(listener) && get_skill(listener,language->gsn) > 11) { sprintf(buf2,"`#`M(%s)`&%s", language->name, buf); }else{ strcpy(buf2,buf); } // load up output buffer strcpy(output,buf2); // do improves on language - player to player only if( number_range(1,50) < str_len(buf2) && !IS_NPC(speaker) && !IS_NPC(listener) && IS_IC(speaker)) { check_improve(speaker,language->gsn,true,4); check_improve(listener,language->gsn,true,4); } } /**************************************************************************/ void do_language(char_data *ch, char *argument) { language_data *language; char arg[MIL]; one_argument( argument, arg ); if( IS_NULLSTR( arg )) { ch->printlnf("You're currently speaking in %s.", ch->language->name); return; } // look up the language language = language_lookup(arg); if( !language || IS_SET(language->flags, LANGFLAG_IMMONLY) && !IS_IMMORTAL(ch) ){ ch->printlnf("No such tongue '%s' exists.", arg); return; } // spirit walking players can't use unlimited languages if (!IS_NPC(ch) || ( IS_CONTROLLED(ch) && get_trust(ch) < LEVEL_IMMORTAL )) { if(IS_SET(language->flags, LANGFLAG_SYSTEM_LANGUAGE)){ if(!IS_IMMORTAL(ch)){ ch->printlnf("No such tongue '%s' exists.", arg); return; } }else{ if ( !IS_SET(language->flags, LANGFLAG_NO_SKILL_REQUIRED) && language->gsn>=0 && get_skill(TRUE_CH(ch), language->gsn)<1) { ch->printlnf("You have no knowledge of any %s words.", language->name); return; } } } if( IS_SET( ch->dyn, DYN_IS_BEING_ORDERED) && IS_SET(language->flags, LANGFLAG_NO_ORDER)) { ch->printlnf("You can't be ordered to speak in '%s'.", language->name); return; } ch->language=language; ch->printlnf("Ok, you will now speak in '%s' by default.", language->name); } /**************************************************************************/ // return true if language matched bool language_dynamic_command(char_data *ch, char *command, char *argument) { language_data *language; if(IS_NULLSTR(command) || (command[0]=='-' && command[1]=='\0')){ return false; } // try an exact match of command first for(language=languages; language; language=language->next){ if(IS_SET(language->flags, LANGFLAG_IMMONLY) && !IS_IMMORTAL(ch) ){ continue; } if(IS_SET(language->flags, LANGFLAG_NO_COMMAND_ACCESS)){ continue; } if(!IS_NULLSTR(language->commandname) && !str_cmp(command,language->commandname)){ break; } } if(!language){ // try a prefix match if the exact match failed for(language=languages; language; language=language->next){ if(IS_SET(language->flags, LANGFLAG_IMMONLY) && !IS_IMMORTAL(ch) ){ continue; } if(IS_SET(language->flags, LANGFLAG_NO_COMMAND_ACCESS)){ continue; } if(!IS_NULLSTR(language->commandname) && !str_prefix(command,language->commandname)){ break; } } } if(!language){ // the command isn't a language return false; } saymote( language, ch, argument, 0); return true; } /**************************************************************************/ /**************************************************************************/