From ebe3faa645862641354b8bddf6c52dccc108a16c Mon Sep 17 00:00:00 2001 From: j-p-e-g Date: Tue, 4 Sep 2007 11:32:42 +0000 Subject: Implementing patch 1775415 (outsourcing monster speech) by zelgadis. Currently, shout.txt and speak.txt share in with the .des files in /dat. That should be changed, but I've no idea how to do this. Also implementing a bug fix by ennewalker (1787428). git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@2052 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/command.cc | 9 +- crawl-ref/source/database.cc | 357 +++++- crawl-ref/source/database.h | 3 + crawl-ref/source/debug.cc | 142 ++- crawl-ref/source/debug.h | 3 + crawl-ref/source/direct.cc | 18 + crawl-ref/source/enum.h | 40 +- crawl-ref/source/insult.cc | 82 +- crawl-ref/source/insult.h | 3 + crawl-ref/source/mon-data.h | 10 +- crawl-ref/source/mon-util.cc | 397 +++++- crawl-ref/source/mon-util.h | 35 + crawl-ref/source/monplace.cc | 10 +- crawl-ref/source/monspeak.cc | 2783 +++++------------------------------------- crawl-ref/source/monstuff.cc | 25 +- crawl-ref/source/view.cc | 273 +++-- 16 files changed, 1480 insertions(+), 2710 deletions(-) (limited to 'crawl-ref') diff --git a/crawl-ref/source/command.cc b/crawl-ref/source/command.cc index 3acf614088..167f93c0c3 100644 --- a/crawl-ref/source/command.cc +++ b/crawl-ref/source/command.cc @@ -535,7 +535,14 @@ static const char *targeting_help_1 = "! : fire at target and stop there (may hit submerged creatures)\n" "p : fire at Previous target (also t, f)\n" ": : show/hide beam path\n" - "Shift-Dir : shoot straight-line beam\n"; + "Shift-Dir : shoot straight-line beam\n" +#ifdef WIZARD + " \n" + "Wizard targeting comands:\n" + "F: make target friendly\n" + "s: force target to shout or speak\n" +#endif +; static const char *targeting_help_2 = "Firing or throwing a missile:\n" diff --git a/crawl-ref/source/database.cc b/crawl-ref/source/database.cc index bf9c4fad14..939ffaa35d 100644 --- a/crawl-ref/source/database.cc +++ b/crawl-ref/source/database.cc @@ -25,12 +25,26 @@ db_list openDBList; DBM *descriptionDB; +DBM *shoutDB; +DBM *speakDB; + #define DESC_BASE_NAME "descript" #define DESC_TXT_DIR "descript" #define DESC_DB (DESC_BASE_NAME ".db") +#define SHOUT_BASE_NAME "shout" +#define SHOUT_TXT (SHOUT_BASE_NAME ".txt") +#define SHOUT_DB (SHOUT_BASE_NAME ".db") + +#define SPEAK_BASE_NAME "speak" +#define SPEAK_TXT (SPEAK_BASE_NAME ".txt") +#define SPEAK_DB (SPEAK_BASE_NAME ".db") + + static std::vector description_txt_paths(); static void generate_description_db(); +static void generate_shout_db(); +static void generate_speak_db(); void databaseSystemInit() { @@ -52,6 +66,30 @@ void databaseSystemInit() if (!(descriptionDB = openDB(descriptionPath.c_str()))) end(1, true, "Failed to open DB: %s", descriptionPath.c_str()); } + + if (!shoutDB) + { + std::string shoutPath = get_savedir_path(SHOUT_DB); + std::string shoutText = datafile_path(SHOUT_TXT); + + check_newer(shoutPath, shoutText, generate_shout_db); + + shoutPath.erase(shoutPath.length() - 3); + if (!(shoutDB = openDB(shoutPath.c_str()))) + end(1, true, "Failed to open DB: %s", shoutPath.c_str()); + } + if (!speakDB) + { + std::string speakPath = get_savedir_path(SPEAK_DB); + std::string speakText = datafile_path(SPEAK_TXT); + + check_newer(speakPath, speakText, generate_speak_db); + + speakPath.erase(speakPath.length() - 3); + if (!(speakDB = openDB(speakPath.c_str()))) + end(1, true, "Failed to open DB: %s", speakPath.c_str()); + } + } void databaseSystemShutdown() @@ -65,6 +103,9 @@ void databaseSystemShutdown() descriptionDB = NULL; } +//////////////////////////////////////////////////////////////////////////// +// Main DB functions + // This is here, and is external, just for future expansion -- if we // want to allow external modules to manage their own DB, they can // use this for the sake of convenience. It's arguable that it's @@ -95,63 +136,8 @@ datum database_fetch(DBM *database, const std::string &key) return result; } -std::string getLongDescription(const std::string &key) -{ - if (!descriptionDB) - return (""); - - // We have to canonicalize the key (in case the user typed it - // in and got the case wrong.) - std::string canonical_key = key; - lowercase(canonical_key); - - // Query the DB. - datum result = database_fetch(descriptionDB, canonical_key); - - // Cons up a (C++) string to return. The caller must release it. - return std::string((const char *)result.dptr, result.dsize); -} - -static std::vector description_txt_paths() -{ - std::vector txt_file_names; - std::vector paths; - - txt_file_names.push_back("features"); - txt_file_names.push_back("items"); - txt_file_names.push_back("monsters"); - txt_file_names.push_back("spells"); - - for (int i = 0, size = txt_file_names.size(); i < size; i++) - { - std::string name = DESC_TXT_DIR; - name += FILE_SEPARATOR; - name += txt_file_names[i]; - name += ".txt"; - - std::string txt_path = datafile_path(name); - paths.push_back(txt_path); - } - - return (paths); -} - -static void store_descriptions(const std::string &in, const std::string &out); -static void generate_description_db() -{ - std::string db_path = get_savedir_path(DESC_BASE_NAME); - std::string full_db_path = get_savedir_path(DESC_DB); - - std::vector txt_paths = description_txt_paths(); - - file_lock lock(get_savedir_path(DESC_BASE_NAME ".lk"), "wb"); - unlink( full_db_path.c_str() ); - - for (int i = 0, size = txt_paths.size(); i < size; i++) - store_descriptions(txt_paths[i], db_path); - DO_CHMOD_PRIVATE(full_db_path.c_str()); -} - +/////////////////////////////////////////////////////////////////////////// +// Internal DB utility functions static void trim_right(std::string &s) { s.erase(s.find_last_not_of(" \r\t\n") + 1); @@ -176,7 +162,7 @@ static void add_entry(DBM *db, const std::string &k, std::string &v) end(1, true, "Error storing %s", k.c_str()); } -static void parse_descriptions(std::ifstream &inf, DBM *db) +static void parse_text_db(std::ifstream &inf, DBM *db) { char buf[1000]; @@ -222,7 +208,7 @@ static void parse_descriptions(std::ifstream &inf, DBM *db) add_entry(db, key, value); } -static void store_descriptions(const std::string &in, const std::string &out) +static void store_text_db(const std::string &in, const std::string &out) { std::ifstream inf(in.c_str()); if (!inf) @@ -230,7 +216,7 @@ static void store_descriptions(const std::string &in, const std::string &out) if (DBM *db = dbm_open(out.c_str(), O_RDWR | O_CREAT, 0660)) { - parse_descriptions(inf, db); + parse_text_db(inf, db); dbm_close(db); } else @@ -238,3 +224,254 @@ static void store_descriptions(const std::string &in, const std::string &out) inf.close(); } + +static std::string chooseStrByWeight(std::string entry) +{ + std::vector parts; + std::vector weights; + + std::vector lines = split_string("\n", entry, false, true); + + int total_weight = 0; + for (int i = 0, size = lines.size(); i < size; i++) + { + // Skip over multiple blank lines, and leading and trailing + // blank lines. + while (i < size && lines[i] == "") + i++; + if (i == size) + break; + + int weight; + std::string part = ""; + + if (sscanf(lines[i].c_str(), "w:%d", &weight)) + { + i++; + if (i == size) + return ("BUG, WEIGHT AT END OF ENTRY"); + } + else + weight = 10; + + total_weight += weight; + + while (i < size && lines[i] != "") + { + part += lines[i++]; + part += "\n"; + } + trim_string(part); + + parts.push_back(part); + weights.push_back(total_weight); + } + + if (parts.size() == 0) + return("BUG, EMPTY ENTRY"); + + int choice = random2(total_weight); + std::string str = ""; + + for (int i = 0, size = parts.size(); i < size; i++) + if (choice < weights[i]) + return(parts[i]); + + return("BUG, NO STRING CHOSEN"); +} + +#define MAX_RECURSION_DEPTH 10 +#define MAX_REPLACEMENTS 100 + +static std::string getRandomizedStr(DBM *database, const std::string &key, + const std::string &suffix, + int &num_replacements, + int recursion_depth = 0) +{ + recursion_depth++; + if (recursion_depth > MAX_RECURSION_DEPTH) + { + mpr("Too many nested replacements, bailing.", MSGCH_DIAGNOSTICS); + + return "TOO MUCH RECURSION"; + } + + // We have to canonicalize the key (in case the user typed it + // in and got the case wrong.) + std::string canonical_key = key + suffix; + lowercase(canonical_key); + + // Query the DB. + datum result = database_fetch(database, canonical_key); + + if (result.dsize <= 0) + { + // Try ignoring the suffix + canonical_key = key; + lowercase(canonical_key); + + // Query the DB. + result = database_fetch(database, canonical_key); + + if (result.dsize <= 0) + return ""; + } + + // Cons up a (C++) string to return. The caller must release it. + std::string str = std::string((const char *)result.dptr, result.dsize); + + str = chooseStrByWeight(str); + + // Replace any "@foo@" markers that can be found in this database; + // those that can't be found are left alone for the caller to deal + // with. + std::string::size_type pos = str.find("@"); + while (pos != std::string::npos) + { + num_replacements++; + if (num_replacements > MAX_REPLACEMENTS) + { + mpr("Too many string replacements, bailing.", MSGCH_DIAGNOSTICS); + + return "TOO MANY REPLACEMENTS"; + } + + std::string::size_type end = str.find("@", pos + 1); + if (end == std::string::npos) + { + mpr("Unbalanced @, bailing.", MSGCH_DIAGNOSTICS); + break; + } + + std::string marker_full = str.substr(pos, end - pos + 1); + std::string marker = str.substr(pos + 1, end - pos - 1); + + std::string replacement = + getRandomizedStr(database, marker, suffix, num_replacements, + recursion_depth); + + if (replacement == "") + // Nothing in database, leave it alone and go onto next @foo@ + pos = str.find("@", end + 1); + else + { + str.replace(pos, marker_full.length(), replacement); + + // Start search from pos rather than end + 1, so that if + // the replacement contains its own @foo@, we can replace + // that too. + pos = str.find("@", pos); + } + } // while (pos != std::string::npos) + + return str; +} + +///////////////////////////////////////////////////////////////////////////// +// Description DB specific functions. + +std::string getLongDescription(const std::string &key) +{ + if (!descriptionDB) + return (""); + + // We have to canonicalize the key (in case the user typed it + // in and got the case wrong.) + std::string canonical_key = key; + lowercase(canonical_key); + + // Query the DB. + datum result = database_fetch(descriptionDB, canonical_key); + + // Cons up a (C++) string to return. The caller must release it. + return std::string((const char *)result.dptr, result.dsize); +} + +static std::vector description_txt_paths() +{ + std::vector txt_file_names; + std::vector paths; + + txt_file_names.push_back("features"); + txt_file_names.push_back("items"); + txt_file_names.push_back("monsters"); + txt_file_names.push_back("spells"); +/* + txt_file_names.push_back("shout"); + txt_file_names.push_back("speak"); +*/ + for (int i = 0, size = txt_file_names.size(); i < size; i++) + { + std::string name = DESC_TXT_DIR; + name += FILE_SEPARATOR; + name += txt_file_names[i]; + name += ".txt"; + + std::string txt_path = datafile_path(name); + + if (!txt_path.empty()) + paths.push_back(txt_path); + } + + return (paths); +} + +static void generate_description_db() +{ + std::string db_path = get_savedir_path(DESC_BASE_NAME); + std::string full_db_path = get_savedir_path(DESC_DB); + + std::vector txt_paths = description_txt_paths(); + + file_lock lock(get_savedir_path(DESC_BASE_NAME ".lk"), "wb"); + unlink( full_db_path.c_str() ); + + for (int i = 0, size = txt_paths.size(); i < size; i++) + store_text_db(txt_paths[i], db_path); + DO_CHMOD_PRIVATE(full_db_path.c_str()); +} + +///////////////////////////////////////////////////////////////////////////// +// Shout DB specific functions. +std::string getShoutString(const std::string &monst, + const std::string &suffix) +{ + int num_replacements = 0; + + return getRandomizedStr(shoutDB, monst, suffix, num_replacements); +} + +static void generate_shout_db() +{ + std::string db_path = get_savedir_path(SHOUT_BASE_NAME); + std::string full_db_path = get_savedir_path(SHOUT_DB); + std::string txt_path = datafile_path(SHOUT_TXT); + + file_lock lock(get_savedir_path(SHOUT_BASE_NAME ".lk"), "wb"); + unlink( full_db_path.c_str() ); + + store_text_db(txt_path, db_path); + DO_CHMOD_PRIVATE(full_db_path.c_str()); +} + +///////////////////////////////////////////////////////////////////////////// +// Speak DB specific functions. +std::string getSpeakString(const std::string &monst) +{ + int num_replacements = 0; + + return getRandomizedStr(speakDB, monst, "", num_replacements); +} + +static void generate_speak_db() +{ + std::string db_path = get_savedir_path(SPEAK_BASE_NAME); + std::string full_db_path = get_savedir_path(SPEAK_DB); + std::string txt_path = datafile_path(SPEAK_TXT); + + file_lock lock(get_savedir_path(SPEAK_BASE_NAME ".lk"), "wb"); + unlink( full_db_path.c_str() ); + + store_text_db(txt_path, db_path); + DO_CHMOD_PRIVATE(full_db_path.c_str()); +} diff --git a/crawl-ref/source/database.h b/crawl-ref/source/database.h index 5cd0923ebc..e1cb3f230e 100644 --- a/crawl-ref/source/database.h +++ b/crawl-ref/source/database.h @@ -42,4 +42,7 @@ datum database_fetch(DBM *database, const std::string &key); std::string getLongDescription(const std::string &key); +std::string getShoutString(const std::string &monst, + const std::string &suffix = ""); +std::string getSpeakString(const std::string &monst); #endif diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc index a8f3c5c8ae..5edb16a701 100644 --- a/crawl-ref/source/debug.cc +++ b/crawl-ref/source/debug.cc @@ -51,11 +51,17 @@ #include "itemprop.h" #include "item_use.h" #include "items.h" + +#ifdef WIZARD +#include "macro.h" +#endif + #include "makeitem.h" #include "mapdef.h" #include "maps.h" #include "misc.h" #include "monplace.h" +#include "monspeak.h" #include "monstuff.h" #include "mon-util.h" #include "mutation.h" @@ -402,6 +408,31 @@ void create_spec_monster_name(int x, int y) const mons_spec mspec = mlist.get_monster(0); if (!force_place && mspec.mid != -1) { + // Only one ghost allowed per level + if (mspec.mid == MONS_PLAYER_GHOST) + { + for (int i = 0; i < MAX_MONSTERS; i++) + if (menv[i].type == MONS_PLAYER_GHOST + && menv[i].alive()) + { + mpr("Only one player ghost per level allowed, " + "and this level already has one."); + return; + } + } + // Only one pandemonium lord allowed per level as well. + else if (mspec.mid == MONS_PANDEMONIUM_DEMON) + { + for (int i = 0; i < MAX_MONSTERS; i++) + if (menv[i].type == MONS_PANDEMONIUM_DEMON + && menv[i].alive()) + { + mpr("Only one Pandemonium lord per level allowed, " + "and this level already has one."); + return; + } + } + coord_def place = find_newmons_square(mspec.mid, x, y); if (in_bounds(place)) { @@ -410,7 +441,58 @@ void create_spec_monster_name(int x, int y) } } - dgn_place_monster(mspec, you.your_level, x, y, false); + if (!dgn_place_monster(mspec, you.your_level, x, y, false)) + { + mpr("Unable to place monster"); + return; + } + + // Need to set a name for the player ghost + if (mspec.mid == MONS_PLAYER_GHOST) + { + unsigned char mid = mgrd[x][y]; + + if (mid >= MAX_MONSTERS || menv[mid].type != MONS_PLAYER_GHOST) + { + for (mid = 0; mid < MAX_MONSTERS; mid++) + if (menv[mid].type == MONS_PLAYER_GHOST + && menv[mid].alive()) + break; + } + + if (mid >= MAX_MONSTERS) + { + mpr("Couldn't find player ghost, probably going to crash."); + more(); + return; + } + + monsters &mon = menv[mid]; + ghost_demon ghost; + + ghost.name = "John Doe"; + ghost.values.init(0); + + char class_str[80]; + mpr( "Make player ghost which class? ", MSGCH_PROMPT ); + get_input_line( class_str, sizeof( class_str ) ); + + int class_id = get_class_index_by_abbrev(class_str); + + if (class_id == -1) + class_id = get_class_index_by_name(class_str); + + if (class_id == -1) + { + mpr("No such class, making it a Fighter."); + class_id = JOB_FIGHTER; + } + ghost.values[GVAL_CLASS] = class_id; + + mon.set_ghost(ghost); + + ghosts.push_back(ghost); + } } #endif @@ -2464,6 +2546,64 @@ void debug_test_explore() #endif +#ifdef WIZARD +extern void force_monster_shout(monsters* monster); + +void debug_make_monster_shout(monsters* mon) +{ + + mpr("Make the monster (S)hout or (T)alk?", MSGCH_PROMPT); + + char type = (char) getchm(KC_DEFAULT); + type = tolower(type); + + if (type != 's' && type != 't') + { + canned_msg( MSG_OK ); + return; + } + + int num_times = debug_prompt_for_int("How many times? ", false); + + if (num_times <= 0) + { + canned_msg( MSG_OK ); + return; + } + + if (type == 's') + { + if (silenced(mon->x, mon->y)) + mpr("The monster is silenced and likely won't give any shouts."); + if (silenced(you.x_pos, you.y_pos)) + mpr("You are silenced and likely won't hear any shouts."); + + for (int i = 0; i < num_times; i++) + force_monster_shout(mon); + } + else + { + if (mon->invisible()) + mpr("The monster is invisble and likely won't speak."); + + if (silenced(you.x_pos, you.y_pos) && !silenced(mon->x, mon->y)) + mpr("You are silenced but the monster isn't; you will " + "probably hear/see nothing."); + else if (!silenced(you.x_pos, you.y_pos) && silenced(mon->x, mon->y)) + mpr("The monster is silenced and likely won't say anything."); + else if (silenced(you.x_pos, you.y_pos) && silenced(mon->x, mon->y)) + mpr("Both you and the monster are silenced, so you likely " + "won't hear anything."); + + for (int i = 0; i< num_times; i++) + mons_speaks(mon); + } + + mpr("== Done =="); +} +#endif + + #ifdef DEBUG_DIAGNOSTICS // Map statistics generation. diff --git a/crawl-ref/source/debug.h b/crawl-ref/source/debug.h index e3bdad8ed2..6dd314f592 100644 --- a/crawl-ref/source/debug.h +++ b/crawl-ref/source/debug.h @@ -158,6 +158,9 @@ void debug_place_map(); void debug_test_explore(); void debug_dismiss_all_monsters(); +class monsters; +void debug_make_monster_shout(monsters* mon); + #ifdef DEBUG_DIAGNOSTICS void generate_map_stats(); class map_def; diff --git a/crawl-ref/source/direct.cc b/crawl-ref/source/direct.cc index c3ea182be1..4d6515b94a 100644 --- a/crawl-ref/source/direct.cc +++ b/crawl-ref/source/direct.cc @@ -582,9 +582,26 @@ void direction(dist& moves, targeting_type restricts, m.attitude = m.attitude == ATT_FRIENDLY? ATT_NEUTRAL : m.attitude == ATT_HOSTILE? ATT_FRIENDLY : ATT_HOSTILE; + + // To update visual branding of friendlies. Only + // seem capabable of adding bolding, not removing it, + // though. + viewwindow(true, false); } break; + + case CMD_TARGET_WIZARD_MAKE_SHOUT: + // Maybe we can skip this check...but it can't hurt + if (!you.wizard || !in_bounds(moves.tx, moves.ty)) + break; + mid = mgrd[moves.tx][moves.ty]; + if (mid == NON_MONSTER) // can put in terrain description here + break; + + debug_make_monster_shout(&menv[mid]); + break; #endif + case CMD_TARGET_DESCRIBE: full_describe_square(moves.target()); @@ -1887,6 +1904,7 @@ command_type targeting_behaviour::get_command(int key) #ifdef WIZARD case 'F': return CMD_TARGET_WIZARD_MAKE_FRIENDLY; + case 's': return CMD_TARGET_WIZARD_MAKE_SHOUT; #endif case 'v': return CMD_TARGET_DESCRIBE; case '?': return CMD_TARGET_HELP; diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 6b1c045bf2..a11550c525 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -757,6 +757,7 @@ enum command_type CMD_TARGET_FIND_YOU, CMD_TARGET_DESCRIBE, CMD_TARGET_WIZARD_MAKE_FRIENDLY, + CMD_TARGET_WIZARD_MAKE_SHOUT, CMD_TARGET_HELP, // Disable/enable -more- prompts. @@ -2492,23 +2493,24 @@ enum mon_flight_type // These are now saved in an unsigned long in the monsters struct. enum monster_flag_type { - MF_CREATED_FRIENDLY = 0x01, // no benefit from killing - MF_GOD_GIFT = 0x02, // player not penalized by its death - MF_BATTY = 0x04, // flutters like a bat - MF_JUST_SUMMONED = 0x08, // monster skips next available action - MF_TAKING_STAIRS = 0x10, // is following player through stairs + MF_CREATED_FRIENDLY = 0x01, // no benefit from killing + MF_GOD_GIFT = 0x02, // player not penalized by its death + MF_BATTY = 0x04, // flutters like a bat + MF_JUST_SUMMONED = 0x08, // monster skips next available action + MF_TAKING_STAIRS = 0x10, // is following player through stairs - MF_INTERESTING = 0x20, // Player finds monster interesting - MF_SEEN = 0x40, // Player already seen monster - MF_DIVINE_PROTECTION = 0x80, // Monster has divine protection. + MF_INTERESTING = 0x20, // Player finds monster interesting + MF_SEEN = 0x40, // Player already seen monster + MF_DIVINE_PROTECTION = 0x80, // Monster has divine protection. - MF_KNOWN_MIMIC = 0x100, // Mimic that has taken a swing at the PC, - // or that the player has inspected with ? - MF_BANISHED = 0x200, // Monster that has been banished. - MF_HARD_RESET = 0x400, // Summoned, should not drop gear on reset - MF_CONVERT_ATTEMPT = 0x800, // Orcs only: seen player and was converted - // (or not) - MF_WAS_IN_VIEW = 0x1000 // Was in view during previous turn + MF_KNOWN_MIMIC = 0x100, // Mimic that has taken a swing at the PC, + // or that the player has inspected with ? + MF_BANISHED = 0x200, // Monster that has been banished. + MF_HARD_RESET = 0x400, // Summoned, should not drop gear on reset + MF_CONVERT_ATTEMPT = 0x800, // Orcs only: seen player and was converted + // (or not) + MF_WAS_IN_VIEW = 0x1000, // Was in view during previous turn + MF_BAND_MEMBER = 0x2000 // Created as a member of a band }; enum mon_dam_level_type @@ -3109,6 +3111,14 @@ enum shout_type S_CROAK, // frog croak S_GROWL, // for bears S_HISS, // for snakes and lizards + + // Loudness setting for shouts that are only defined in dat/shout.txt + S_VERY_SOFT, + S_SOFT, + S_NORMAL, + S_LOUD, + S_VERY_LOUD, + NUM_SHOUTS, S_RANDOM }; diff --git a/crawl-ref/source/insult.cc b/crawl-ref/source/insult.cc index 160ef04a5e..24bbd93f0b 100644 --- a/crawl-ref/source/insult.cc +++ b/crawl-ref/source/insult.cc @@ -38,11 +38,9 @@ void init_cap(char * str) str[0] = toupper( str[0] ); } -void imp_taunt( const monsters *mons ) +std::string imp_taunt_str() { char buff[80]; - const std::string mon_name = mons->name(DESC_CAP_THE); - snprintf( buff, sizeof(buff), "%s, thou %s!", random2(7) ? run_away() : give_up(), @@ -50,41 +48,31 @@ void imp_taunt( const monsters *mons ) init_cap( buff ); + return (buff); +} + +void imp_taunt( const monsters *mons ) +{ + std::string str = imp_taunt_str(); + std::string mon_name = mons->name(DESC_CAP_THE); + + // XXX: Not pretty, but stops truncation... - if (mon_name.length() + 11 + strlen( buff ) >= 79) + if (mon_name.length() + 11 + str.length() >= 79) { mprf(MSGCH_TALK, "%s shouts:", mon_name.c_str() ); - mprf(MSGCH_TALK, "%s", buff ); + mprf(MSGCH_TALK, "%s", str.c_str() ); } else { - mprf(MSGCH_TALK, "%s shouts, \"%s\"", mon_name.c_str(), buff ); + mprf(MSGCH_TALK, "%s shouts, \"%s\"", mon_name.c_str(), + str.c_str() ); } } -void demon_taunt( const monsters *mons ) +std::string demon_taunt_str() { - static const char * sound_list[] = - { - "says", // actually S_SILENT - "shouts", - "barks", - "shouts", - "roars", - "screams", - "bellows", - "screeches", - "buzzes", - "moans", - "whines", - "croaks", - "growls", - }; - char buff[80]; - const std::string mon_name = mons->name(DESC_CAP_THE); - const char *voice = sound_list[ mons_shouts(mons->type) ]; - if (coinflip()) { snprintf( buff, sizeof(buff), @@ -126,15 +114,49 @@ void demon_taunt( const monsters *mons ) init_cap( buff ); + return (buff); +} + +void demon_taunt( const monsters *mons ) +{ + std::string str = demon_taunt_str(); + const std::string mon_name = mons->name(DESC_CAP_THE); + + static const char * sound_list[] = + { + "says", // actually S_SILENT + "shouts", + "barks", + "shouts", + "roars", + "screams", + "bellows", + "screeches", + "buzzes", + "moans", + "whines", + "croaks", + "growls", + "hisses", + "breathes", // S_VERY_SOFT + "whispers", // S_SOFT + "says", // S_NORMAL + "shouts", // S_LOUD + "screams" // S_VERY_LOUD + }; + + const char *voice = sound_list[ mons_shouts(mons->type) ]; + // XXX: Not pretty, but stops truncation... - if (mon_name.length() + strlen(voice) + strlen(buff) + 5 >= 79) + if (mon_name.length() + strlen(voice) + str.length() + 5 >= 79) { mprf(MSGCH_TALK, "%s %s:", mon_name.c_str(), voice ); - mprf(MSGCH_TALK, "%s", buff); + mprf(MSGCH_TALK, "%s", str.c_str()); } else { - mprf(MSGCH_TALK, "%s %s, \"%s\"", mon_name.c_str(), voice, buff ); + mprf(MSGCH_TALK, "%s %s, \"%s\"", mon_name.c_str(), voice, + str.c_str() ); } } diff --git a/crawl-ref/source/insult.h b/crawl-ref/source/insult.h index d29b66acb4..adeed139f3 100644 --- a/crawl-ref/source/insult.h +++ b/crawl-ref/source/insult.h @@ -8,4 +8,7 @@ void demon_taunt( const monsters *mons ); const char * generic_insult(void); const char * racial_insult(void); +std::string imp_taunt_str(); +std::string demon_taunt_str(); + #endif diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h index c66b117164..fc70f66c00 100644 --- a/crawl-ref/source/mon-data.h +++ b/crawl-ref/source/mon-data.h @@ -1484,7 +1484,7 @@ { MONS_CRYSTAL_GOLEM, '8', WHITE, "crystal golem", - M_SEE_INVIS, + M_SEE_INVIS | M_SPEAKS, MR_RES_POISON | MR_RES_FIRE | MR_RES_COLD | MR_RES_ELEC, 0, 10, MONS_CLAY_GOLEM, MONS_CRYSTAL_GOLEM, MH_NONLIVING, MAG_IMMUNE, { {AT_HIT, AF_PLAIN, 40}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} }, @@ -3844,7 +3844,7 @@ { MONS_ELECTRIC_GOLEM, '8', LIGHTCYAN, "electric golem", - M_SPELLCASTER | M_SEE_INVIS, + M_SPELLCASTER | M_SEE_INVIS | M_SPEAKS, MR_RES_ELEC | MR_RES_POISON | MR_RES_FIRE | MR_RES_COLD, 0, 10, MONS_CLAY_GOLEM, MONS_ELECTRIC_GOLEM, MH_NONLIVING, -8, { {AT_HIT, AF_ELEC, 15}, {AT_HIT, AF_ELEC, 15}, {AT_HIT, AF_PLAIN, 15}, {AT_HIT, AF_PLAIN, 15} }, @@ -4383,7 +4383,7 @@ { MONS_ORANGE_STATUE, '8', LIGHTRED, "orange crystal statue", - M_SPECIAL_ABILITY, + M_SPECIAL_ABILITY | M_SPEAKS, MR_RES_POISON | MR_RES_FIRE | MR_RES_COLD | MR_RES_ELEC, 0, 10, MONS_CLAY_GOLEM, MONS_ORANGE_STATUE, MH_NONLIVING, MAG_IMMUNE, { {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} }, @@ -4395,7 +4395,7 @@ { MONS_SILVER_STATUE, '8', WHITE, "silver statue", - M_SPECIAL_ABILITY, + M_SPECIAL_ABILITY | M_SPEAKS, MR_RES_POISON | MR_RES_FIRE | MR_RES_COLD | MR_RES_ELEC, 0, 10, MONS_CLAY_GOLEM, MONS_SILVER_STATUE, MH_NONLIVING, MAG_IMMUNE, { {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} }, @@ -4407,7 +4407,7 @@ { MONS_ICE_STATUE, '8', LIGHTBLUE, "ice statue", - M_SPELLCASTER, + M_SPELLCASTER | M_SPEAKS, MR_RES_POISON | MR_VUL_FIRE | MR_RES_COLD | MR_RES_ELEC, 0, 10, MONS_CLAY_GOLEM, MONS_ICE_STATUE, MH_NONLIVING, MAG_IMMUNE, { {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} }, diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 5b8a200e5f..627f80e6e3 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -34,6 +34,7 @@ #include "debug.h" #include "delay.h" #include "dgnevent.h" +#include "insult.h" #include "itemname.h" #include "itemprop.h" #include "items.h" @@ -2067,7 +2068,7 @@ bool mons_has_ranged_attack( const monsters *mon ) const item_def *primary = mnc->mslot_item(MSLOT_WEAPON); const item_def *missile = mnc->missiles(); - if (!missile && weapon != primary + if (!missile && weapon != primary && primary && get_weapon_brand(*primary) == SPWPN_RETURNING) { return (true); @@ -4654,3 +4655,397 @@ void mon_enchant::set_duration(const monsters *mons, const mon_enchant *added) if (duration > maxduration) maxduration = duration; } + +// Replaces the "@foo@" strings in monster shout and monster speak +// definitions. +std::string do_mon_str_replacements(const std::string in_msg, + const monsters* monster) +{ + std::string msg = in_msg; + + description_level_type nocap, cap; + + if (monster->attitude == ATT_FRIENDLY && player_monster_visible(monster)) + { + nocap = DESC_PLAIN; + cap = DESC_PLAIN; + + msg = replace_all(msg, "@the_something@", "your @the_something@"); + msg = replace_all(msg, "@The_something@", "Your @The_something@"); + msg = replace_all(msg, "@the_monster@", "your @the_monster@"); + msg = replace_all(msg, "@The_monster@", "Your @the_monster@"); + } + else + { + nocap = DESC_NOCAP_THE; + cap = DESC_CAP_THE; + } + + if (see_grid(monster->x, monster->y)) + { + dungeon_feature_type feat = grd[monster->x][monster->y]; + if (feat < MINMOVE || feat >= NUM_REAL_FEATURES) + msg = replace_all(msg, "@surface@", "buggy surface"); + else if (feat == DNGN_LAVA) + msg = replace_all(msg, "@surface@", "lava"); + else if (feat == DNGN_DEEP_WATER || feat == DNGN_SHALLOW_WATER) + msg = replace_all(msg, "@surface@", "water"); + else if (feat >= DNGN_ALTAR_ZIN && feat < DNGN_BLUE_FOUNTAIN) + msg = replace_all(msg, "@surface@", "altar"); + else + msg = replace_all(msg, "@surface@", "ground"); + + msg = replace_all(msg, "@feature@", raw_feature_description(feat)); + } + else + { + msg = replace_all(msg, "@surface@", "buggy unseen surface"); + msg = replace_all(msg, "@feature@", "buggy unseen feature"); + } + + msg = replace_all(msg, "@player_name@", you.your_name); + + if (player_monster_visible(monster)) + { + std::string something = monster->name(DESC_PLAIN); + msg = replace_all(msg, "@something@", something); + msg = replace_all(msg, "@a_something@", monster->name(DESC_NOCAP_A)); + msg = replace_all(msg, "@the_something@", monster->name(nocap)); + + something[0] = toupper(something[0]); + msg = replace_all(msg, "@Something@", something); + msg = replace_all(msg, "@A_something@", monster->name(DESC_CAP_A)); + msg = replace_all(msg, "@The_something@", monster->name(cap)); + } + else + { + msg = replace_all(msg, "@something@", "something"); + msg = replace_all(msg, "@a_something@", "something"); + msg = replace_all(msg, "@the_something@", "something"); + + msg = replace_all(msg, "@Something@", "Something"); + msg = replace_all(msg, "@A_something@", "Something"); + msg = replace_all(msg, "@The_something@", "Something"); + } + + std::string plain = monster->name(DESC_PLAIN); + msg = replace_all(msg, "@monster@", plain); + msg = replace_all(msg, "@a_monster@", monster->name(DESC_NOCAP_A)); + msg = replace_all(msg, "@the_monster@", monster->name(nocap)); + + plain[0] = toupper(plain[0]); + msg = replace_all(msg, "@Monster@", plain); + msg = replace_all(msg, "@A_monster@", monster->name(DESC_CAP_A)); + msg = replace_all(msg, "@The_monster@", monster->name(cap)); + + msg = replace_all(msg, "@possessive@", + mons_pronoun(monster->type, 3)); + msg = replace_all(msg, "@pronoun@", + mons_pronoun(monster->type, 0)); + + msg = replace_all(msg, "@imp_taunt@", imp_taunt_str()); + msg = replace_all(msg, "@demon_taunt@", demon_taunt_str()); + + static const char * sound_list[] = + { + "says", // actually S_SILENT + "shouts", + "barks", + "shouts", + "roars", + "screams", + "bellows", + "screeches", + "buzzes", + "moans", + "whines", + "croaks", + "growls", + "hisses", + "breathes", // S_VERY_SOFT + "whispers", // S_SOFT + "says", // S_NORMAL + "shouts", // S_LOUD + "screams" // S_VERY_LOUD + }; + + if (mons_shouts(monster->type) >= NUM_SHOUTS) + { + mpr("Invalid @says@ type.", MSGCH_DIAGNOSTICS); + msg = replace_all(msg, "@says@", "bugilly says"); + } + else + msg = replace_all(msg, "@says@", + sound_list[mons_shouts(monster->type)]); + + // The proper possessive for a word ending in an "s" is to + // put an appostraphe after the "s": "Chris" -> "Chris'", + // not "Chris" -> "Chris's". Stupid English language... + msg = replace_all(msg, "s's", "s'"); + + return msg; +} + +static mon_body_shape get_ghost_shape(const monsters *mon) +{ + const ghost_demon &ghost = *(mon->ghost); + + switch(ghost.values[GVAL_SPECIES]) + { + case SP_NAGA: + return (MON_SHAPE_NAGA); + + case SP_CENTAUR: + return (MON_SHAPE_CENTAUR); + + case SP_KENKU: + return (MON_SHAPE_HUMANOID_WINGED); + + case SP_RED_DRACONIAN: + case SP_WHITE_DRACONIAN: + case SP_GREEN_DRACONIAN: + case SP_GOLDEN_DRACONIAN: + case SP_GREY_DRACONIAN: + case SP_BLACK_DRACONIAN: + case SP_PURPLE_DRACONIAN: + case SP_MOTTLED_DRACONIAN: + case SP_PALE_DRACONIAN: + case SP_UNK0_DRACONIAN: + case SP_UNK1_DRACONIAN: + case SP_BASE_DRACONIAN: + return (MON_SHAPE_HUMANOID_TAILED); + } + + return (MON_SHAPE_HUMANOID); +} + +mon_body_shape get_mon_shape(const monsters *mon) +{ + if (mon->type == MONS_PLAYER_GHOST) + return get_ghost_shape(mon); + else if (mons_is_zombified(mon)) + return get_mon_shape(mon->number); + else + return get_mon_shape(mon->type); +} + +mon_body_shape get_mon_shape(const int type) +{ + switch(mons_char(type)) + { + case 'a': // ants and cockroaches + return(MON_SHAPE_INSECT); + case 'b': // bats and butterflys + if (type == MONS_BUTTERFLY) + return(MON_SHAPE_INSECT_WINGED); + else + return(MON_SHAPE_BAT); + case 'c': // centaurs + return(MON_SHAPE_CENTAUR); + case 'd': // draconions and drakes + if (mons_genus(type) == MONS_DRACONIAN || + mons_class_flag(type, M_HUMANOID)) + { + if (mons_class_flag(type, M_FLIES)) + return(MON_SHAPE_HUMANOID_WINGED_TAILED); + else + return(MON_SHAPE_HUMANOID_TAILED); + } + else if (mons_class_flag(type, M_FLIES)) + return(MON_SHAPE_QUADRUPED_WINGED); + else + return(MON_SHAPE_QUADRUPED); + case 'e': // elves + return(MON_SHAPE_HUMANOID); + case 'f': // fungi + return(MON_SHAPE_FUNGUS); + case 'g': // gargoyles, gnolls, goblins and hobgoblins + if (type == MONS_GARGOYLE) + return(MON_SHAPE_HUMANOID_WINGED_TAILED); + else + return(MON_SHAPE_HUMANOID); + case 'h': // hounds + case 'j': // jackals + return(MON_SHAPE_QUADRUPED); + case 'k': // killer bees + return(MON_SHAPE_INSECT_WINGED); + case 'l': // lizards + return(MON_SHAPE_QUADRUPED); + case 'm': // minotaurs, manticores, and snails/slugs/etc + if (type == MONS_MINOTAUR) + return(MON_SHAPE_HUMANOID); + else if (type == MONS_MANTICORE) + return(MON_SHAPE_QUADRUPED); + else + return(MON_SHAPE_SNAIL); + case 'n': // necrophages and ghouls + return(MON_SHAPE_HUMANOID); + case 'o': // orcs + return(MON_SHAPE_HUMANOID); + case 'p': // ghosts + if (type != MONS_INSUBSTANTIAL_WISP && + type != MONS_PLAYER_GHOST) + return(MON_SHAPE_HUMANOID); + case 'q': // quasists + return(MON_SHAPE_HUMANOID_TAILED); + case 'r': // rodents + return(MON_SHAPE_QUADRUPED); + case 's': // arachnids and centidpeds + if (type == MONS_GIANT_CENTIPEDE) + return(MON_SHAPE_CENTIPEDE); + else + return(MON_SHAPE_ARACHNID); + case 'u': // ugly things are humanoid??? + return(MON_SHAPE_HUMANOID); + case 'v': // vortices and elementals + return(MON_SHAPE_MISC); + case 'w': // worms + return(MON_SHAPE_SNAKE); + case 'x': // small abominations + return(MON_SHAPE_MISC); + case 'y': // winged insects + return(MON_SHAPE_INSECT_WINGED); + case 'z': // small skeletons + if (type == MONS_SKELETAL_WARRIOR) + return(MON_SHAPE_HUMANOID); + else + // constructed type, not enough info to determine shape + return(MON_SHAPE_MISC); + case 'A': // angelic beings + return(MON_SHAPE_HUMANOID_WINGED); + case 'B': // beetles + return(MON_SHAPE_INSECT); + case 'C': // giants + return(MON_SHAPE_HUMANOID); + case 'D': // dragons + if (mons_class_flag(type, M_FLIES)) + return(MON_SHAPE_QUADRUPED_WINGED); + else + return(MON_SHAPE_QUADRUPED); + case 'E': // effreets + return(MON_SHAPE_HUMANOID); + case 'F': // frogs + return(MON_SHAPE_QUADRUPED_TAILLESS); + case 'G': // floating eyeballs and orbs + return(MON_SHAPE_ORB); + case 'H': // hippogriffs and griffns + return(MON_SHAPE_QUADRUPED_WINGED); + case 'I': // ice beasts + return(MON_SHAPE_QUADRUPED); + case 'J': // jellies and jellyfish + return(MON_SHAPE_BLOB); + case 'K': // kobolds + return(MON_SHAPE_HUMANOID); + case 'L': // liches + return(MON_SHAPE_HUMANOID); + case 'M': // mummies + return(MON_SHAPE_HUMANOID); + case 'N': // nagas + return(MON_SHAPE_NAGA); + case 'O': // ogres + return(MON_SHAPE_HUMANOID); + case 'P': // plants + return(MON_SHAPE_PLANT); + case 'Q': // queen insects + if (type == MONS_QUEEN_BEE) + return(MON_SHAPE_INSECT_WINGED); + else + return(MON_SHAPE_INSECT); + case 'R': // rakshasa; humanoid? + return(MON_SHAPE_HUMANOID); + case 'S': // snakes + return(MON_SHAPE_SNAKE); + case 'T': // trolls + return(MON_SHAPE_HUMANOID); + case 'U': // bears + return(MON_SHAPE_QUADRUPED_TAILLESS); + case 'V': // vampires + return(MON_SHAPE_HUMANOID); + case 'W': // wraiths, humanoid if not a spectral thing + if (type == MONS_SPECTRAL_THING) + // constructed type, not enough info to determine shape + return(MON_SHAPE_MISC); + else + return(MON_SHAPE_HUMANOID); + case 'X': // large abominations + return(MON_SHAPE_MISC); + case 'Y': // yaks and sheep + if (type == MONS_SHEEP) + return(MON_SHAPE_QUADRUPED_TAILLESS); + else + return(MON_SHAPE_QUADRUPED); + case 'Z': // constructed type, not enough info to determine shape + return(MON_SHAPE_MISC); + case ';': // Fish and eels + if (type == MONS_ELECTRICAL_EEL) + return(MON_SHAPE_SNAKE); + else + return (MON_SHAPE_FISH); + + // The various demons, plus some golems and statues. And humanoids. + case '1': + case '2': + case '3': + case '4': + case '5': + case '&': + case '8': + case '@': + // Assume demon has wings if it can fly. + bool flies = mons_class_flag(type, M_FLIES); + + // Assume demon has a tail if it has a sting attack or a + // tail slap attack. + monsterentry *mon_data = get_monster_data(type); + bool tailed = false; + for (int i = 0; i < 4; i++) + if (mon_data->attack[i].type == AT_STING || + mon_data->attack[i].type == AT_TAIL_SLAP) + { + tailed = true; + break; + } + + if (flies && tailed) + return(MON_SHAPE_HUMANOID_WINGED_TAILED); + else if (flies && !tailed) + return(MON_SHAPE_HUMANOID_WINGED); + else if (!flies && tailed) + return(MON_SHAPE_HUMANOID_TAILED); + else + return(MON_SHAPE_HUMANOID); + } + + return(MON_SHAPE_MISC); +} + +std::string get_mon_shape_str(const monsters *mon) +{ + return get_mon_shape_str(get_mon_shape(mon)); +} + +std::string get_mon_shape_str(const int type) +{ + return get_mon_shape_str(get_mon_shape(type)); +} + +std::string get_mon_shape_str(const mon_body_shape shape) +{ + ASSERT(shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_MISC); + + if (shape < MON_SHAPE_HUMANOID || shape > MON_SHAPE_MISC) + return("buggy shape"); + + static const char *shape_names[] = + { + "humanoid", "winged humanoid", "tailed humanoid", + "winged tailed humanoid", "centaur", "naga", + "quadruped", "tailless quadruped", "winged quadruped", + "bat", "snake", "fish", "insect", "winged insect", + "arachnid", "centipede", "snail", "plant", "fungus", "orb", + "blob", "misc" + }; + + return (shape_names[shape]); +} + diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h index 1461c5bd88..61e9d9099b 100644 --- a/crawl-ref/source/mon-util.h +++ b/crawl-ref/source/mon-util.h @@ -371,4 +371,39 @@ monster_type random_monster_at_grid(int grid); monster_type get_monster_by_name(std::string name, bool exact = false); +std::string do_mon_str_replacements(const std::string msg, + const monsters* monster); + +enum mon_body_shape { + MON_SHAPE_HUMANOID, + MON_SHAPE_HUMANOID_WINGED, + MON_SHAPE_HUMANOID_TAILED, + MON_SHAPE_HUMANOID_WINGED_TAILED, + MON_SHAPE_CENTAUR, + MON_SHAPE_NAGA, + MON_SHAPE_QUADRUPED, + MON_SHAPE_QUADRUPED_TAILLESS, + MON_SHAPE_QUADRUPED_WINGED, + MON_SHAPE_BAT, + MON_SHAPE_SNAKE, // Including eels and worms + MON_SHAPE_FISH, + MON_SHAPE_INSECT, + MON_SHAPE_INSECT_WINGED, + MON_SHAPE_ARACHNID, + MON_SHAPE_CENTIPEDE, + MON_SHAPE_SNAIL, + MON_SHAPE_PLANT, + MON_SHAPE_FUNGUS, + MON_SHAPE_ORB, + MON_SHAPE_BLOB, + MON_SHAPE_MISC +}; + +mon_body_shape get_mon_shape(const monsters *mon); +mon_body_shape get_mon_shape(const int type); + +std::string get_mon_shape_str(const monsters *mon); +std::string get_mon_shape_str(const int type); +std::string get_mon_shape_str(const mon_body_shape shape); + #endif diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc index 4d80b20520..7ad2cf81f3 100644 --- a/crawl-ref/source/monplace.cc +++ b/crawl-ref/source/monplace.cc @@ -618,11 +618,17 @@ bool place_monster(int &id, int mon_type, int power, beh_type behaviour, if (proximity == PROX_NEAR_STAIRS) return (true); + // Not PROX_NEAR_STAIRS, so will it will be part of a band, if there + // is any. + if (band_size > 1) + menv[id].flags |= MF_BAND_MEMBER; + // (5) for each band monster, loop call to place_monster_aux(). for(i = 1; i < band_size; i++) { - place_monster_aux( band_monsters[i], behaviour, target, px, py, - lev_mons, extra, false, dur); + id = place_monster_aux( band_monsters[i], behaviour, target, px, py, + lev_mons, extra, false, dur); + menv[id].flags |= MF_BAND_MEMBER; } // placement of first monster, at least, was a success. diff --git a/crawl-ref/source/monspeak.cc b/crawl-ref/source/monspeak.cc index b79a7f9aec..90a7556e66 100644 --- a/crawl-ref/source/monspeak.cc +++ b/crawl-ref/source/monspeak.cc @@ -23,6 +23,7 @@ #include "externs.h" #include "beam.h" +#include "database.h" #include "debug.h" #include "fight.h" #include "insult.h" @@ -38,187 +39,128 @@ #include "stuff.h" #include "view.h" -struct mon_dialogue +static std::string get_speak_string(const std::vector prefixes, + const std::string key, + const monsters *monster) { - monster_type speaker; - const char **silenced; - const char **confused; - const char **confused_friend; - const char **fleeing; - const char **fleeing_friend; - const char **friendly; - const char **hostile; // Most common. -}; - -static const char *murray_silenced[] = -{ - "%s rolls in a circle.", - "%s rolls around.", - "%s spins like a top.", - "%s grins evilly.", - "%s seems to say something.", - "%s says something you can't hear. It was probably not a compliment.", - NULL -}; - -static const char *murray_hostile[] = -{ - "%s rolls in a circle.", - "%s rolls around.", - "%s spins like a top.", - "%s grins evilly.", - "%s laughs evilly.", - "%s cackles, \"I will rule the world!\"", - "%s shouts, \"Give me your head, so I can impale it on a pike!\"", - "%s's teeth chatter loudly.", - "%s yells, \"I'm a mighty demonic power!\"", - "%s asks, \"How could you choose the Orb over me, your best friend?\"", - "%s shouts, \"Let the forces of evil and voodoo overcome you!\"", - "%s screams, \"If I had legs, you would be dead twenty times over!\"", - "%s yells, \"My visage is famous all over the dungeon!\"", - "%s says, \"You're the second biggest fool I've ever met!\"", - NULL -}; + std::string prefix = ""; + const int size = prefixes.size(); + for (int i = 0; i < size; i++) + { + prefix += prefixes[i]; + prefix += " "; + } -static mon_dialogue vox_populi[] = -{ - { MONS_MURRAY, murray_silenced, NULL, NULL, NULL, NULL, NULL, - murray_hostile }, -}; + std::string msg = ""; + msg = getSpeakString(prefix + key); + if (msg != "") + return msg; -static const mon_dialogue *find_dialogue(const monsters *monster) -{ - for (unsigned i = 0; i < sizeof(vox_populi) / sizeof(*vox_populi); ++i) - if (vox_populi[i].speaker == monster->type) - return (&vox_populi[i]); - return (NULL); -} + // Combinations of prefixes by threes + if (size >= 3) + { + for (int i = 0; i < (size - 2); i++) + for (int j = i + 1; j < (size - 1); j++) + for (int k = j + 1; k < size; k++) + { + prefix = prefixes[i] + " "; + prefix += prefixes[j] + " "; + prefix += prefixes[k] + " "; -static bool say_dialogue(const monsters *monster, - const char **dialogue) -{ - if (!dialogue) - return (false); + msg = getSpeakString("default " + prefix + key); + if (msg != "") + return msg; + } + } - int nitems = 0; - for (const char **run = dialogue; *run; ++run, ++nitems) - ; + // Combinations of prefixes by twos + if (size >= 2) + { + for (int i = 0; i < (size - 1); i++) + for (int j = i + 1; j < size; j++) + { + prefix = prefixes[i] + " "; + prefix += prefixes[j] + " "; - const char *chosen = nitems? dialogue[random2(nitems)] : NULL; + msg = getSpeakString("default " + prefix + key); + if (msg != "") + return msg; + } + } - if (chosen && *chosen) + // Prefixes singly + if (size >= 1) { - mprf(MSGCH_TALK, chosen, monster->name(DESC_CAP_THE).c_str()); - return (true); + for (int i = 0; i < size; i++) + { + prefix = prefixes[i] + " "; + + msg = getSpeakString("default " + prefix + key); + if (msg != "") + return msg; + } } - return (false); + // No prefixes + msg = getSpeakString("default " + key); + + return msg; } -static bool say_specific_dialogue(const monsters *monster, - const mon_dialogue *dialogue) +// Player ghosts with different classes can potentially speak different +// things. +static std::string player_ghost_speak_str(const monsters *monster, + const std::vector prefixes) { - if (silenced(monster->x, monster->y)) - return (say_dialogue(monster, dialogue->silenced)); - - if (monster->has_ench(ENCH_CHARM)) - return (false); + const ghost_demon &ghost = *(monster->ghost); + std::string ghost_class = get_class_name(ghost.values[GVAL_CLASS]); - const bool friendly = (monster->attitude == ATT_FRIENDLY); + std::string prefix = ""; + for (int i = 0, size = prefixes.size(); i < size; i++) + { + prefix += prefixes[i]; + prefix += " "; + } - if (mons_is_confused(monster)) - return (say_dialogue( - monster, - friendly? dialogue->confused_friend - : dialogue->confused)); + std::string msg = getSpeakString(prefix + ghost_class + " player ghost"); - if (monster->behaviour == BEH_FLEE) - return (say_dialogue( - monster, - friendly? dialogue->fleeing_friend - : dialogue->fleeing)); + if (msg == "__NONE") + return ""; - if (monster->attitude == ATT_FRIENDLY) - return (say_dialogue(monster, dialogue->friendly)); + if (msg == "" || msg == "__NEXT") + msg = getSpeakString(prefix + "player ghost"); - return (say_dialogue(monster, dialogue->hostile)); + return msg; } -// returns true if something is said + // returns true if something is said bool mons_speaks(const monsters *monster) { - int temp_rand; // probability determination - - // This function is a little bit of a problem for the message channels - // since some of the messages it generates are "fake" warning to - // scare the player. In order to accomidate this intent, we're - // falsely categorizing various things in the function as spells and - // danger warning... everything else just goes into the talk channel -- bwr - msg_channel_type msg_type = MSGCH_TALK; - - const std::string m_name = monster->name(DESC_CAP_THE); - strcpy(info, m_name.c_str()); + // Invisible monster tries to remain unnoticed. Unless they're + // confused, since then they're too confused to realize they + // should stay silent, but only if the player can see them, so as + // to not have to deal with cases of speaking monsters which the + // player can't see. + if (monster->invisible() && !(player_monster_visible(monster) + && monster->has_ench(ENCH_CONFUSION))) + return false; - if (monster->invisible()) + // Dealing with the monster not being silenced while the player + // *is* silenced, and is hence able to see the monsters' gestures + // and such but not hear any sounds it makes, would be a big + // headache to deal with, so skip it. + if (!silenced(monster->x, monster->y) + && silenced(you.x_pos, you.y_pos)) return false; - // invisible monster tries to remain unnoticed - const mon_dialogue *dialogue = find_dialogue(monster); - if (dialogue) - return (say_specific_dialogue(monster, dialogue)); - - //mv: if it's also invisible, program never gets here - if (silenced(monster->x, monster->y)) - { + // Silenced monsters only "speak" 1/3 as often as non-silenced, + // unless they're normally silent (S_SILENT). Use + // get_monster_data(monster->type) to bypass mon_shouts() + // replacing S_RANDOM with a random value. + if (silenced(monster->x, monster->y) + && get_monster_data(monster->type)->shouts != S_SILENT) if (!one_chance_in(3)) - return false; // while silenced, don't bother so often - - if (monster->has_ench(ENCH_CONFUSION)) - { - temp_rand = random2(10); - strcat(info, (temp_rand < 4) ? " gestures wildly." : - (temp_rand == 4) ? " looks confused." : - (temp_rand == 5) ? " grins evilly." : - (temp_rand == 6) ? " smiles happily." : - (temp_rand == 7) ? " cries." - : " says something but you don't hear anything."); - } - else if (monster->behaviour == BEH_FLEE) - { - temp_rand = random2(10); - strcat(info, - (temp_rand < 3) ? " glances furtively about." : - (temp_rand == 3) ? " opens its mouth, as if shouting." : - (temp_rand == 4) ? " looks around." : - (temp_rand == 5) ? " appears indecisive." : - (temp_rand == 6) ? " ponders the situation." - : " seems to say something."); - } - // disregard charmed critters.. they're not too expressive - else if (monster->attitude == ATT_FRIENDLY) - { - temp_rand = random2(10); - strcat(info, (temp_rand < 3) ? " gives you a thumbs up." : - (temp_rand == 3) ? " looks at you." : - (temp_rand == 4) ? " waves at you." : - (temp_rand == 5) ? " smiles happily.": - (temp_rand == 6) ? " winks at you." - : " says something you can't hear."); - } - else - { - temp_rand = random2(10); - strcat(info, (temp_rand < 3) ? " gestures." : - (temp_rand == 3) ? " gestures obscenely." : - (temp_rand == 4) ? " grins." : - (temp_rand == 5) ? " looks angry." : - (temp_rand == 6) ? " seems to be listening." - : " says something but you don't hear anything."); - } //end switch silenced monster's behaviour - - mpr(info, MSGCH_TALK); - return true; - } // end silenced monster + return false; // charmed monsters aren't too expressive if (monster->has_ench(ENCH_CHARM)) @@ -227,2397 +169,216 @@ bool mons_speaks(const monsters *monster) // berserk monsters just want your hide. if (monster->has_ench(ENCH_BERSERK)) return false; - + + std::vector prefixes; + if (monster->attitude == ATT_FRIENDLY) + prefixes.push_back("friendly"); + + if (monster->behaviour == BEH_FLEE) + prefixes.push_back("fleeing"); + + if (silenced(monster->x, monster->y)) + prefixes.push_back("silenced"); + if (monster->has_ench(ENCH_CONFUSION)) - { - if (mons_holiness( monster ) == MH_DEMONIC - && monster->type != MONS_IMP) - { - return (false); - } + prefixes.push_back("confused"); + + std::string msg; + + // __NONE means to be silent, and __NEXT means to try the next, + // less exact method of describing the monster to find a speech + // string. + + // First, try its exact name + if (monster->type == MONS_PLAYER_GHOST) + // Player ghosts are treated differently. + msg = player_ghost_speak_str(monster, prefixes); + else if (monster->type == MONS_PANDEMONIUM_DEMON) + // Pandemonium demons have randomly generated names, + // so use "pandemonium lord" instead. + msg = get_speak_string(prefixes, "pandemonium lord", monster); + else + msg = get_speak_string(prefixes, monster->name(DESC_PLAIN), monster); - if (mons_friendly(monster)) - { - switch (random2(18)) // speaks for friendly confused monsters - { - case 0: - strcat(info, " prays for help."); - break; - case 1: - strcat(info, " screams, \"Help!\""); - break; - case 2: - strcat(info, " shouts, \"I'm losing control!\""); - break; - case 3: - strcat(info, " shouts, \"What's happening?\""); - break; - case 4: - case 5: - strcat(info, " gestures wildly."); - break; - case 6: - strcat(info, " cries."); - break; - case 7: - strcat(info, " shouts, \"Yeah!\""); - break; - case 8: - strcat(info, " sings."); - break; - case 9: - strcat(info, " laughs crazily."); - break; - case 10: - strcat(info, " ponders the situation."); - break; - case 11: - strcat(info, " grins madly."); - break; - case 12: - strcat(info, " looks very confused."); - break; - case 13: - strcat(info, " mumbles something."); - break; - case 14: - strcat(info, " giggles crazily."); - break; - case 15: - strcat(info, " screams, \""); - strcat(info, you.your_name); - strcat(info, "! Help!\""); - break; - case 16: - strcat(info, " screams, \""); - strcat(info, you.your_name); - strcat(info, "! What's going on?\""); - break; - case 17: - strcat(info, " says, \""); - strcat(info, you.your_name); - strcat(info, ", I'm a little confused.\""); - break; - } - } - else - { - switch (random2(23)) // speaks for unfriendly confused monsters - { - case 0: - strcat(info, " yells, \"Get them off me!\""); - break; - case 1: - strcat(info, " screams, \"I will kill you anyway!\""); - break; - case 2: - strcat(info, " shouts, \"What's happening?\""); - break; - case 3: - case 4: - case 5: - strcat(info, " gestures wildly."); - break; - case 6: - strcat(info, " cries."); - break; - case 7: - strcat(info, " shouts, \"NO!\""); - break; - case 8: - strcat(info, " shouts, \"YES!\""); - break; - case 9: - strcat(info, " laughs crazily."); - break; - case 10: - strcat(info, " ponders the situation."); - break; - case 11: - strcat(info, " grins madly."); - break; - case 12: - strcat(info, " looks very confused."); - break; - case 13: - strcat(info, " mumbles something."); - break; - case 14: - strcat(info, " says, \"I'm a little confused.\""); - break; - case 15: - strcat(info, " asks, \"Where am I?\""); - break; - case 16: - strcat(info, " shakes."); - break; - case 17: - strcat(info, " asks, \"Who are you?\""); - break; - case 18: - strcat(info, " asks, \"What the hell are we doing here? Mmm, I see...\""); - break; - case 19: - strcat(info, " cries, \"My head! MY HEAD!!!\""); - break; - case 20: - strcat(info, " says, \"Why is everything spinning?\""); - break; - case 21: - strcat(info, " screams, \"NO! I can't bear that much noise!\""); - break; - case 22: - strcat(info, " is trying to cover his eyes."); - break; - } - } + if (msg == "__NONE") + return false; - } - else if (monster->has_ench(ENCH_HELD)) + // Now that we're not dealing with a specific monster name, include + // whether or not it can move in the prefix + if (mons_is_stationary(monster)) + prefixes.insert(prefixes.begin(), "stationary"); + + // Names for the monster, its species and its genus all failed, + // so try the monster's glyph/symbol. + if (msg == "" || msg == "__NEXT") { - if (mons_friendly(monster)) - { - switch(random2(8)) - { - case 0: - strcat(info, " says, \"Help me, "); - strcat(info, you.your_name); - strcat(info, ", please!\""); - break; - case 1: - strcat(info, " cries, \"MUMMY!\""); - break; - case 2: - strcat(info, " shouts, \""); - strcat(info, you.your_name); - strcat(info, "! Can't you see I need your help?\""); - break; - case 3: - strcat(info, " shouts, \"I could do with a little help here, you know.\""); - break; - case 4: - strcat(info, " mumbles something."); - break; - case 5: - strcat(info, " says, \"Umm, "); - strcat(info, you.your_name); - strcat(info, "? Help?\""); - break; - case 6: - strcat(info, " cries."); - break; - case 7: - strcat(info, " cries, \"Why me?"); - break; - } - } - else // unfriendly monsters - { - switch(random2(12)) - { - case 0: - strcat(info, " screams, \"HEY! This isn't fair!\""); - break; - case 1: - strcat(info, " screams, \"Help! Get me out of here!\""); - break; - case 2: - strcat(info, " begs, \"Could you help me? I swear I won't hurt you.\""); - break; - case 3: - strcat(info, " yells, \"LEMME GO!\""); - break; - case 4: - strcat(info, " cries, \"Please! I'll never do it again!\""); - break; - case 5: - strcat(info, " mutters, \"Just what did I do to deserve this?\""); - break; - case 6: - strcat(info, " asks, \"Hey, want to switch places?\""); - break; - case 7: - strcat(info, " cries, \"I hate you!\""); - break; - case 8: - strcat(info, " snarls, \"This is all your fault!\""); - break; - case 9: - strcat(info, " says, \"I meant to do this, just so you know.\""); - break; - case 10: - strcat(info, " shouts, \"This is all a huge misunderstanding!"); - break; - case 11: - strcat(info, " cries, \"Why me?\""); - break; - } - } + std::string key = "'"; + + // Database keys are case-insensitve. + if (isupper(mons_char(monster->type))) + key += "cap-"; + + key += mons_char(monster->type); + key += "'"; + msg = get_speak_string(prefixes, key, monster); } - else if (monster->behaviour == BEH_FLEE) - { - if (mons_holiness( monster ) == MH_DEMONIC - && monster->type != MONS_IMP) - { - return (false); - } + if (msg == "__NONE") + return false; - if (mons_friendly(monster)) + // Monster symbol didn't work, try monster shape. Since we're + // dealing with just the monster shape, change the prefix to + // include info on if the monster's intelligence is at odds with + // its shape. + mon_body_shape shape = get_mon_shape(monster); + mon_intel_type intel = mons_intel(monster->type); + if (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA + && intel < I_NORMAL) + prefixes.insert(prefixes.begin(), "stupid"); + else if (shape >= MON_SHAPE_QUADRUPED && shape <= MON_SHAPE_FISH) + { + if (mons_char(monster->type) == 'w') { - switch (random2(11)) - { - case 0: - snprintf( info, INFO_SIZE, "%s %s, \"WAIT FOR ME!\"", - m_name.c_str(), coinflip() ? "shouts" : "yells"); - strcat(info, you.your_name); - strcat(info, ", could you help me?\""); - break; - case 1: - strcat(info, " screams, \"Help!\""); - break; - case 2: - strcat(info, " shouts, \"Cover me!\""); - break; - case 3: - strcat(info, " screams, \""); - strcat(info, you.your_name); - strcat(info, "! Help me!\""); - break; - case 4: - case 5: - case 6: - strcat(info, " tries to hide somewhere."); - break; - case 7: - strcat(info, " prays for help."); - break; - case 8: - strcat(info, " looks at you beseechingly."); - break; - case 9: - strcat(info, " shouts, \"Protect me!\""); - break; - case 10: - strcat(info, " cries, \"Don't forget your friends!\""); - break; - } + if (intel > I_INSECT) + prefixes.insert(prefixes.begin(), "smart"); + else if (intel < I_INSECT) + prefixes.insert(prefixes.begin(), "stupid"); } else { - switch (random2(20)) // speaks for unfriendly fleeing monsters - { - case 0: - snprintf( info, INFO_SIZE, "%s %s, \"Help!\"", m_name.c_str(), - coinflip()? "yells" : "wails"); - break; - case 1: - snprintf( info, INFO_SIZE, "%s %s, \"Help!\"", m_name.c_str(), - coinflip() ? "cries" : "screams"); break; - case 2: - snprintf( info, INFO_SIZE, "%s %s, \"Why can't we all just get along?\"", - m_name.c_str(), coinflip() ? "begs" : "pleads"); - break; - case 3: - snprintf( info, INFO_SIZE, "%s %s trips in trying to escape.", m_name.c_str(), - coinflip() ? "nearly" : "almost"); - break; - case 4: - snprintf( info, INFO_SIZE, "%s %s, \"Of all the rotten luck!\"", m_name.c_str(), - coinflip() ? "mutters" : "mumbles"); - break; - case 5: - snprintf( info, INFO_SIZE, "%s %s, \"Oh dear! Oh dear!\"", m_name.c_str(), - coinflip() ? "moans" : "wails"); - case 6: - snprintf( info, INFO_SIZE, "%s %s, \"Damn and blast!\"", m_name.c_str(), - coinflip() ? "mutters" : "mumbles"); - break; - case 7: - strcat(info, " prays for help."); - break; - case 8: - strcat(info, " shouts, \"No! I'll never do that again!\""); - break; - case 9: - snprintf( info, INFO_SIZE, "%s %s", m_name.c_str(), - coinflip() ? "begs for mercy." : "cries, \"Mercy!\""); - break; - case 10: - snprintf( info, INFO_SIZE, "%s %s, \"%s!\"", m_name.c_str(), - coinflip() ? "blubbers" : "cries", - coinflip() ? "Mommeee" : "Daddeee"); - break; - case 11: - snprintf( info, INFO_SIZE, "%s %s, \"Please don't kill me!\"", m_name.c_str(), - coinflip() ? "begs" : "pleads"); - break; - case 12: - snprintf( info, INFO_SIZE, "%s %s, \"Please don't hurt me!\"", m_name.c_str(), - coinflip() ? "begs" : "pleads"); - break; - case 13: - snprintf( info, INFO_SIZE, "%s %s, \"Please, I have a lot of children...\"", - m_name.c_str(), coinflip() ? "begs" : "pleads"); - break; - case 14: - strcat(info, " tries to recover lost courage."); - break; - case 15: - case 16: - case 17: - strcat(info, " gives up."); - break; - case 19: - snprintf( info, INFO_SIZE, "%s looks really %s.", - m_name.c_str(), - coinflip() ? "scared stiff" : "rattled"); - break; - } + if (intel > I_ANIMAL) + prefixes.insert(prefixes.begin(), "smart"); + else if (intel < I_ANIMAL) + prefixes.insert(prefixes.begin(), "stupid"); } } - else if (mons_friendly(monster)) + else if (shape >= MON_SHAPE_INSECT && shape <= MON_SHAPE_SNAIL) { - if (mons_holiness( monster ) == MH_DEMONIC - && monster->type != MONS_IMP) - { - return (false); - } - - // friendly imps are too common so they speak very very rarely - if (monster->type == MONS_IMP) - { - if (!one_chance_in(10)) - return (false); - - switch (random2(12)) - { - case 0: - strcat(info, " says, \"Just tell me who NOT to kill.\""); - break; - case 1: - strcat(info, " says, \"OK Boss!\""); - break; - case 2: - strcat(info, " grins impishly at you."); - break; - case 3: - strcat(info, " picks up some beetles from the floor " - "and offers them to you."); - break; - case 4: - strcat(info, " blows smoke rings."); - break; - case 5: - strcat(info, " shouts, \"Over here! I found it!\""); - break; - case 6: - strcat(info, " says, \"The Orb is all yours.\""); - break; - case 7: - strcat(info, " says, \"Isn't this more fun with friends?\""); - break; - case 8: - strcat(info, " says, \"Uh-oh! Wait. OK.\""); - break; - case 9: - strcat(info, " shouts, \"Stay back! It could be a trick.\""); - break; - case 10: - strcat(info, " says, \"You're so much nicer than " - "my last boss.\""); - break; - case 11: - strcat(info, " jumps up and down with excitement.\""); - break; - } - } - - else - { - switch (random2(18)) - { - case 0: - strcat(info, " yells, \"Run! I'll cover you!\""); - break; - case 1: - strcat(info, " shouts, \"Die, monster!\""); - break; - case 2: - strcat(info, " says, \"It's nice to have friends.\""); - break; + if (intel > I_INSECT) + prefixes.insert(prefixes.begin(), "smart"); + else if (intel < I_INSECT) + prefixes.insert(prefixes.begin(), "stupid"); + } + else if (shape >= MON_SHAPE_PLANT && shape <= MON_SHAPE_BLOB + && intel > I_PLANT) + prefixes.insert(prefixes.begin(), "smart"); - case 3: - strcat(info, " looks at you."); - break; - case 4: - strcat(info, " smiles at you."); - break; - case 5: - strcat(info, " says, \""); - strcat(info, you.your_name); - strcat(info, ", you are my only friend.\""); - break; - case 6: - strcat(info, " says, \""); - strcat(info, you.your_name); - strcat(info, ", I like you.\""); - break; + if (msg == "" || msg == "__NEXT") + msg = get_speak_string(prefixes, get_mon_shape_str(shape), monster); + if (msg == "__NONE") + return false; - case 7: - strcat(info, " waves at you."); - break; - case 8: - strcat(info, " says, \"Be careful!\""); - break; - case 9: - strcat(info, " says, \"Don't worry. I'm here with you.\""); - break; - case 10: - strcat(info, " smiles happily."); - break; - case 11: - strcat(info, " shouts, \"No mercy! Kill them all!"); - break; - case 12: - strcat(info, " winks at you."); - break; - case 13: - strcat(info, " says, \"Me and you. It sounds cool.\""); - break; - case 14: - strcat(info, " says, \"I'll never leave you.\""); - break; - case 15: - strcat(info, " says, \"I would die for you.\""); - break; - case 16: - strcat(info, " shouts, \"Beware of monsters!\""); - break; - case 17: - strcat(info, " looks friendly."); - break; - } - } - } - else + // If we failed to get a message with a winged or tailed humanoid, + // or a naga or centaur, try moving closer to plain humanoid + if ((msg == "" || msg == "__NEXT") && shape > MON_SHAPE_HUMANOID + && shape <= MON_SHAPE_NAGA) { - switch (monster->type) + // If a humanoid monster has both wings and a tail, try + // removing one and then the other to see if we get any + // results. + if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED) { - case MONS_TERENCE: // fighter who likes to kill - switch (random2(15)) - { - case 0: - strcat(info, " screams, \"I'm going to kill you! \""); - break; - case 1: - strcat(info, " shouts, \"Now you die.\""); - break; - case 2: - strcat(info, " says, \"Rest in peace.\""); - break; - case 3: - snprintf( info, INFO_SIZE, "%s shouts, \"%s!!!\"", - m_name.c_str(), coinflip() ? "ATTACK" : "DIE"); - break; - case 4: - strcat(info, " says, \"How do you enjoy it?\""); - break; - case 5: - strcat(info, " shouts, \"Get ready for death!\""); - break; - case 6: - strcat(info, " says, \"You are history.\""); - break; - case 7: - strcat(info, " says, \"Do you want it fast or slow?.\""); - break; - case 8: - strcat(info, " says, \"Did you write a testament? You should...\""); - break; - case 9: - strcat(info, " says, \"Time to say good-bye...\""); - break; - case 10: - snprintf( info, INFO_SIZE, "%s says, \"Don't try to defend, it's %s.\"", - m_name.c_str(), coinflip() ? "pointless" : "senseless"); - break; - case 11: - strcat(info, " bares his teeth."); - break; - case 12: - snprintf( info, INFO_SIZE, "%s says, \"I'll show you few %s.\"", - m_name.c_str(), coinflip() ? "tricks" : "ploys."); - break; - case 13: - strcat(info, " screams, \"I want your blood.\""); - break; - case 14: - strcat(info, " looks scornfully at you."); - break; - } - break; // end Terence + shape = MON_SHAPE_HUMANOID_TAILED; + msg = get_speak_string(prefixes, + get_mon_shape_str(shape), + monster); - case MONS_EDMUND: // mercenaries guarding dungeon - case MONS_LOUISE: - case MONS_FRANCES: - case MONS_DUANE: - case MONS_FREDERICK: - switch (random2(17)) + // Only be silent if both tailed and winged return __NONE + if (msg == "" || msg == "__NONE" || msg == "__NEXT") { - case 0: - strcat(info, " screams, \"I'm going to kill you! Now!\""); - break; - case 1: - strcat(info, - " shouts, \"Return immediately or I'll kill you!\""); - break; - case 2: - strcat(info, - " says, \"Now you've reached the end of your journey!\""); - break; - case 3: - strcat(info, - " screams, \"One false step and I'll kill you!\""); - break; - case 4: - strcat(info, " says, \"Drop everything you've found here and return home.\""); - break; - case 5: - strcat(info, " shouts, \"You will never get the Orb.\""); - break; - case 6: - strcat(info, " looks very unfriendly."); - break; - case 7: - strcat(info, " looks very cold."); - break; - case 8: - strcat(info, " shouts, \"It's the end of the party!\""); - break; - case 9: - strcat(info, " says, \"Return every stolen item!\""); - break; - case 10: - strcat(info, " says, \"No trespassing is allowed here.\""); - break; - case 11: - strcat(info, " grins evilly."); - break; - case 12: - strcat(info, " screams, \"You must be punished!\""); - break; - case 13: - strcat(info, " says, \"It's nothing personal...\""); - break; - case 14: - strcat(info, " says, \"A dead adventurer is a good adventurer.\""); - break; - case 15: - strcat(info, " says, \"Coming here was your last mistake.\""); - break; - case 16: - strcat(info, " shouts, \"Intruder!\""); - break; - } - break; // end Edmund & Co + shape = MON_SHAPE_HUMANOID_WINGED; + std::string msg2; + msg2 = get_speak_string(prefixes, + get_mon_shape_str(shape), + monster); + if (msg == "__NONE" && msg2 == "__NONE") + return false; - case MONS_JOSEPH: - switch (random2(16)) - { - case 0: - strcat(info, " smiles happily."); - break; - case 1: - strcat(info, " says, \"I'm happy to see you. And I'll be happy to kill you.\""); - break; - case 2: - strcat(info, " says, \"I've waited for this moment for such a long time.\""); - break; - case 3: - strcat(info, - " says, \"It's nothing personal, but I have to kill you.\""); - break; - case 5: - strcat(info, " says, \"You will never get the Orb, sorry.\""); - break; - case 9: - strcat(info, " shouts, \"I love to fight! I love killing!\""); - break; - case 10: - strcat(info, - " says, \"I'm here to kill trespassers. I like my job.\""); - break; - case 11: - strcat(info, " tries to grin evilly."); - break; - case 12: - strcat(info, - " says, \"You must be punished! Or... I want to punish you!\""); - break; - case 13: - strcat(info, - " sighs, \"Being a guard is usually so boring...\""); - break; - case 14: - strcat(info, " shouts, \"At last some action!\""); - break; - case 15: - strcat(info, " shouts, \"Wow!\""); - break; - } - break; // end Joseph + if (msg2 == "__NONE") + msg2 = ""; - case MONS_ORC_HIGH_PRIEST: // priest, servants of dark ancient god - case MONS_DEEP_ELF_HIGH_PRIEST: - switch (random2(12)) - { - case 0: - case 1: - strcat(info, " prays."); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 2: - strcat(info, " mumbles some strange prayers."); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 3: - strcat(info, - " shouts, \"You are a heretic and must be destroyed.\""); - break; - case 4: - strcat(info, " says, \"All sinners must die.\""); - break; - - case 5: - strcat(info, " looks excited."); - break; - case 6: - strcat(info, " says, \"You will make a fine sacrifice.\""); - break; - case 7: - strcat(info, " starts to sing a prayer."); - break; - case 8: - strcat(info, " shouts, \"You must be punished.\""); - break; - case 9: - strcat(info, " intones a terrible prayer."); - msg_type = MSGCH_MONSTER_SPELL; - break; - case 10: - strcat(info, " says, \" Right in the middle of my " - "sermon, too.\""); - break; - case 11: - strcat(info, " says, \"The wages of sin are death, " - "you know.\""); - break; + msg = msg2; } - break; // end priests - - case MONS_ORC_SORCERER: // hateful wizards, using strange powers - case MONS_DEEP_ELF_SORCERER: - case MONS_WIZARD: - switch (random2(19)) - { - case 0: - case 1: - case 2: - strcat(info, " wildly gestures."); - mpr( info, MSGCH_MONSTER_SPELL ); - if (coinflip()) - canned_msg( MSG_NOTHING_HAPPENS ); - else - canned_msg( MSG_YOU_RESIST ); - return (true); - - case 3: - case 4: - case 5: - strcat(info, " mumbles some strange words."); - mpr( info, MSGCH_MONSTER_SPELL ); - if (coinflip()) - canned_msg( MSG_NOTHING_HAPPENS ); - else - canned_msg( MSG_YOU_RESIST ); - return (true); - - case 6: - strcat(info, " shouts, \"You can't withstand my power!\""); - break; + } // if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED) + if (msg == "" || msg == "__NONE" || msg == "__NEXT") + { + shape = MON_SHAPE_HUMANOID; + msg = get_speak_string(prefixes, + get_mon_shape_str(shape), + monster); + } + } + if (msg == "__NONE" || msg == "") + return false; - case 7: - strcat(info, " shouts, \"You are history.\""); - break; + if (msg == "__NEXT") + { + msg::streams(MSGCH_DIAGNOSTICS) + << "__NEXT used by shape-based speech string for monster '" + << monster->name(DESC_PLAIN) << "'" << std::endl; + return false; + } - case 8: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); + // We have a speech string, now parse and act on it. + msg = do_mon_str_replacements(msg, monster); - strcat(info, " becomes transparent for a moment."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; + std::vector lines = split_string("\n", msg); - case 9: - strcat(info, " throws some strange powder towards you."); - msg_type = MSGCH_MONSTER_SPELL; - break; + for (int i = 0, size = lines.size(); i < size; i++) + { + std::string line = lines[i]; - case 10: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); + if (line == "__YOU_RESIST") + { + canned_msg( MSG_YOU_RESIST ); + continue; + } + else if (line == "__NOTHING_HAPPENS") + { + canned_msg( MSG_NOTHING_HAPPENS ); + continue; + } + else if (line == "__MORE") + { + more(); + continue; + } - strcat(info, " glows brightly for a moment."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; + // This function is a little bit of a problem for the message + // channels since some of the messages it generates are "fake" + // warning to scare the player. In order to accomidate this + // intent, we're falsely categorizing various things in the + // function as spells and danger warning... everything else + // just goes into the talk channel -- bwr + msg_channel_type msg_type = MSGCH_TALK; - case 11: - strcat(info, " says, \"argatax netranoch dertex\""); - msg_type = MSGCH_MONSTER_SPELL; - break; + std::string::size_type pos = line.find(":"); - case 12: - strcat(info, " says, \"dogrw nutew berg\""); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 13: - strcat(info, " shouts, \"Entram moth deg ulag!\""); + if (pos != std::string::npos) + { + std::string param = line.substr(0, pos); + bool match = true; + + if (param == "DANGER") + msg_type = MSGCH_DANGER; + else if (param == "WARN") + msg_type = MSGCH_WARN; + else if (param == "SOUND") + msg_type = MSGCH_SOUND; + else if (param == "SPELL") msg_type = MSGCH_MONSTER_SPELL; - break; - - case 14: - strcat(info, " casts a spell."); - mpr(info, MSGCH_MONSTER_SPELL); - - strcpy(info, m_name.c_str()); - strcat(info, " becomes larger for a moment."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 15: - strcat(info, " casts a spell."); - mpr(info, MSGCH_MONSTER_SPELL); - - strcpy(info, m_name.c_str()); - strcat(info, "'s fingertips start to glow."); + else if (param == "ENCHANT") msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 16: - strcat(info, "'s eyes start to glow."); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 17: - strcat(info, " tries to paralyze you with his gaze."); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 18: - strcat(info, " casts a spell."); - mpr(info, MSGCH_MONSTER_SPELL); - canned_msg( MSG_YOU_RESIST ); - return (true); - } - break; // end wizards - - case MONS_JESSICA: // sorceress disturbed by player - switch (random2(10)) - { - case 0: - strcat(info, " grins evilly."); - break; - case 1: - strcat(info, " says, \"I'm really upset.\""); - break; - case 2: - strcat(info, " shouts, \"I don't like beings like you.\""); - break; - case 3: - strcat(info, - " shouts, \"Stop bothering me, or I'll kill you!\""); - break; - case 4: - strcat(info, " very coldly says, \"I hate your company.\""); - break; - case 5: - strcat(info, " mumbles something strange."); - msg_type = MSGCH_MONSTER_SPELL; - break; - case 6: - strcat(info, " looks very angry."); - break; - case 7: - strcat(info, - " shouts, \"You're disturbing me. I'll have to kill you.\""); - break; - case 8: - strcat(info, " screams, \"You are a ghastly nuisance!\""); - break; - case 9: - strcat(info, " gestures wildly."); - msg_type = MSGCH_MONSTER_SPELL; - break; - } - break; // end Jessica - - case MONS_SIGMUND: // mad old wizard - switch (random2(19)) - { - case 0: - case 1: - case 2: - strcat(info, " laughs crazily."); - break; - case 3: - strcat(info, " says, \"Don't worry, I'll kill you fast.\""); - break; - case 4: - strcat(info, " grinds his teeth."); - break; - case 5: - strcat(info, " asks, \"Do you like me?\""); - break; - case 6: - strcat(info, " screams, \"Die, monster!\""); - break; - case 7: - strcat(info, " says, \"You will soon forget everything.\""); - break; - case 8: - strcat(info, " screams, \"You will never... NEVER!\""); - break; - - case 9: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); - - strcat(info, "'s eyes start to glow with a red light. "); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 10: - strcat(info, " says, \"Look into my eyes.\""); - break; - case 11: - strcat(info, " says, \"I'm your fate.\""); - break; - - case 12: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); - - strcat(info, " is suddenly surrounded by pale blue light."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 13: - strcat(info, " tries to bite you."); - break; - - case 14: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); - - strcat(info, " is suddenly surrounded by pale green light."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 15: - strcat(info, " screams, \"I am the angel of Death!\""); - break; - case 16: - strcat(info, " screams, \"Only death can liberate you!\""); - break; - case 17: - strcat(info, " whispers, \"You'll know eternity soon...\""); - break; - case 18: - strcat(info, " screams, \"Don't try to resist!\""); - break; - } - break; // end Sigmund - - case MONS_IMP: // small demon - case MONS_WHITE_IMP: - case MONS_SHADOW_IMP: - if (one_chance_in(3)) - { - imp_taunt( monster ); - return (true); - } + else if (param == "PLAIN") + msg_type = MSGCH_PLAIN; else - { - switch (random2(11)) - { - case 0: - strcat(info, " laughs crazily."); - break; - case 1: - strcat(info, " grins evilly."); - break; - case 2: - strcat(info, " breathes smoke at you."); - break; - case 3: - strcat(info, " lashes his tail."); - break; - case 4: - strcat(info, " grinds his teeth."); - break; - case 5: - strcat(info, " sputters."); - break; - case 6: - strcat(info, " breathes steam at you."); - break; - case 7: - strcat(info, " spits at you."); - break; - case 8: - strcat(info, " disappears for a moment."); - break; - case 9: - strcat(info, " summons a swarm of flies."); - break; - case 10: - strcat(info, " picks up a beetle and eats it."); - break; - } - } - break; // end imp - - case MONS_TORMENTOR: // cruel devil - if (one_chance_in(10)) - { - demon_taunt( monster ); - return (true); - } - else - { - switch (random2(18)) - { - case 0: - strcat(info, " laughs crazily."); - break; - case 1: - strcat(info, " grins evilly."); - break; - case 2: - strcat(info, " says, \"I am all your nightmares come true.\""); - break; - case 3: - strcat(info, " says, \"I will show you what pain is.\""); - break; - case 4: - strcat(info, " shouts, \"I'll tear you apart.\""); - break; - case 5: - strcat(info, - " says, \"You will wish to die when I get to you.\""); - break; - case 6: - strcat(info, " says, \"I will drown you in your own blood.\""); - break; - case 7: - strcat(info, - " screams, \"You will die horribly!\""); - break; - case 8: - strcat(info, " says, \"I will eat your liver.\""); - break; - case 9: - strcat(info, " grins madly."); - break; - case 10: - strcat(info, " shouts, \"Prepare for my thousand needles of pain!\""); - break; - case 11: - strcat(info, - " says, \"I know a thousand and one ways to kill you.\""); - break; - case 12: - strcat(info, - " says, \"I'll show you my torture chamber!\""); - break; - case 13: - case 14: - strcat(info, - " says, \"I'll crush your bones, one by one.\""); - break; - case 15: - strcat(info, " says, \"I know your fate. It's pain.\""); - break; - case 16: - strcat(info, " says, \"Get ready! Throes await you.\""); - break; - case 17: - strcat(info, " grins malevolently."); - break; - } - } - break; // end tormentor - - case MONS_PANDEMONIUM_DEMON: // named demons - case MONS_GERYON: - case MONS_ASMODEUS: - case MONS_DISPATER: - case MONS_ANTAEUS: - case MONS_ERESHKIGAL: - case MONS_MNOLEG: - case MONS_LOM_LOBON: - case MONS_CEREBOV: - case MONS_GLOORX_VLOQ: - demon_taunt( monster ); - return (true); - - case MONS_PLAYER_GHOST: // ghost of unsuccesful player - switch (random2(29)) - { - case 0: - strcat(info, " laughs crazily."); - break; - - case 1: - strcat(info, " grins evilly."); - break; - - case 2: - strcat(info, " shouts, \"You will never get the ORB!\""); - break; - - case 3: // mv: ghosts are usually wailing, aren't ? - strcat(info, " says, \"I have seen your future. " - "And it's all used up.\""); - break; - case 4: - strcat(info, " makes a sound of rattling chains."); - break; - - case 5: - strcat(info, " says, \"They lied to you. " - "The Dungeon just goes down and down forever.\""); - break; - case 6: - strcat(info, " says, \"Do you think the gods " - "will protect you?\""); - break; - case 7: - strcat(info, " says, \"I was like you once.\""); - break; - case 8: - strcat(info, " says, \"Very impressive. But it won't help. " - "Nothing will.\""); - break; - case 9: - strcat(info, " whispers, \"They're coming for you...\""); - break; - case 10: - strcat(info, " says, \"What have you got " - "that I didn't have?\""); - break; - - case 11: - strcat(info, " wails."); - break; - - case 12: - strcat(info, " stares at you."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel cold.", MSGCH_WARN); - return (true); - - case 13: - strcat(info, " screams, \"You will join me soon!\""); - break; - case 14: - strcat(info, " wails, \"To die, to sleep, no more.\""); - break; //Hamlet - case 15: - strcat(info, - " screams, \"You must not succeed where I failed.\""); - break; - case 16: - strcat(info, - " screams, \"I'll kill anyone who wants the ORB.\""); - break; - case 17: - strcat(info, " whispers, \"Meet emptiness of death!\""); - break; - case 18: - strcat(info, " whispers, \"Death is liberation.\""); - break; - case 19: - strcat(info, - " whispers, \"Everlasting silence awaits you.\""); - break; - case 20: - strcat(info, - " screams, \"Don't try to defend. You have no chance!\""); - break; - case 21: - strcat(info, - " whispers, \"Death doesn't hurt. What you feel is life.\""); - break; - case 22: - strcat(info, " whispers, \"The ORB doesn't exist.\""); - break; - case 23: - strcat(info, " wails, \"Death is your only future.\""); - break; - case 24: - strcat(info, " says, \"The more you struggle now, " - "the more you'll suffer later.\""); - break; - case 25: - strcat(info, " whispers, \"Trust me. Just give in.\""); - break; - case 26: - strcat(info, " says very slowly, \"There's no hope.\""); - break; - case 27: - strcat(info, " lets out a mournful wail."); - break; - case 28: - strcat(info, " keens inconsolably."); - break; - } - break; // end players ghost - - case MONS_PSYCHE: // insane girl - switch (random2(20)) - { - case 0: - strcat(info, " smiles happily."); - break; - case 1: - strcat(info, " giggles crazily."); - break; - case 2: - strcat(info, " cries."); - break; - case 3: - strcat(info, " stares at you for a moment."); - break; - case 4: - strcat(info, " sings."); - break; - case 5: - strcat(info, - " says, \"Please, could you die a little faster?\""); - break; - case 6: - strcat(info, - " says, \"I'm a bad girl. But I can't do anything about it.\""); - break; - case 7: - strcat(info, - " screams, \"YOU ARE VIOLATING AREA SECURITY!\""); - break; - case 8: - strcat(info, " cries, \"I hate blood and violence.\""); - break; - case 9: - strcat(info, - " screams, \"Peace! Flowers! Freedom! Dead adventurers!\""); - break; - case 10: - strcat(info, - " says, \"I'm so lonely. Only corpses are my friends.\""); - break; - case 11: - strcat(info, " cries, \"You've killed my pet.\""); - break; - case 12: - strcat(info, - " cries, \"You want to steal my orb collection?!\""); - break; - case 13: - strcat(info, " sings a strange song."); - break; - case 14: - strcat(info, " bursts into tears."); - break; - case 15: - strcat(info, " sucks her thumb."); - break; - case 16: - strcat(info, - " whispers, \"Hold me, thrill me, kiss me, kill me.\""); - break; //(c) U2 ? - case 17: - strcat(info, " says, \"I'll kill you and take you home.\""); - break; - case 18: - strcat(info, - " shouts, \"Well, maybe I'm nutty, but who cares?\""); - break; - case 19: - strcat(info, - " shouts, \"I hope that you are sorry for that.\""); - break; - } - break; // end Psyche - - case MONS_DONALD: // adventurers hating competition - case MONS_WAYNE: - switch (random2(11)) - { - case 0: - strcat(info, " screams, \"Return home!\""); - break; - case 1: - strcat(info, " screams, \"The Orb is mine!\""); - break; - case 2: - strcat(info, " screams, \"Give me all your treasure!\""); - break; - case 3: - strcat(info, " screams, \"You will never get the Orb!\""); - break; - case 4: - strcat(info, " screams, \"I was here first!\""); - break; - case 5: - strcat(info, " frowns."); - break; - case 6: - strcat(info, " looks very upset."); - break; - case 7: - strcat(info, " screams, \"Get away or die!\""); - break; - case 8: - strcat(info, " screams, \"Die!\""); - break; - case 9: - strcat(info, " screams, \"First you have to pass me!\""); - break; - case 10: - strcat(info, " screams, \"I hate you!\""); - break; - } - break; // end Donald - - case MONS_MICHAEL: // spellcaster who wanted to be alone - switch (random2(11)) - { - case 0: - strcat(info, " looks very angry."); - break; - case 1: - strcat(info, " frowns."); - break; - case 2: - strcat(info, " screams, \"I want to be alone!\""); - break; - case 3: - strcat(info, " says, \"You are really a nuisance.\""); - break; - case 4: - strcat(info, - " screams, \"I wanted to be alone. And you...\""); - break; - case 5: - strcat(info, " screams, \"Get away! Or better yet, die!\""); - break; - case 6: - strcat(info, " mumbles some strange words."); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 7: - strcat(info, " points at you."); - mpr(info, MSGCH_MONSTER_SPELL); - canned_msg(MSG_YOU_RESIST); - return (true); - - case 8: - strcat(info, " shakes with wrath."); - break; - case 9: - strcat(info, " drinks a potion."); - break; - case 10: - strcat(info, " gestures wildly."); - msg_type = MSGCH_MONSTER_SPELL; - break; - } - break; // end Michael - - case MONS_ERICA: // wild tempered adventuress - switch (random2(12)) - { - case 0: - strcat(info, " screams, \"Die!\""); - break; - case 1: - strcat(info, " screams, \"Do you want it fast or slow?\""); - break; - case 2: - strcat(info, " looks angry."); - break; - case 3: - strcat(info, " drinks a potion."); - break; - case 4: - strcat(info, " says, \"I'm so much better than you.\""); - break; - case 5: - strcat(info, - " says, \"Fast and perfect. Such is my way of killing.\""); - break; - case 6: - strcat(info, " screams, \"Hurry! Death awaits!\""); - break; - case 7: - strcat(info, " laughs wildly."); - break; - case 8: - strcat(info, " screams, \"I'll never tell where it is!\""); - break; - case 9: - strcat(info, " screams, \"You'll never get it!\""); - break; - case 10: - strcat(info, " screams, \"Coming here was suicide!\""); - break; - case 11: - strcat(info, - " says, \"I love to fight, but killing is better.\""); - break; - } - break; // end Erica - - case MONS_JOSEPHINE: // ugly old witch looking for somone to kill - switch (random2(13)) - { - case 0: - case 1: - case 2: - strcat(info, " grins evilly."); - break; - case 3: - case 4: - strcat(info, " screams, \"I will kill you!\""); - break; - case 5: - strcat(info, " grinds her teeth."); - break; - case 6: - strcat(info, " grins malevolently."); - break; - case 7: - strcat(info, " laughs insanely."); - break; - case 8: - strcat(info, " screams, \"Die!\""); - break; - case 9: - strcat(info, - " screams, \"I have something special for you!\""); - break; - case 10: - strcat(info, - " screams, \"I'll use your head as decoration in my hut!\""); - break; - case 11: - strcat(info, " says, \"I'll make a rug of your skin.\""); - break; - case 12: - strcat(info, " says, \"How about some decapitation?\""); - break; - } - break; // end Josephine + match = false; - case MONS_HAROLD: // middle aged man, hired to kill you. He is in a hurry. - switch (random2(11)) - { - case 0: - strcat(info, " looks nervous."); - break; - case 1: - strcat(info, " screams, \"Hurry up!\""); - break; - case 2: - strcat(info, " screams, \"Could you die faster?\""); - break; - case 3: - strcat(info, - " says, \"Stand still. I'm trying to kill you.\""); - break; - case 4: - strcat(info, " screams, \"Die!\""); - break; - case 5: - strcat(info, " says, \"I hope you die soon!\""); - break; - case 6: - strcat(info, - " says, \"Only a few hits and it's over.\"."); - break; - case 7: - strcat(info, " says, \"You know, I'm in a hurry.\""); - break; - case 8: - strcat(info, " screams, \"I'll finish you soon!\""); - break; - case 9: - strcat(info, " screams, \"Don't delay it.\""); - break; - case 11: - strcat(info, " says, \"Mine is not to reason why. Mine's to do, yours to die.\"" ); - } - break; // end Harold - - // skilled warrior looking for some fame. More deads = more fame - case MONS_NORBERT: - switch (random2(13)) - { - case 0: - strcat(info, " smiles happily."); - break; - case 1: - strcat(info, " screams, \"Die, monster!\""); - break; - case 2: - strcat(info, " screams, \"I'm a hero!\""); - break; - case 3: - strcat(info, " shouts, \"YES! Another notch!\""); - break; - case 4: - strcat(info, " says, \"A pity your head will make such an ugly trophy.\""); - break; - case 5: - strcat(info, - " screams, \"Pray, because you'll die soon!\""); - break; - case 6: - strcat(info, - " asks \"Did you write a will? You should.\"."); - break; - case 7: - strcat(info, - " says, \"I love killing ugly monsters like you.\""); - break; - case 8: - strcat(info, " screams, \"Blood and destruction!\""); - break; - case 9: - strcat(info, " says, \"You know, it's an honour to die by my hand.\""); - break; - case 10: - strcat(info, " shouts, \"Your time has come!\""); - break; - case 11: - strcat(info, - " says, \"I'm sorry but you don't have a chance.\""); - break; - case 12: - strcat(info, - " says, \"Another dead monster... It must be my lucky day!\""); - break; - } - break; // end Norbert - - case MONS_JOZEF: // bounty hunter - switch (random2(14)) - { - case 0: - strcat(info, " looks satisfied."); - break; - case 1: - strcat(info, " screams, \"Die!\""); - break; - case 2: - strcat(info, " screams, \"At last I found you!\""); - break; - case 3: - strcat(info, " shouts, \"I'll get 500 for your head!\""); - break; - case 4: - strcat(info, - " says, \"You don't look worth it for that money.\""); - break; - case 5: - strcat(info, - " says, \"It's nothing personal. I'm paid for it!\""); - break; - case 6: - strcat(info, - " asks \"Did you write a testament? You should.\""); - break; - case 7: - strcat(info, " says, \"You are "); - strcat(info, you.your_name); - strcat(info, ", aren't you?.\""); - break; - case 8: - strcat(info, " says, \"I suppose that you are "); - strcat(info, you.your_name); - strcat(info, ". Sorry, if I'm wrong.\""); - break; - case 9: - strcat(info, " says, \"One dead "); - strcat(info, you.your_name); - strcat(info, ", 500 gold pieces. It's in my contract.\""); - break; - case 10: - strcat(info, " shouts, \"Your time has come!\""); - break; - case 11: - strcat(info, - " says, \"My job is sometimes very exciting. Sometimes...\""); - break; - case 12: - strcat(info, " says, \"I think I deserve my money.\""); - break; - case 13: - strcat(info, - " screams, \"Die! I've got more contracts today.\""); - break; - } - break; // end Jozef - - case MONS_AGNES: // she is trying to get money and treasure - switch (random2(10)) - { - case 0: - strcat(info, " screams, \"Give me all your money!\""); - break; - case 1: - strcat(info, " screams, \"All treasure is mine!\""); - break; - case 2: - strcat(info, " screams, \"You'll never get my money!\""); - break; - case 3: - strcat(info, " grins evilly."); - break; - case 4: - strcat(info, - " screams, \"Give me everything and get away!\""); - break; - case 5: - strcat(info, - " says, \"I need a new robe. I'll buy it with your money.\""); - break; - case 6: - strcat(info, - " screams, \"I want your rings! And amulets! And... EVERYTHING!\""); - break; - case 7: - strcat(info, - " screams, \"I hate dirty adventurers like you.\""); - break; - case 8: - strcat(info, " says, \"How can you wear that ugly dress?\""); - break; - case 9: - strcat(info, " screams, \"Die, beast!\""); - break; - } - break; // end Agnes - - case MONS_MAUD: // warrior princess looking for sword "Entarex" - switch (random2(11)) - { - case 0: - strcat(info, " screams, \"Submit or die!\""); - break; - case 1: - strcat(info, " screams, \"Give me \"Entarex\"!\""); - break; - case 2: - strcat(info, - " screams, \"If you give me \"Entarex\", I'll let you live!\""); - break; - case 3: - strcat(info, " frowns."); - break; - case 4: - strcat(info, " looks upset."); - break; - case 5: - strcat(info, " screams, \"You can't face my power!\""); - break; - case 6: - strcat(info, - " screams, \"Give me that sword! Immediately!\""); - break; - case 7: - strcat(info, - " screams, \"Your life or \"Entarex\"! You must choose.\""); - break; - case 8: - strcat(info, " screams, \"I want it!\""); - break; - case 9: - strcat(info, " screams, \"Die, you thief!\""); - break; - case 10: - // needed at least one in here to tie to the amnesia - // scroll reference -- bwr - strcat(info, " asks \"Will you think of me as you die?\""); - break; - } - break; // end Maud - - // wizard looking for bodyparts as spell components - case MONS_FRANCIS: - switch (random2(15)) - { - case 0: - strcat(info, - " says, \"You've nice eyes. I could use them.\""); - break; - case 1: - strcat(info, " says, \"Excuse me, but I need your head.\""); - break; - case 2: - strcat(info, " says, \"I only need a few of your organs!\""); - break; - case 3: - strcat(info, " ponders the situation."); - break; - case 4: - strcat(info, " looks for a scalpel."); - break; - - case 5: - simple_monster_message( monster, " casts a spell", - MSGCH_MONSTER_SPELL ); - - strcat(info, "'s hands start to glow with a soft light."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 6: - strcat(info, " says, \"This won't hurt a bit.\""); - break; - case 7: - strcat(info, " throws something at you."); - break; - case 8: - strcat(info, " says, \"I want you in my laboratory!\""); - break; - case 9: - strcat(info, " says, \"What about a little dissection?\""); - break; - case 10: - strcat(info, - " says, \"I have something special for you.\""); - break; - case 11: - strcat(info, - " screams, \"Don't move! I want to cut off your ear!\""); - break; - case 12: - strcat(info, - " says, \"What about your heart? Do you need it?\""); - break; - case 13: - strcat(info, - " says, \"Did you know that corpses are an important natural resource?\""); - break; - case 14: - strcat(info, - " says, \"Don't worry, I'll only take what I need.\""); - break; - } - break; // end Francis - - case MONS_RUPERT: // crazy adventurer - switch (random2(11)) - { - case 0: - strcat(info, " says, \"You are a monster, aren't you?\""); - break; - case 1: - strcat(info, " screams, \"Die, monster!\""); - break; - case 2: - strcat(info, " screams, \"Give me the Holy Grail!\""); - break; - case 3: - strcat(info, " screams, \"Red! No, blue!\""); - break; - case 4: - strcat(info, " looks confused."); - break; - case 5: - strcat(info, " looks excited."); - break; - case 6: - strcat(info, " shouts, \"I'm a great and powerful hero!\""); - break; - case 7: - strcat(info, - " screams, \"Get ready! I'll kill you! Or something like it...\""); - break; - case 8: - strcat(info, - " says, \"My Mom always said, kill them all.\""); - break; - case 9: - strcat(info, - " screams, \"You killed all those lovely monsters, you murderer!\""); - break; - case 10: - strcat(info, " screams, \"Hurray!\""); - break; - } - break; // end Rupert - - case MONS_NORRIS: // enlighten but crazy man - switch (random2(24)) - { - case 0: - strcat(info, " sings \"Hare Rama, Hare Krishna!\""); - break; - case 1: - strcat(info, " smiles at you."); - break; - case 2: - strcat(info, - " says, \"After death you'll find inner peace.\""); - break; - case 3: - strcat(info, - " says, \"Life is just suffering. I'll help you.\""); - break; - case 4: - strcat(info, " is surrounded with aura of peace."); - break; - case 5: - strcat(info, " looks very balanced."); - break; - case 6: - strcat(info, " says, \"Don't resist. I'll do it for you.\""); - break; - case 7: - strcat(info, " screams, \"Enter NIRVANA! Now!\""); - break; - case 8: - strcat(info, " says, \"Death is just a liberation!\""); - break; - case 9: - strcat(info, - " says, \"Feel free to die. It's great thing.\""); - break; - case 10: - strcat(info, " says, \"OHM MANI PADME HUM!\""); - break; - - case 11: - strcat(info, " mumbles some mantras."); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 12: - strcat(info, " says, \"Breathe deeply.\""); - break; - case 13: - strcat(info, " screams, \"Love! Eternal love!\""); - break; - case 14: - strcat(info, - " screams, \"Peace! I bring you eternal peace!\""); - break; - case 15: - strcat(info, - " sighs \"Enlightenment is such responsibility.\""); - break; - case 16: - strcat(info, " looks relaxed."); - break; - case 17: - strcat(info, " screams, \"Free your soul! Die!\""); - break; - case 18: - strcat(info, " screams, \"Blow your mind!\""); - break; - case 19: - strcat(info, - " says, \"The Orb is only a myth. Forget about it.\""); - break; - case 20: - strcat(info, " says, \"It's all maya.\""); - break; - case 21: - strcat(info, " says, \"Drop out!\""); - break; - case 22: - strcat(info, - " sings, \"Peace now, freedom now! Peace now, freedom now!\""); - break; - case 23: - strcat(info, " says, \"This is called Combat Meditation.\""); - break; - } - break; // end Norris - - case MONS_MARGERY: // powerful sorceress, guarding the ORB - switch (random2(22)) - { - case 0: - strcat(info, " says, \"You are dead.\""); - break; - case 1: - strcat(info, " looks very self-confident."); - break; - case 2: - strcat(info, " screams, \"You must be punished!\""); - break; - case 3: - strcat(info, " screams, \"You can't withstand my power!\""); - break; - - case 4: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); - - strcat(info, " is surrounded with aura of power."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 5: - strcat(info, "'s eyes start to glow with a red light."); - break; - case 6: - strcat(info, - "'s eyes start to glow with a green light."); - break; - case 7: - strcat(info, "'s eyes start to glow with a blue light."); - break; - case 8: - strcat(info, " screams, \"All trespassers must die!\""); - break; - case 9: - strcat(info, " says, \"Die!\""); - break; - case 10: - strcat(info, " screams, \"You'll have to get past me!\""); - break; - - case 11: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); - - strcat(info, " becomes transparent for a moment."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 12: - strcat(info, " gestures."); - msg_type = MSGCH_MONSTER_SPELL; - break; - - case 13: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); - - strcat(info, "'s hands start to glow."); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - - case 14: - strcat(info, " screams, \"Ergichanteg reztahaw!\""); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel really bad.", MSGCH_WARN); - return (true); - - case 15: - strcat(info, " screams, \"You are doomed!\""); - break; - case 16: - strcat(info, " screams, \"Nothing can help you.\""); - break; - case 17: - strcat(info, " screams, \"Death is my middle name!\""); - break; - - case 18: - strcat(info, " gestures."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel doomed.", MSGCH_WARN); - return (true); - - case 19: - strcat(info, " gestures."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel weakened.", MSGCH_WARN); - return (true); - - case 20: - strcat(info, " throws some purple powder towards you."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel cursed.", MSGCH_WARN); - return (true); - - case 21: - strcat(info, - " screams, \"The ORB is only a tale, but I will kill you anyway!"); - break; - } - break; // end Margery - - case MONS_IJYB: // twisted goblin - switch (random2(14)) - { - case 0: - strcat(info, " screams, \"Die!\""); - break; - case 1: - strcat(info, " screams, \"Me kill you!\""); - break; - case 2: - strcat(info, " screams, \"Me stronger than you!\""); - break; - case 3: - case 4: - strcat(info, " grins evilly."); - break; - case 5: - strcat(info, " screams, \"It's all mine!\""); - break; - case 6: - strcat(info, " screams, \"Get away!\""); - break; - case 7: - strcat(info, " screams, \"Level is mine! All mine!\""); - break; - case 8: - strcat(info, " screams, \"I cut your head off!\""); - break; - case 9: - strcat(info, " screams, \"I dance on your bones!\""); - break; - case 10: - strcat(info, " screams, \"Me very upset!\""); - break; - case 11: - strcat(info, " screams, \"You nasty! Big nasty!\""); - break; - case 12: - strcat(info, " screams, \"No! No, no, no, no!\""); - break; - case 13: - strcat(info, " screams, \"I no like you!\""); - break; - } - break; // end IJYB - - case MONS_BLORK_THE_ORC: // unfriendly orc - switch (random2(21)) - { - case 0: - strcat(info, " screams, \"I don't like you!\""); - break; - case 1: - strcat(info, " screams, \"I'm going to kill you!\""); - break; - case 2: - strcat(info, - " screams, \"I'm much stronger than you!\""); - break; - case 3: - case 4: - strcat(info, " grins evilly."); - break; - case 5: - strcat(info, " frowns."); - break; - case 6: - case 7: - case 8: - case 9: - strcat(info, " looks angry."); - break; - case 10: - strcat(info, - " screams, \"I'll eat your brain! And then I'll vomit it back up!\""); - break; - case 11: - strcat(info, - " screams, \"You are the ugliest creature I've ever seen!\""); - break; - case 12: - strcat(info, " screams, \"I'll cut your head off!\""); - break; - case 13: - strcat(info, " screams, \"I'll break your legs!\""); - break; - case 14: - strcat(info, " screams, \"I'll break your arms!\""); - break; - case 15: - strcat(info, - " screams, \"I'll crush all your ribs! One by one!\""); - break; - case 16: - strcat(info, - " screams, \"I'll make a cloak from your skin!\""); - break; - case 17: - strcat(info, - " screams, \"I'll decorate my home with your organs!\""); - break; - case 18: - strcat(info, " screams, \"Die!\""); - break; - case 19: - strcat(info, - " screams, \"I'll cover the dungeon with your blood!\""); - break; - case 20: - strcat(info, " screams, \"I'll drink your blood! Soon!\""); - break; - } - break; // end Blork - - case MONS_EROLCHA: // ugly ogre - switch (random2(11)) - { - case 0: - strcat(info, " tries to grin evilly."); - break; - case 1: - strcat(info, " screams, \"Eat!\""); - break; - case 2: - strcat(info, " screams, \"Stand! Erolcha hit you!\""); - break; - case 3: - strcat(info, " screams, \"Blood!\""); - break; - case 4: - strcat(info, " screams, \"Erolcha kill you!\""); - break; - case 5: - strcat(info, - " screams, \"Erolcha crush your head!\""); - break; - case 6: - strcat(info, " roars."); - break; - case 7: - strcat(info, " growls."); - break; - case 8: - strcat(info, " screams, \"Lunch!\""); - break; - case 9: - strcat(info, " screams, \"Erolcha happy to kill you!\""); - break; - case 10: - strcat(info, " screams, \"Erolcha angry!\""); - break; - } - break; // end Erolcha - - case MONS_URUG: // orc hired to kill you - switch (random2(11)) - { - case 0: - strcat(info, " grins evilly."); - break; - case 1: - strcat(info, " screams, \"Die!\""); - break; - case 2: - strcat(info, " screams, \"I'm going to kill you! Now!\""); - break; - case 3: - strcat(info, " screams, \"Blood and destruction!\""); - break; - case 4: - strcat(info, - " sneers, \"Innocent? I'll kill you anyway.\""); - break; - case 5: - strcat(info, - " screams, \"I'll get 30 silver pieces for your head!\""); - break; - case 6: - strcat(info, " roars."); - break; - case 7: - strcat(info, " howls with blood-lust."); - break; - case 8: - strcat(info, " screams, \"You are already dead.\""); - break; - case 9: - strcat(info, " says, \"Maybe you aren't "); - strcat(info, you.your_name); - strcat(info, ". It doesn't matter.\""); - break; - case 10: - strcat(info, " screams, \"I love blood!\""); - break; - } - break; // end Urug - - case MONS_SNORG: // troll - switch (random2(16)) - { - case 0: - strcat(info, " grins."); - break; - case 1: - case 2: - case 3: - strcat(info, " smells terrible."); - break; - case 4: - case 5: - case 6: - strcat(info, " looks very hungry."); - break; - case 7: - strcat(info, " screams, \"Snack!\""); - break; - case 8: - strcat(info, " roars."); - break; - case 9: - strcat(info, " says, \"Food!\""); - break; - case 10: - strcat(info, " screams, \"Snorg hungry!\""); - break; - case 11: - strcat(info, " screams, \"Snorg very, very hungry!\""); - case 12: - strcat(info, " says, \"Snorg eat you.\""); - break; - case 13: - strcat(info, " says, \"You food?\""); - break; - case 14: - strcat(info, " says, \"Yum, yum.\""); - break; - case 15: - strcat(info, " burps."); - break; - } - break; // end Snorg - - case MONS_XTAHUA: // ancient dragon - switch (random2(13)) - { - case 0: - strcat(info, " roars, \"DIE, PUNY ONE!\""); - break; - case 1: - strcat(info, " growls, \"YOU BORE ME SO.\""); - break; - case 2: - strcat(info, " rumbles, \"YOU'RE BARELY A SNACK.\""); - break; - case 3: - strcat(info, " roars, \"I HATE BEING BOTHERED!\""); - break; - case 4: - strcat(info, " roars, \"I HOPE YOU'RE TASTY!\""); - break; - case 5: - strcat(info, " roars, \"BAH! BLOODY ADVENTURERS.\""); - break; - case 6: - strcat(info, " roars, \"FACE MY WRATH!\""); - break; - case 7: - strcat(info, " glares at you."); - break; - case 8: - strcat(info, - " roars, \"COMING HERE WAS YOUR LAST MISTAKE!\""); - break; - case 9: - strcat(info, - " roars, \"I'VE KILLED HUNDREDS OF ADVENTURERS!\""); - break; - case 10: - case 11: - case 12: - strcat(info, " roars horribly."); - mpr(info, MSGCH_TALK); - mpr("You are afraid.", MSGCH_WARN); - return (true); - } - break; // end Xtahua - - case MONS_BORIS: // ancient lich - switch (random2(24)) - { - case 0: - strcat(info, " says, \"I didn't invite you.\""); - break; - case 1: - strcat(info, " says, \"You can't imagine my power.\""); - break; - case 2: - strcat(info, - " says, \"Orb? You want the Orb? You'll never get it.\""); - break; - - case 3: - strcat(info, " says, \"The world, the flesh, and the devil.\""); - break; - - case 4: - strcat(info, " gestures."); - break; - - case 5: - strcat(info, " stares at you."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel drained.", MSGCH_WARN); - return (true); - - case 6: - strcat(info, " stares at you."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel weakened.", MSGCH_WARN); - return (true); - - case 7: - strcat(info, " stares at you."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You feel troubled.", MSGCH_WARN); - return (true); - - case 8: - strcat(info, " says \"Magic. You know nothing about it.\""); - break; - - case 9: - strcat(info, " says, \"My power is unlimited.\""); - break; - case 10: - strcat(info, " says, \"You can't kill me. I'm immortal.\""); - break; - - case 11: - strcat(info, " casts a spell."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("Your equipment suddenly seems to weigh more.", MSGCH_WARN); - return (true); - - case 12: - strcat(info, - " says, \"I know the secret of eternal life. Do you?\""); - break; - case 13: - strcat(info, " says, \"I'll be back.\""); - break; - - case 14: - strcat(info, " casts a spell."); - mpr(info, MSGCH_MONSTER_SPELL); - canned_msg( MSG_YOU_RESIST ); - return (true); - - case 15: - strcat(info, " casts a spell."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("Suddenly you are surrounded with a pale green light.", MSGCH_WARN); - return (true); - - case 16: - strcat(info, " casts a spell."); - mpr(info, MSGCH_MONSTER_SPELL); - mpr("You have a terrible headache.", MSGCH_WARN); - return (true); - - case 17: - strcat(info, - " says, \"I know your future. Your future is death.\""); - break; - case 18: - strcat(info, " says, \"Who wants to live forever? Me.\""); - break; - case 19: - strcat(info, " laughs."); - break; - case 20: - strcat(info, " says, \"Join the legion of my servants.\""); - break; - case 21: - strcat(info, " says, \"There's only one solution for you. To die.\""); - break; - case 22: - strcat(info, " says, \"You can never win.\""); - break; - case 23: - simple_monster_message( monster, " casts a spell.", - MSGCH_MONSTER_SPELL ); - - strcat( info, " speeds up." ); - msg_type = MSGCH_MONSTER_ENCHANT; - break; - } - break; // end BORIS - - - case MONS_DEATH_COB: - if (one_chance_in(2000)) - { - mpr("The death cob makes a corny joke.", MSGCH_TALK); - return (true); - } - return (false); - - case MONS_KILLER_KLOWN: // Killer Klown - guess! - switch (random2(10)) - { - case 0: - strcat(info, " giggles crazily."); - break; - case 1: - strcat(info, " laughs merrily."); - break; - case 2: - strcat(info, " beckons to you."); - break; - case 3: - strcat(info, " does a flip."); - break; - case 4: - strcat(info, " does a somersault."); - break; - case 5: - strcat(info, " smiles at you."); - break; - case 6: - strcat(info, " grins with merry abandon."); - break; - case 7: - strcat(info, " howls with blood-lust!"); - break; - case 8: - strcat(info, " pokes out its tongue."); - break; - case 9: - strcat(info, " says, \"Come and play with me!\""); - break; - } - break; // end Killer Klown + if (match) + line = line.substr(pos + 1); + } - default: - strcat(info, - " says, \"I don't know what to say. It's a bug.\""); - break; - } // end monster->type - monster type switch - } // end default + mpr(line.c_str(), msg_type); + } - mpr(info, msg_type); return true; } // end mons_speaks = end of routine diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index 574843070b..d2e574b950 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -2134,11 +2134,34 @@ static void handle_nearby_ability(monsters *monster) return; } +#define MON_SPEAK_CHANCE 21 + if (mons_class_flag(monster->type, M_SPEAKS) - && monster->behaviour != BEH_WANDER && one_chance_in(21)) + && monster->behaviour != BEH_WANDER && one_chance_in(MON_SPEAK_CHANCE)) { mons_speaks(monster); } + else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED) + { + // Non-humanoid-ish monsters have a low chance of speaking + // wihtout the M_SPEAKS flag, to give the dungeon some + // atmosphere/flavor. + int chance = MON_SPEAK_CHANCE * 4; + + // Band members are a lot less likely to speak, since there's + // a lot of them. + if (testbits(monster->flags, MF_BAND_MEMBER)) + chance *= 10; + + // However, confused and fleeing monsters are more interesting. + if (monster->behaviour == BEH_FLEE) + chance /= 2; + if (monster->has_ench(ENCH_CONFUSION)) + chance /= 2; + + if (one_chance_in(chance)) + mons_speaks(monster); + } switch (monster->type) { diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc index dd080ef687..53cd62555d 100644 --- a/crawl-ref/source/view.cc +++ b/crawl-ref/source/view.cc @@ -40,12 +40,12 @@ #include "cio.h" #include "cloud.h" #include "clua.h" +#include "database.h" #include "debug.h" #include "delay.h" #include "direct.h" #include "dungeon.h" #include "initfile.h" -#include "insult.h" #include "itemprop.h" #include "luadgn.h" #include "macro.h" @@ -744,95 +744,202 @@ inline static void beogh_follower_convert(monsters *monster) } } -static void handle_monster_shouts(monsters* monster) +static void handle_monster_shouts(monsters* monster, bool force = false) { - if (you.turn_is_over - && mons_shouts(monster->type) != S_SILENT - && random2(30) >= you.skills[SK_STEALTH]) + if (!force + && (!you.turn_is_over || random2(30) < you.skills[SK_STEALTH])) + return; + + // Get it once, since monster might be S_RANDOM, in which case + // mons_shouts() will return a different value every time. + shout_type type = mons_shouts(monster->type); + + // Silent monsters can give noiseless "visual shouts" if the + // player can see them, in which case silence isn't checked for. + if (mons_friendly(monster) + || (type == S_SILENT && !player_monster_visible(monster)) + || (type != S_SILENT && (silenced(you.x_pos, you.y_pos) + || silenced(monster->x, monster->y)))) + return; + + int noise_level = 8; + std::string default_msg_key; + + switch (type) { - int noise_level = 8; + case NUM_SHOUTS: + case S_RANDOM: + default_msg_key = "__BUGGY"; + break; + case S_SILENT: + default_msg_key = ""; + noise_level = 0; + break; + case S_SHOUT: + default_msg_key = "__SHOUT"; + break; + case S_BARK: + default_msg_key = "__BARK"; + break; + case S_SHOUT2: + default_msg_key = "__TWO_SHOUTS"; + noise_level = 12; + break; + case S_ROAR: + default_msg_key = "__ROAR"; + noise_level = 12; + break; + case S_SCREAM: + default_msg_key = "__SCREAM"; + break; + case S_BELLOW: + default_msg_key = "__BELLOW"; + break; + case S_SCREECH: + default_msg_key = "__SCREECH"; + break; + case S_BUZZ: + default_msg_key = "__BUZZ"; + break; + case S_MOAN: + default_msg_key = "__MOAN"; + break; + case S_WHINE: + default_msg_key = "__WHINE"; + break; + case S_CROAK: + default_msg_key = "__CROAK"; + break; + case S_GROWL: + default_msg_key = "__GROWL"; + break; + case S_HISS: + default_msg_key = "__HISS"; + noise_level = 4; // not very loud -- bwr + break; - if (!mons_friendly(monster) - && (!silenced(you.x_pos, you.y_pos) - && !silenced(monster->x, monster->y))) - { - if (mons_is_demon( monster->type ) && coinflip()) - { - if (monster->type == MONS_IMP - || monster->type == MONS_WHITE_IMP - || monster->type == MONS_SHADOW_IMP) - { - imp_taunt( monster ); - } - else - { - demon_taunt( monster ); - } - } - else - { - std::string msg = "You hear "; - switch (mons_shouts(monster->type)) - { - case S_SILENT: - case NUM_SHOUTS: - case S_RANDOM: - msg += "buggy behaviour!"; - break; - case S_SHOUT: - msg += "a shout!"; - break; - case S_BARK: - msg += "a bark!"; - break; - case S_SHOUT2: - msg += "two shouts!"; - noise_level = 12; - break; - case S_ROAR: - msg += "a roar!"; - noise_level = 12; - break; - case S_SCREAM: - msg += "a hideous shriek!"; - break; - case S_BELLOW: - msg += "a bellow!"; - break; - case S_SCREECH: - msg += "a screech!"; - break; - case S_BUZZ: - msg += "an angry buzzing noise."; - break; - case S_MOAN: - msg += "a chilling moan."; - break; - case S_WHINE: - msg += "an irritating high-pitched whine."; - break; - case S_CROAK: - if (coinflip()) - msg += "a loud, deep croak!"; - else - msg += "a croak."; - break; - case S_GROWL: - msg += "an angry growl!"; - break; - case S_HISS: - msg += "an angry hiss!"; - noise_level = 4; // not very loud -- bwr - break; - } - msg::streams(MSGCH_SOUND) << msg << std::endl; - } - } + // Loudness setting for shouts that are only defined in dat/shout.txt + case S_VERY_SOFT: + default_msg_key = ""; + noise_level = 4; + break; + case S_SOFT: + default_msg_key = ""; + noise_level = 6; + break; + case S_NORMAL: + default_msg_key = ""; + noise_level = 8; + break; + case S_LOUD: + default_msg_key = ""; + noise_level = 10; + break; + case S_VERY_LOUD: + default_msg_key = ""; + noise_level = 12; + break; + } - noisy( noise_level, monster->x, monster->y ); + // Use get_monster_data(monster->type) to bypass mon_shouts() + // replacing S_RANDOM with a random value. + if (mons_is_demon( monster->type ) && coinflip() + && (type != S_SILENT || + get_monster_data(monster->type)->shouts == S_RANDOM)) + { + noise_level = 8; + default_msg_key = "__DEMON_TAUNT"; } + + std::string msg, suffix; + std::string key = mons_type_name(monster->type, DESC_PLAIN); + + // Pandemonium demons have random names, so use "pandemonium lord" + if (monster->type == MONS_PANDEMONIUM_DEMON) + key = "pandemonium lord"; + // Search for player ghost shout by the ghost's class. + else if (monster->type == MONS_PLAYER_GHOST) + { + const ghost_demon &ghost = *(monster->ghost); + std::string ghost_class = get_class_name(ghost.values[GVAL_CLASS]); + + key = ghost_class + " player ghost"; + + default_msg_key = "player ghost"; + } + + // Tries to find an entry for "name seen" or "name unseen", + // and if no such entry exists then looks simply for "name". + if (player_monster_visible(monster)) + suffix = " seen"; + else + suffix = " unseen"; + + msg = getShoutString(key, suffix); + + if (msg == "__DEFAULT" || msg == "__NEXT") + msg = getShoutString(default_msg_key, suffix); + else if (msg == "") + { + // See if there's a shout for all monsters using the + // same glyph/symbol + std::string glyph_key = "'"; + + // Database keys are case-insensitve. + if (isupper(mons_char(monster->type))) + glyph_key += "cap-"; + + glyph_key += mons_char(monster->type); + glyph_key += "'"; + msg = getShoutString(glyph_key, suffix); + + if (msg == "" || msg == "__DEFAULT") + msg = getShoutString(default_msg_key, suffix); + } + + if (default_msg_key == "__BUGGY") + msg::streams(MSGCH_SOUND) << "You hear something buggy!" + << std::endl; + else if ((msg == "" || msg == "__NONE") + && mons_shouts(monster->type) == S_SILENT) + ; // No "visual shout" defined for silent monster, do nothing + else if (msg == "") + { + msg::streams(MSGCH_DIAGNOSTICS) + << "No shout entry for default shout type '" + << default_msg_key << "'" << std::endl; + msg::streams(MSGCH_SOUND) << "You hear something buggy!" + << std::endl; + } + else if (msg == "__NONE") + { + msg::streams(MSGCH_DIAGNOSTICS) + << "__NONE returned as shout for non-silent monster '" + << default_msg_key << "'" << std::endl; + msg::streams(MSGCH_SOUND) << "You hear something buggy!" + << std::endl; + } + else + { + msg = do_mon_str_replacements(msg, monster); + + if (mons_shouts(monster->type) == S_SILENT) + msg::streams(MSGCH_TALK) << msg << std::endl; + else + msg::streams(MSGCH_SOUND) << msg << std::endl; + } + + if (noise_level > 0) + noisy( noise_level, monster->x, monster->y ); } +#ifdef WIZARD +void force_monster_shout(monsters* monster) +{ + handle_monster_shouts(monster, true); +} +#endif + inline static bool update_monster_grid(const monsters *monster) { const int ex = monster->x - you.x_pos + 9; -- cgit v1.2.3-54-g00ecf