summaryrefslogblamecommitdiffstats
path: root/crawl-ref/source/mon-speak.cc
blob: 59e17efaeb7daa28f6d93f29b52013f2f2f15ece (plain) (tree)
1
2
3
4
5
6
7
8
  
                            
                                                     


                   
 
                      



                   
                    
 


                    
                     
                     
                  
                 
                  
                    
                     
                 
                   
                     
                  
                  
                  

                 


                                                                         





                                                                               
 


                          
                          


                            
                                     

                                  































                                                                     
                                    
                                                          
                         

                                                      
                                                                           
                                            
                                                                    

                                                                
                                                                      

                                                                    
                                                    
                                                                            
     

               
 
                                                                             
                                                                    






































                                                                   


                                                          






                                                                              
                                                 



                                                                  
 


                                            
             
















                                                                             
                                                                              













                                                                               

                                                                    

                                                                         
                                                                     
 
                     
                   
 
                                         

                                     








                                                    
 
                                                                    
 
                                     


                                   
 







                                              
 
                                                                
 
                                 


                               
 

                      
     




                                                            
 
                             

                           

     



                                           

 
                                                                              
                                                     

                                                                 

                                                                   
 


                                 
                                                                             



                                                              


                                            
             
                                                                         
                                                            














                                                                        
                                                                              










                     

                                                                       
                                                                   
                                                                             
 
                                                 
                                                        
 





                                                          
 
                                         
                                                                             
 

                                       
                                                      
 
               

 





















                                                                              
                                             












                                                                     


                                                    














                                                                       
                                     
                                   
 
                                                 
 
                                                                     
                     
                                              





                                                                          

                     
     
                                                                      
                                                                   


                                                                        
                               
                          










                                                                       
 
                                                
                               
                           
 


                                                                       
 



                                                               
 
                                      
                           
     
                                                                        
                           
 
                                      
     
                                                       
                                       

                                      
 
                                 

                                      

                                       

                       
                                       
     
 
                 
                                       
 
                                                               

                                                                         
 
                                                                         

                                                                         
                                                                  
                                                                          



                                                                          
     












                                                                       

     



                                                           
                                                                           
                                                                            


                                                                          
     

                                        
            
                                             
     

        
                             
                                           
                                  
                                           

                                      
     
 
                     
     








                                                                            
     

      
                                           
                                              
                                                        
                                                                          
                                             






                                                                               

                    
                                 
                                           
     
                                                 

                                                         
                                                     
     

                                                                   
                                                                      

                                                                       
     
        
     


































                                                                              
 
                                                               






                                                                               


                                                                             
                                                                           
         
                                                                        
                                                                            
                                                    
         

                        
         
                                                                             
                                                                            
                                                    
         
     
 


                                                            
     
                                         
                                                                             

                                                                       

     




                                                                   



                                                      
                       
     
 
                                                                       
                                                


                                                        
                                                                  
                                         
                                       
     







                                              
                                                                          
                                                             
     
 
                        



                                                      
                       
     
 




                                                                    
                                               

                                                              
     
                                                    
     


                                                                     
         



                                                            


            



                                                            

         
                                                                   
     






                                                                
     
                                                   
     
 
                                       
     
                                                                            

                                                                       
     
 
                        



                                                      
                       
     
 
                                                                      
                                                                 
                                                                      
                                   
     

                                                                        
                                                      
         
                                              

                                                                            
                                                    
 
                                                                      
                                                                  
             

                                                  

                                                                            
                                                                      
 
                                                        



                                                                  
                                   
                 
 

                                     
 
                           
             
                                                           
                                                              

                                       

                                                                            
                                                    

         





                                                     
                       
     
 




                                                                       
                       
     
 









                                                                             
                                                                
 
 
                                                               

                                                                         
                            
                       
 
                             
 
                                                        

                                                                    
 
                                                                 
 
                                                       

                                    
 

                                                                      
                                                                    


                                                                   
                                                               
                                             
 
                               
                                                    
 
                                     


                                        
         
                              


                                        
                                                                           


                                       


                                                                             
             
                                                                   

                                                                        

                                                   
             
                                                                     

                                                                          

                                       
                
                              
 


                                            
 


                                                              
                                                                
                                                                      
                                         
                                                                                
                                              
                                                                     
                   
                                                                        
                                  
            

                                     
                                               
                                        
         
     
                     
 
/*
 *  File:       mon-speak.cc
 *  Summary:    Functions to handle speaking monsters
 */

