/**************************************************************************/
// 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;
}
/**************************************************************************/
/**************************************************************************/