#include "AppHdr.h"

#include "mon-speak.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <algorithm>

#include "externs.h"

#include "beam.h"
#include "cluautil.h"
#include "database.h"
#include "debug.h"
#include "dlua.h"
#include "ghost.h"
#include "message.h"
#include "mon-util.h"
#include "jobs.h"
#include "player.h"
#include "religion.h"
#include "state.h"
#include "stuff.h"
#include "areas.h"
#include "view.h"

// Try the exact key lookup along with the entire prefix list.
// If that fails, start ignoring hostile/religion/silence, in that order,
// first skipping hostile, then hostile *and* religion, then all three.
static std::string __try_exact_string(const std::vector<std::string> &prefixes,
                                      const std::string &key,
                                      bool ignore_hostile  = false,
                                      bool ignore_related  = false,
                                      bool ignore_religion = false,
                                      bool ignore_silenced = false)
{
    bool hostile  = false;
    bool related  = false;
    bool religion = false;
    bool silenced = false;

    std::string prefix = "";
    std::string msg = "";
    const int size = prefixes.size();
    for (int i = 0; i < size; i++)
    {
         if (prefixes[i] == "hostile")
         {
             if (ignore_hostile)
                 continue;
             hostile = true;
         }
         else if (prefixes[i] == "related")
         {
             if (ignore_related)
                 continue;
             related = true;
         }
         else if (prefixes[i] == "silenced")
         {
             if (ignore_silenced)
                 continue;
             silenced = true;
         }
         else if (prefixes[i] == "beogh" || prefixes[i] == "good god"
                  || prefixes[i] == "evil god")
         {
             if (ignore_religion)
                 continue;
             religion = true;
         }
         prefix += prefixes[i];
         prefix += " ";
    }
    msg = getSpeakString(prefix + key);

    if (msg.empty())
    {
        if (hostile) // skip hostile
            msg = __try_exact_string(prefixes, key, true);
        else if (related)
        {
            if (religion) // skip hostile and religion
                msg = __try_exact_string(prefixes, key, true, false, true);
            else // skip hostile and related
                msg = __try_exact_string(prefixes, key, true, true);
        }
        else if (religion) // skip hostile, related and religion
            msg = __try_exact_string(prefixes, key, true, true, true);
        // 50% use non-verbal monster speech,
        // 50% try for more general silenced monster message instead
        else if (silenced && coinflip()) // skip all
            msg = __try_exact_string(prefixes, key, true, true, true, true);
    }
    return msg;
}

static bool _invalid_msg(const std::string &msg, bool no_player, bool no_foe,
                         bool no_foe_name, bool no_god, bool unseen)
{
    if (no_player
        && (msg.find("@player") != std::string::npos
            || msg.find("@Player") != std::string::npos
            || msg.find(":You") != std::string::npos))
    {
        return (true);
    }

    if (no_player)
    {
        std::vector<std::string> lines = split_string("\n", msg);
        for (unsigned int i = 0; i < lines.size(); i++)
        {
            if (starts_with(lines[i], "You")
                || ends_with(lines[i], "you."))
            {
                return (true);
            }
        }
    }

    if (no_foe && (msg.find("@foe") != std::string::npos
                   || msg.find("@Foe") != std::string::npos
                   || msg.find("foe@") != std::string::npos
                   || msg.find("@species") != std::string::npos))
    {
        return (true);
    }

    if (no_god && (msg.find("_god@") != std::string::npos
                   || msg.find("@god_") != std::string::npos))
    {
        return (true);
    }

    if (no_foe_name && msg.find("@foe_name@") != std::string::npos)
        return (true);

    if (unseen && msg.find("VISUAL") != std::string::npos)
        return (true);

    return (false);
}

static std::string _try_exact_string(const std::vector<std::string> &prefixes,
                                     const std::string &key,
                                     bool no_player, bool no_foe,
                                     bool no_foe_name, bool no_god,
                                     bool unseen,
                                     bool ignore_hostile  = false,
                                     bool ignore_related  = false,
                                     bool ignore_religion = false,
                                     bool ignore_silenced = false)
{
    std::string msg;
    for (int tries = 0; tries < 10; tries++)
    {
        msg =
            __try_exact_string(prefixes, key, ignore_hostile, ignore_related,
                               ignore_religion, ignore_silenced);

        // If the first message was non-empty and discarded then discard
        // all subsequent empty messages, so as to not replace an
        // invalid non-empty message with an empty one.
        if (msg.empty())
        {
            if (tries == 0)
                return (msg);
            else
            {
                tries--;
                continue;
            }
        }

        if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen))
        {
            msg = "";
            continue;
        }
        break;
    }

    return (msg);
}

static std::string __get_speak_string(const std::vector<std::string> &prefixes,
                                      const std::string &key,
                                      const monsters *monster,
                                      bool no_player, bool no_foe,
                                      bool no_foe_name, bool no_god,
                                      bool unseen)
{
    std::string msg = _try_exact_string(prefixes, key, no_player, no_foe,
                                        no_foe_name, no_god, unseen);

    if (!msg.empty())
        return msg;

    // Combinations of prefixes by threes
    const int size = prefixes.size();
    std::string prefix = "";
    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] + " ";

                    msg = getSpeakString("default " + prefix + key);

                    if (!msg.empty())
                        return msg;
                }
    }

    // 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] + " ";

                msg = getSpeakString("default " + prefix + key);

                if (!msg.empty())
                    return msg;
            }
    }

    // Prefixes singly
    if (size >= 1)
    {
        for (int i = 0; i < size; i++)
        {
            prefix  = prefixes[i] + " ";

            msg = getSpeakString("default " + prefix + key);

            if (!msg.empty())
                return msg;
        }
    }

    // No prefixes
    msg = getSpeakString("default " + key);

    return msg;
}

static std::string _get_speak_string(const std::vector<std::string> &prefixes,
                                     std::string key,
                                     const monsters *monster,
                                     bool no_player, bool no_foe,
                                     bool no_foe_name, bool no_god,
                                     bool unseen)
{
    int duration = 1;
    if (monster->hit_points <= 0)
        key += " killed";
    else if ((monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS)
        key += " banished";
    else if (monster->is_summoned(&duration) && duration <= 0)
        key += " unsummoned";

    std::string msg;
    for (int tries = 0; tries < 10; tries++)
    {
        msg =
            __get_speak_string(prefixes, key, monster, no_player, no_foe,
                               no_foe_name, no_god, unseen);

        // If the first message was non-empty and discarded then discard
        // all subsequent empty messages, so as to not replace an
        // invalid non-empty message with an empty one.
        if (msg.empty())
        {
            if (tries == 0)
                return (msg);
            else
            {
                tries--;
                continue;
            }
        }

        if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen))
        {
            msg = "";
            continue;
        }

        break;
    }

    return (msg);
}

// Player ghosts with different classes can potentially speak different
// things.
static std::string _player_ghost_speak_str(const monsters *monster,
                                     const std::vector<std::string> prefixes)
{
    const ghost_demon &ghost = *(monster->ghost);
    std::string ghost_class = get_class_name(ghost.job);

    std::string prefix = "";
    for (int i = 0, size = prefixes.size(); i < size; i++)
    {
        prefix += prefixes[i];
        prefix += " ";
    }

    // first try together with class name
    std::string msg = getSpeakString(prefix + ghost_class + " player ghost");

    // else try without class name
    if (msg.empty() || msg == "__NEXT")
        msg = getSpeakString(prefix + "player ghost");

    return msg;
}

// Returns true if the monster did speak, false otherwise.
// Maybe monsters will speak!
void maybe_mons_speaks (monsters *monster)
{
#define MON_SPEAK_CHANCE 21

    if (monster->is_patrolling() || mons_is_wandering(monster)
        || monster->attitude == ATT_NEUTRAL)
    {
        // Very fast wandering/patrolling monsters might, in one monster turn,
        // move into the player's LOS and then back out (or the player
        // might move into their LOS and the monster move back out before
        // the player's view has a chance to update) so prevent them
        // from speaking.
        ;
    }
    else if ((mons_class_flag(monster->type, M_SPEAKS)
                    || !monster->mname.empty())
                && one_chance_in(MON_SPEAK_CHANCE))
    {
        mons_speaks(monster);
    }
    else if (monster->type == MONS_CRAZY_YIUF
        && one_chance_in(MON_SPEAK_CHANCE / 3))
    {
        // Yiuf gets an extra chance to speak!
        mons_speaks(monster);
    }
    else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED)
    {
        // Non-humanoid-ish monsters have a low chance of speaking
        // without the M_SPEAKS flag, to give the dungeon some
        // atmosphere/flavour.
        int chance = MON_SPEAK_CHANCE * 4;

        // Band members are a lot less likely to speak, since there's
        // a lot of them.  Except for uniques.
        if (testbits(monster->flags, MF_BAND_MEMBER)
            && !mons_is_unique(monster->type))
            chance *= 10;

        // However, confused and fleeing monsters are more interesting.
        if (mons_is_fleeing(monster))
            chance /= 2;
        if (monster->has_ench(ENCH_CONFUSION))
            chance /= 2;

        if (one_chance_in(chance))
            mons_speaks(monster);
    }
    // Okay then, don't speak.
}


// Returns true if something is said.
bool mons_speaks(monsters *monster)
{
    ASSERT(!invalid_monster_type(monster->type));

    // Monsters always talk on death, even if invisible/silenced/etc.
    int duration = 1;
    const bool force_speak = !monster->alive()
        || (monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS
        || (monster->is_summoned(&duration) && duration <= 0)
        || crawl_state.prev_cmd == CMD_LOOK_AROUND; // Wizard testing

    const bool unseen   = !you.can_see(monster);
    const bool confused = monster->confused();

    if (!force_speak)
    {
       // Invisible monster tries to remain unnoticed.  Unless they're
       // confused, since then they're too confused to realise 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 (unseen && !confused)
           return (false);

        // 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->pos())
            && get_monster_data(monster->type)->shouts != S_SILENT)
        {
            if (!one_chance_in(3))
                return (false);
        }

        // Berserk monsters just want your hide.
        if (monster->berserk())
            return (false);

        // Monsters in a battle frenzy are likewise occupied.
        if (monster->has_ench(ENCH_BATTLE_FRENZY) && !one_chance_in(3))
            return (false);

        // Charmed monsters aren't too expressive.
        if (monster->has_ench(ENCH_CHARM) && !one_chance_in(3))
            return (false);
    }

    std::vector<std::string> prefixes;
    if (monster->neutral())
    {
        if (!force_speak && coinflip()) // Neutrals speak half as often.
            return (false);

        prefixes.push_back("neutral");
    }
    else if (monster->friendly() && !crawl_state.arena)
        prefixes.push_back("friendly");
    else
        prefixes.push_back("hostile");

    if (mons_is_fleeing(monster))
        prefixes.push_back("fleeing");

    bool silence = silenced(you.pos());
    if (silenced(monster->pos()))
    {
        silence = true;
        prefixes.push_back("silenced");
    }

    if (confused)
        prefixes.push_back("confused");

    // Allows monster speech to be altered slightly on-the-fly.
    if (monster->props.exists("speech_prefix"))
        prefixes.push_back(monster->props["speech_prefix"].get_string());

    const actor*    foe   = (!crawl_state.arena && monster->wont_attack()
                                && invalid_monster_index(monster->foe)) ?
                                    &you : monster->get_foe();
    const monsters* m_foe = (foe && foe->atype() == ACT_MONSTER) ?
                                dynamic_cast<const monsters*>(foe) : NULL;

    // animals only look at the current player form, smart monsters at the
    // actual player genus
    if (!foe || foe->atype() == ACT_PLAYER)
    {
        if (is_player_same_species(monster->type,
                                   mons_intel(monster) <= I_ANIMAL))
        {
            prefixes.push_back("related"); // maybe overkill for Beogh?
        }
    }
    else
    {
        if (mons_genus(monster->mons_species()) ==
            mons_genus(foe->mons_species()))
        {
            prefixes.push_back("related");
        }
    }

    const god_type god = foe               ? foe->deity() :
                         crawl_state.arena ? GOD_NO_GOD :
                                             you.religion;

    // Add Beogh to list of prefixes for orcs (hostile and friendly) if you
    // worship Beogh. (This assumes your being a Hill Orc, so might have odd
    // results in wizard mode.) Don't count charmed or summoned orcs.
    if (you.religion == GOD_BEOGH && mons_genus(monster->type) == MONS_ORC
        && !monster->has_ench(ENCH_CHARM) && !monster->is_summoned())
    {
        if (monster->god == GOD_BEOGH)
            prefixes.push_back("beogh");
        else
            prefixes.push_back("unbeliever");
    }
    else
    {
        if (is_good_god(god))
            prefixes.push_back("good god");
        else if (is_evil_god(god))
            prefixes.push_back("evil god");
        else if (god == GOD_XOM)
            prefixes.push_back("Xom");
    }

#ifdef DEBUG_MONSPEAK
    {
        std::string prefix;
        const int size = prefixes.size();
        for (int i = 0; i < size; i++)
        {
            prefix += prefixes[i];
            prefix += " ";
        }
        mprf(MSGCH_DIAGNOSTICS, "monster speech lookup for %s: prefix = %s",
             monster->name(DESC_PLAIN).c_str(), prefix.c_str());
    }
#endif

    const bool no_foe      = (foe == NULL);
    const bool no_player   = crawl_state.arena
                             || (!monster->wont_attack()
                                 && (!foe || foe->atype() != ACT_PLAYER));
    const bool mon_foe     = (m_foe != NULL);
    const bool no_god      = no_foe || (mon_foe && foe->deity() == GOD_NO_GOD);
    const bool named_foe   = !no_foe
        && (!mon_foe || (m_foe->is_named()
                         && m_foe->type != MONS_ROYAL_JELLY));
    const bool no_foe_name = !named_foe
                          || (mon_foe && (m_foe->flags & MF_NAME_MASK));

    std::string msg;

    // 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,
                                no_player, no_foe, no_foe_name, no_god,
                                unseen);
    }
    else
    {
        if (monster->props.exists("speech_func"))
        {
#ifdef DEBUG_MONSPEAK
            mpr("Trying Lua function for monster speech", MSGCH_DIAGNOSTICS);
#endif
            lua_stack_cleaner clean(dlua);

            dlua_chunk &chunk = monster->props["speech_func"];

            if (!chunk.load(dlua))
            {
                push_monster(dlua, monster);
                dlua.callfn(NULL, 1, 1);
                dlua.fnreturns(">s", &msg);

                // __NONE means to be silent, and __NEXT means to try the next
                // method of getting a speech message.
                if (msg == "__NONE")
                {
#ifdef DEBUG_MONSPEAK
                    mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
#endif
                    return (false);
                }
                if (msg == "__NEXT")
                    msg.clear();
            }
            else
            {
                mprf(MSGCH_ERROR,
                     "Lua speech function for monster '%s' didn't load: %s",
                     monster->full_name(DESC_PLAIN).c_str(),
                     dlua.error.c_str());
            }
        }

        if (msg.empty() && monster->props.exists("speech_key"))
        {
            msg = _get_speak_string(prefixes,
                                     monster->props["speech_key"].get_string(),
                                     monster, no_player, no_foe, no_foe_name,
                                     no_god, unseen);
        }

        // If the monster was originally a unique which has been polymorphed
        // into a non-unique, is its current monter type capable of using its
        // old speech?
        if (!monster->mname.empty() && monster->can_speak() && msg.empty())
        {
            msg = _get_speak_string(prefixes, monster->name(DESC_PLAIN),
                                    monster, no_player, no_foe, no_foe_name,
                                    no_god, unseen);
        }

        if (msg.empty())
        {
            msg = _get_speak_string(prefixes, monster->base_name(DESC_PLAIN),
                                    monster, no_player, no_foe, no_foe_name,
                                    no_god, unseen);
        }
    }

    // The exact name brought no results, try monster genus.
    if ((msg.empty() || msg == "__NEXT")
        && mons_genus(monster->type) != monster->type)
    {
        msg = _get_speak_string(prefixes,
                       mons_type_name(mons_genus(monster->type), DESC_PLAIN),
                       monster, no_player, no_foe, no_foe_name, no_god,
                       unseen);
    }

    // __NONE means to be silent, and __NEXT means to try the next,
    // less exact method of describing the monster to find a speech
    // string.

    if (msg == "__NONE")
    {
#ifdef DEBUG_MONSPEAK
        mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
#endif
        return (false);
    }

    // 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 exact monster name and its genus have failed,
    // so try the monster's glyph/symbol.
    if (msg.empty() || msg == "__NEXT")
    {
        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, no_player, no_foe,
                                no_foe_name, no_god, unseen);
    }

    if (msg == "__NONE")
    {
#ifdef DEBUG_MONSPEAK
        mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
#endif
        return (false);
    }

    // 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);
    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')
        {
            if (intel > I_INSECT)
                prefixes.insert(prefixes.begin(), "smart");
            else if (intel < I_INSECT)
                prefixes.insert(prefixes.begin(), "stupid");
        }
        else
        {
            if (intel > I_ANIMAL)
                prefixes.insert(prefixes.begin(), "smart");
            else if (intel < I_ANIMAL)
                prefixes.insert(prefixes.begin(), "stupid");
        }
    }
    else if (shape >= MON_SHAPE_INSECT && shape <= MON_SHAPE_SNAIL)
    {
        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");
    }

    if (msg.empty() || msg == "__NEXT")
    {
        msg = _get_speak_string(prefixes, get_mon_shape_str(shape), monster,
                                no_player, no_foe, no_foe_name, no_god,
                                unseen);
    }

    if (msg == "__NONE")
    {
#ifdef DEBUG_MONSPEAK
        mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
#endif
        return (false);
    }

    // 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.empty() || msg == "__NEXT") && shape > MON_SHAPE_HUMANOID
        && shape <= MON_SHAPE_NAGA)
    {
        // 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)
        {
            shape = MON_SHAPE_HUMANOID_TAILED;
            msg = _get_speak_string(prefixes, get_mon_shape_str(shape),
                                    monster, no_player, no_foe, no_foe_name,
                                    no_god, unseen);

            // Only be silent if both tailed and winged return __NONE.
            if (msg.empty() || msg == "__NONE" || msg == "__NEXT")
            {
                shape = MON_SHAPE_HUMANOID_WINGED;
                std::string msg2;
                msg2 = _get_speak_string(prefixes, get_mon_shape_str(shape),
                                         monster, no_player, no_foe,
                                         no_foe_name, no_god, unseen);

                if (msg == "__NONE" && msg2 == "__NONE")
                {
#ifdef DEBUG_MONSPEAK
                    mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
#endif
                    return (false);
                }

                if (msg2 == "__NONE")
                    msg2 = "";

                msg = msg2;
            }
        } // if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED)
        if (msg.empty() || msg == "__NONE" || msg == "__NEXT")
        {
            shape = MON_SHAPE_HUMANOID;
            msg = _get_speak_string(prefixes, get_mon_shape_str(shape),
                                    monster, no_player, no_foe, no_foe_name,
                                    no_god, unseen);
        }
    }
    if (msg.empty() || msg == "__NONE")
    {
#ifdef DEBUG_MONSPEAK
        mprf(MSGCH_DIAGNOSTICS, "final result: %s!",
             (msg.empty() ? "empty" : "\"__NONE\""));
#endif
        return (false);
    }

    if (msg == "__NEXT")
    {
        msg::streams(MSGCH_DIAGNOSTICS)
            << "__NEXT used by shape-based speech string for monster '"
            << monster->name(DESC_PLAIN) << "'" << std::endl;
        return (false);
    }

    if (foe == NULL)
        msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS");
    else if (foe->atype() == ACT_MONSTER)
    {
        if (you.can_see(foe))
            msg = replace_all(msg, "__YOU_RESIST", "@The_monster@ resists.");
        else
            msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS");
    }

    return (mons_speaks_msg(monster, msg, MSGCH_TALK, silence));
}

bool mons_speaks_msg(monsters *monster, const std::string &msg,
                     const msg_channel_type def_chan, const bool silence)
{
    if (!mons_near(monster))
        return (false);

    mon_acting mact(monster);

    // We have a speech string, now parse and act on it.
    const std::string _msg = do_mon_str_replacements(msg, monster);
    const std::vector<std::string> lines = split_string("\n", _msg);

    bool noticed = false;       // Any messages actually printed?

    for (int i = 0, size = lines.size(); i < size; ++i)
    {
        std::string line = lines[i];

        // 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 accomodate 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
        // [jpeg] Added MSGCH_TALK_VISUAL for silent "chatter".
        msg_channel_type msg_type = def_chan;

        std::string param = "";
        std::string::size_type pos = line.find(":");

        if (pos != std::string::npos)
            param = line.substr(0, pos);

        if (!param.empty())
        {
            bool match = true;

            if (param == "DANGER")
                msg_type = MSGCH_DANGER;
            else if (param == "WARN" && !silence || param == "VISUAL WARN")
                msg_type = MSGCH_WARN;
            else if (param == "SOUND")
                msg_type = MSGCH_SOUND;
            else if (param == "VISUAL")
                msg_type = MSGCH_TALK_VISUAL;
            else if (param == "SPELL" && !silence || param == "VISUAL SPELL")
            {
                msg_type = monster->friendly() ? MSGCH_FRIEND_SPELL
                                                  : MSGCH_MONSTER_SPELL;
            }
            else if (param == "ENCHANT" && !silence
                     || param == "VISUAL ENCHANT")
            {
                msg_type = monster->friendly() ? MSGCH_FRIEND_ENCHANT
                                                  : MSGCH_MONSTER_ENCHANT;
            }
            else if (param == "PLAIN")
                msg_type = MSGCH_PLAIN;
            else
                match = false;

            if (match)
                line = line.substr(pos + 1);
        }

        const bool old_noticed = noticed;
        noticed = true;         // Only one case is different.

        // Except for VISUAL, none of the above influence these.
        if (line == "__YOU_RESIST" && (!silence || param == "VISUAL"))
            canned_msg( MSG_YOU_RESIST );
        else if (line == "__NOTHING_HAPPENS" && (!silence || param == "VISUAL"))
            canned_msg( MSG_NOTHING_HAPPENS );
        else if (line == "__MORE" && (!silence || param == "VISUAL"))
            more();
        else if (msg_type == MSGCH_TALK_VISUAL && !you.can_see(monster))
            noticed = old_noticed;
        else
        {
            if (you.can_see(monster))
                handle_seen_interrupt(monster);
            mpr(line.c_str(), msg_type);
        }
    }
    return (noticed);
}