uox3/source/speech.cpp
2023-04-07 11:17:51 -04:00

726 lines
20 KiB
C++
Raw Permalink Blame History

#include "uox3.h"
#include "speech.h"
#include "cVersionClass.h"
#include "cRaces.h"
#include "commands.h"
#include "skills.h"
#include "CJSMapping.h"
#include "cScript.h"
#include "cEffects.h"
#include "CPacketSend.h"
#include "CResponse.h"
#include "movement.h"
#include "Dictionary.h"
#include "StringUtility.hpp"
#include "strutil.hpp"
//o------------------------------------------------------------------------------------------------o
//| Function - ClilocMessage()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends a cliloc message to the client, which is displayed as a system message
//o------------------------------------------------------------------------------------------------o
void ClilocMessage( CSocket *mSock, SpeechType speechType, UI16 hue, UI16 font, UI32 messageNum, const char *types = "", ... )
{
bool multipleArgs = false;
std::string argList = "";
std::string stringVal = "";
const char *typesPtr = types;
va_list marker;
va_start( marker, types );
while( *typesPtr != '\0' )
{
if( *typesPtr == 'i' )
{
stringVal = util::ntos( va_arg( marker, SI32 ));
}
else if( *typesPtr == 's' )
{
stringVal = va_arg( marker, char * );
}
if( !stringVal.empty() )
{
if( !multipleArgs )
{
multipleArgs = true;
argList = stringVal;
}
else
{
argList += util::format( "\t%s", stringVal.c_str() );
}
}
++typesPtr;
}
va_end( marker );
CPClilocMessage toSend;
toSend.Type( speechType );
toSend.Hue( hue );
toSend.Font( font );
toSend.Message( messageNum );
toSend.ArgumentString( argList );
mSock->Send( &toSend );
}
//o------------------------------------------------------------------------------------------------o
//| Function - ClilocMessage()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends a clilocmessage to the client, which will be displayed as if said by srcObj
//o------------------------------------------------------------------------------------------------o
void ClilocMessage( CSocket *mSock, CBaseObject *srcObj, SpeechType speechType, UI16 hue, UI16 font, UI32 messageNum, bool sendAll, const char *types = "", ... )
{
bool multipleArgs = false;
std::string argList = "";
std::string stringVal = "";
const char *typesPtr = types;
va_list marker;
va_start( marker, types );
while( *typesPtr != '\0' )
{
if( *typesPtr == 'i' )
{
stringVal = util::ntos( va_arg( marker, SI32 ));
}
else if( *typesPtr == 's' )
{
stringVal = va_arg( marker, char * );
}
if( !stringVal.empty() )
{
if( !multipleArgs )
{
multipleArgs = true;
argList = stringVal;
}
else
{
argList += util::format( "\t%s", stringVal.c_str() );
}
}
++typesPtr;
}
va_end( marker );
CPClilocMessage toSend(( *srcObj ));
toSend.Type( speechType );
toSend.Hue( hue );
toSend.Font( font );
toSend.Message( messageNum );
toSend.ArgumentString( argList );
bool sendSock = ( mSock != nullptr );
if( sendAll )
{
UI16 searchDistance = DIST_SAMESCREEN;
if( speechType == WHISPER || speechType == ASCIIWHISPER )
{
searchDistance = DIST_SAMETILE;
}
else if( speechType == YELL || speechType == ASCIIYELL )
{
searchDistance = DIST_SAMESCREEN * 1.5;
}
else if( speechType == EMOTE || speechType == ASCIIEMOTE )
{
searchDistance = DIST_INRANGE;
}
for( auto &tmSock :FindNearbyPlayers( srcObj, searchDistance ))
{
if( sendSock && ( tmSock == mSock ))
{
sendSock = false;
}
tmSock->Send( &toSend );
}
}
if( sendSock )
{
mSock->Send( &toSend );
}
}
CSpeechQueue *SpeechSys;
std::map<std::string, UnicodeTypes> codeLookup;
void InitializeLookup()
{
for( SI32 i = static_cast<SI32>( ZERO ); i < static_cast<SI32>( TOTAL_LANGUAGES ); ++i )
{
codeLookup[LanguageCodes[static_cast<UnicodeTypes>( i )]] = static_cast<UnicodeTypes>( i );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - FindLanguage()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Attempt to find language used by client
//o------------------------------------------------------------------------------------------------o
UnicodeTypes FindLanguage( CSocket *s, UI16 offset )
{
if( s == nullptr )
return ZERO;
char langCode[4];
langCode[0] = s->GetByte( offset );
langCode[1] = s->GetByte( ++offset );
langCode[2] = s->GetByte( ++offset );
langCode[3] = 0;
std::string ulangCode = langCode;
ulangCode = util::upper( ulangCode );
UnicodeTypes cLang = s->Language();
if( LanguageCodes[cLang] != ulangCode.c_str() )
{
std::map<std::string, UnicodeTypes>::const_iterator p = codeLookup.find( ulangCode );
if( p != codeLookup.end() )
{
return p->second;
}
else
{
Console.Error( util::format( "Unknown language type \"%s\". PLEASE report this in the Bugs section of the forums at https://www.uox3.org!", ulangCode.c_str() ));
}
}
return cLang;
}
//o------------------------------------------------------------------------------------------------o
//| Function - SysBroadcast()
//o------------------------------------------------------------------------------------------------o
//| Purpose - This function was adapted to be used with the new code
//| in the console thread that allows text to be entered on
//| the console and it be shipped out to all logged in players.
//|
//| Changes - (February 27, 2000)
//o------------------------------------------------------------------------------------------------o
void SysBroadcast( const std::string& txt )
{
if( !txt.empty() )
{
/*if( cwmWorldState->ServerData()->UseUnicodeMessages() )
{
Network->pushConn();
for( CSocket *mSock = Network->FirstSocket(); !Network->FinishedSockets(); mSock = Network->NextSocket() )
{
CChar *mChar = mSock->CurrcharObj();
if( ValidateObject( mChar ))
{
CPUnicodeMessage unicodeMessage;
unicodeMessage.Message( txt );
unicodeMessage.Font( 0xFFFF );
unicodeMessage.Colour( 0xFFFF );
unicodeMessage.Type( 1 );
unicodeMessage.Language( "ENG" );
unicodeMessage.Name( "System" );
unicodeMessage.ID( 0 );
unicodeMessage.Serial( 0 );
mSock->Send( &unicodeMessage );
}
}
Network->popConn();
}
else
{*/
CSpeechEntry& toAdd = SpeechSys->Add();
toAdd.Speech( txt );
toAdd.Font( FNT_NORMAL );
toAdd.Speaker( INVALIDSERIAL );
toAdd.SpokenTo( INVALIDSERIAL );
toAdd.Type( SYSTEM );
toAdd.At( cwmWorldState->GetUICurrentTime() );
toAdd.TargType( SPTRG_BROADCASTPC );
toAdd.Colour( 0x084D );
toAdd.Font( FNT_BOLD );
toAdd.SpeakerName( "System: " );
//}
}
}
bool WhichResponse( CSocket *mSock, CChar *mChar, std::string text, CChar *tChar = nullptr );
//o------------------------------------------------------------------------------------------------o
//| Function - CPITalkRequest::Handle()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handles speech requests from client
//|
//| Notes - Unicode speech format
//| byte=char, short=char[2], int=char[4], wchar=char[2]=unicode character
//|
//| Message Sent By Client:
//| 0xAD - Unicode Speech Request
//| UI08 cmd (0xAD)
//| short msgsize 1,2
//| byte type (0=say, 2=emote, 8=whisper, 9=yell) 3
//| short color 4,5
//| short font 6,7
//| UI08[4] lang (null terminated, "enu " for US english.) 8,9,10,11
//| wchar[?] text (null terminated, ?=(msgsize-12)/2) 13
//|
//| Message Sent By Server:
//| Unicode Speech message(Variable # of bytes)
//| <09> BYTE cmd 0
//| <09> BYTE[2] blockSize 1-2
//| <09> BYTE[4] ID 3-6
//| <09> BYTE[2] Model 7-8
//| <09> BYTE Type 9
//| <09> BYTE[2] Color 10-11
//| <09> BYTE[2] Font 12-13
//| <09> BYTE[4] Language 14-17 (3 byte unicode language, mirrored from client)
//| <09> BYTE[30] Name 18-47
//| <09> BYTE[?][2] Msg - Null Terminated (blockSize - 48) 48+?
//o------------------------------------------------------------------------------------------------o
bool CPITalkRequest::Handle( void )
{
if( HandleCommon() )
return true;
CChar *mChar = tSock->CurrcharObj();
if( !ValidateObject( mChar ))
return true;
char *asciiText = Text();
if( strlen( asciiText ) == 0 )
return true;
std::vector<UI16> scriptTriggers = mChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returned false/0, prevent hard-code (and other scripts with event) from running
if( toExecute->OnTalk( mChar, asciiText ) == 0 )
{
return true;
}
}
}
if(( asciiText[0] == cwmWorldState->ServerData()->ServerCommandPrefix() ) || (( asciiText[0] == '.' ) && ( asciiText[1] != '.' )))
{
Commands->Command( tSock, mChar, &asciiText[1] );
}
else
{
if( mChar->IsDead() )
{
if( !mChar->IsAtWar() )
{
mChar->SetWar( true );
CPWarMode wMode( 1 );
tSock->Send( &wMode );
}
}
if(( Type() != WHISPER && Type() != ASCIIWHISPER ) && ( mChar->GetVisible() == VT_TEMPHIDDEN || mChar->GetVisible() == VT_INVISIBLE ))
{
mChar->ExposeToView();
}
if(( Type() == YELL || Type() == ASCIIYELL ) && mChar->CanBroadcast() )
{
CSpeechEntry& toAdd = SpeechSys->Add();
toAdd.Speech( asciiText );
toAdd.Font( static_cast<FontType>( mChar->GetFontType() ));
toAdd.Speaker( mChar->GetSerial() );
toAdd.SpokenTo( INVALIDSERIAL );
toAdd.Type( PROMPT );
toAdd.At( cwmWorldState->GetUICurrentTime() );
toAdd.TargType( SPTRG_BROADCASTPC );
if( mChar->GetSayColour() == 0x1700 )
{
toAdd.Colour( 0x5A );
}
else if( mChar->GetSayColour() == 0x0 )
{
toAdd.Colour( 0x5A );
}
else
{
toAdd.Colour( mChar->GetSayColour() );
}
toAdd.Font( FNT_BOLD );
std::string mCharName = GetNpcDictName( mChar );
toAdd.SpeakerName( mCharName );
}
else
{
std::string text( asciiText );
if( Type() == 0 )
{
mChar->SetSayColour( TextColour() );
}
if( Type() == 2 )
{
mChar->SetEmoteColour( TextColour() );
}
if( cwmWorldState->ServerData()->ServerSpeechLog() && !mChar->IsNpc() ) // Logging
{
auto temp = util::format( "%s.log", mChar->GetName().c_str() );
auto temp2 = util::format( "%s [%x %x %x %x] [%i]: %s\n", mChar->GetName().c_str(), mChar->GetSerial( 1 ), mChar->GetSerial( 2 ), mChar->GetSerial( 3 ), mChar->GetSerial( 4 ), mChar->GetAccount().wAccountIndex, asciiText );
Console.Log( temp, temp2 );
}
std::string upperText = util::upper( text );
if( upperText.find( "DEVTEAM033070" ) != std::string::npos )
{
std::string temp3 = "RBuild: " + CVersionClass::GetRealBuild() + " PBuild: " + CVersionClass::GetBuild() + " --> Version: " + CVersionClass::GetVersion();
tSock->SysMessage( temp3.c_str() );
}
if( !WhichResponse( tSock, mChar, text ))
return true;
CPUOXBuffer *txtToSend = nullptr;
CPUOXBuffer *ghostedText = nullptr;
CPUnicodeSpeech *uniTxtToSend = nullptr;
CPUnicodeSpeech *uniGhostedText = nullptr;
CPacketSpeech *asciiTxtToSend = nullptr;
CPacketSpeech *asciiGhostedText = nullptr;
if( IsUnicode() )
{
uniTxtToSend = new CPUnicodeSpeech();
uniTxtToSend->Object( *( static_cast<CPITalkRequestUnicode *>( this )));
uniTxtToSend->Object( *mChar );
txtToSend = uniTxtToSend;
uniGhostedText = new CPUnicodeSpeech();
( *uniGhostedText ) = ( *uniTxtToSend );
uniGhostedText->GhostIt( 0 );
ghostedText = uniGhostedText;
}
else
{
asciiTxtToSend = new CPacketSpeech( *( static_cast<CPITalkRequestAscii *>( this )));
asciiTxtToSend->SpeakerSerial( mChar->GetSerial() );
asciiTxtToSend->SpeakerModel( mChar->GetId() );
std::string mCharName = GetNpcDictName( mChar );
asciiTxtToSend->SpeakerName( mCharName );
txtToSend = asciiTxtToSend;
asciiGhostedText = new CPacketSpeech();
( *asciiGhostedText ) = ( *asciiTxtToSend );
asciiGhostedText->GhostIt( 0 );
ghostedText = asciiGhostedText;
}
tSock->Send( txtToSend );
std::vector<CSocket *> nearbyChars;
// Distance at which other players can hear the speech depends on speech-type
if(( Type() == WHISPER || Type() == ASCIIWHISPER ) && !mChar->IsGM() && !mChar->IsCounselor() )
{
nearbyChars = FindNearbyPlayers( mChar, 1 );
}
else if(( Type() == WHISPER || Type() == ASCIIWHISPER ) && ( mChar->IsGM() || mChar->IsCounselor() ))
{
nearbyChars = FindNearbyPlayers( mChar, 3 );
}
else if( Type() == YELL || Type() == ASCIIYELL )
{
nearbyChars = FindNearbyPlayers( mChar, ( DIST_SAMESCREEN * 1.5 ));
}
else
{
nearbyChars = FindNearbyPlayers( mChar );
}
for( auto &tSock : nearbyChars )
{
CChar *tChar = tSock->CurrcharObj();
if( mChar != tChar )
{
// Line of Sight check!
if( !tChar->IsGM() && !LineOfSight( mChar->GetSocket(), tChar, mChar->GetX(), mChar->GetY(), mChar->GetZ() + 15, WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false, 0, false ))
continue;
if( mChar->IsDead() && tChar->GetCommandLevel() < CL_CNS && tSock->GetTimer( tPC_SPIRITSPEAK ) == 0 ) // GM/Counselors can see ghosts talking always Seers?
{
if( mChar->IsDead() && !tChar->IsDead() ) // Ghost can talk normally to other ghosts
{
tSock->Send( ghostedText );
}
else
{
tSock->Send( txtToSend );
}
}
else if( tChar->GetRace() != mChar->GetRace() && !tChar->IsGM() && !tChar->IsCounselor() )
{
SKILLVAL raceLangMin = Races->LanguageMin( mChar->GetRace() );
if( raceLangMin > 0 && Skills->CheckSkill( tChar, SPIRITSPEAK, Races->LanguageMin( mChar->GetRace() ), 1000 ) != 1 )
{
tSock->Send( ghostedText );
}
else
{
tSock->Send( txtToSend );
}
}
else
{
std::string mCharName = GetNpcDictName( mChar );
if( mChar->GetVisible() == VT_TEMPHIDDEN || mChar->GetVisible() == VT_INVISIBLE || mChar->GetVisible() == VT_PERMHIDDEN )
{
if(( tChar->IsGM() || tChar->IsCounselor() ) || ( Type() == WHISPER || Type() == ASCIIWHISPER ))
{
tSock->SysMessage( 1794, mCharName.c_str() ); // (Whisper from %s)
tSock->Send( txtToSend );
}
}
else
{
if(( Type() == YELL || Type() == ASCIIYELL ) && !ObjInRange( tChar, mChar, DIST_SAMESCREEN ))
{
tSock->SysMessage( 1795, mCharName.c_str() ); // (Yelled by %s)
tSock->Send( txtToSend );
}
else
{
tSock->Send( txtToSend );
}
}
}
}
}
delete uniTxtToSend;
delete asciiTxtToSend;
delete uniGhostedText;
delete asciiGhostedText;
}
}
return true;
}
CSpeechQueue::CSpeechQueue() : pollTime( 100 ), runAsThread( false )
{
speechList.resize( 0 );
//InitializeLookup();
}
auto CSpeechQueue::Startup() -> void
{
InitializeLookup();
}
CSpeechQueue::~CSpeechQueue()
{
for( SPEECHLIST_ITERATOR slIter = speechList.begin(); slIter != speechList.end(); ++slIter )
{
delete ( *slIter );
( *slIter ) = nullptr;
}
speechList.clear();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSpeechQueue::SayIt()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends out specified speech entry to users
//o------------------------------------------------------------------------------------------------o
void CSpeechQueue::SayIt( CSpeechEntry& toSay )
{
CPacketSpeech toSend = toSay;
CChar *thisChar = nullptr;
CItem *thisItem = nullptr;
CBaseObject *thisObj = nullptr;
CSocket *thisSock = nullptr;
if( toSay.Speaker() > BASEITEMSERIAL )
{
thisItem = CalcItemObjFromSer( toSay.Speaker() );
thisObj = thisItem;
}
else
{
thisChar = CalcCharObjFromSer( toSay.Speaker() );
thisObj = thisChar;
}
CChar *sChar = nullptr;
CSocket *mSock = nullptr;
switch( toSay.TargType() )
{
case SPTRG_INDIVIDUAL: // aimed at individual person
if( ValidateObject( thisChar ))
{
thisSock = thisChar->GetSocket();
if( thisSock != nullptr )
{
thisSock->Send( &toSend );
}
}
sChar = CalcCharObjFromSer( toSay.SpokenTo() );
if( ValidateObject( sChar ) && sChar != thisChar )
{
mSock = sChar->GetSocket();
if( mSock != nullptr )
{
mSock->Send( &toSend );
}
}
break;
case SPTRG_PCNPC: // all NPCs and PCs in range
case SPTRG_PCS: // all PCs in range
{
if( !ValidateObject( thisObj ))
break;
if( ValidateObject( thisItem ) && thisItem->GetCont() != nullptr ) // not on ground, can't guarantee speech
break;
for( auto &mSock : FindPlayersInVisrange( thisObj ))
{
CChar *mChar = mSock->CurrcharObj();
if( ValidateObject( mChar ))
{
if( mChar->GetCommandLevel() >= toSay.CmdLevel() )
{
mSock->Send( &toSend );
}
}
}
break;
}
case SPTRG_BROADCASTPC: // ALL PCs everywhere + NPCs in range
case SPTRG_BROADCASTALL:
{
for( auto &mSock : Network->connClients )
{
if( mSock )
{
auto mChar = mSock->CurrcharObj();
if( ValidateObject( mChar ))
{
if( mChar->GetCommandLevel() >= toSay.CmdLevel() )
{
mSock->Send( &toSend );
}
}
}
}
break;
}
case SPTRG_ONLYRECEIVER: // only the receiver gets the message
sChar = CalcCharObjFromSer( toSay.SpokenTo() );
if( ValidateObject( sChar ) && sChar != thisChar )
{
mSock = sChar->GetSocket();
if( mSock != nullptr )
{
mSock->Send( &toSend );
}
}
break;
case SPTRG_NULL:
break;
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - bool InternalPoll( void )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends out any pending speech, returning true if entries were sent
//o------------------------------------------------------------------------------------------------o
bool CSpeechQueue::InternalPoll( void )
{
bool retVal = false;
CSpeechEntry *toCheck = nullptr;
SPEECHLIST_ITERATOR slIter = speechList.begin();
while( slIter != speechList.end() )
{
toCheck = ( *slIter );
if( toCheck->At() == -1 || static_cast<UI32>( toCheck->At() ) <= cwmWorldState->GetUICurrentTime() )
{
retVal = true;
SayIt(( *toCheck ));
delete ( *slIter );
( *slIter ) = nullptr;
slIter = speechList.erase( slIter );
}
else
{
++slIter;
}
}
return retVal;
}
bool CSpeechQueue::Poll( void )
{
if( RunAsThread() )
{
while( RunAsThread() )
{
InternalPoll();
std::this_thread::sleep_for(std::chrono::milliseconds( pollTime )); // so that it's never 0 (and number of milliseconds)
}
return true;
}
else
{
return InternalPoll();
}
}
CSpeechEntry& CSpeechQueue::Add( void ) // Make space in queue, and return pointer to new entry
{
size_t iSize = speechList.size();
speechList.push_back( new CSpeechEntry );
return ( *speechList[iSize] );
}
SI32 CSpeechQueue::PollTime( void ) const
{
return pollTime;
}
void CSpeechQueue::PollTime( SI32 value )
{
pollTime = value;
}
bool CSpeechQueue::RunAsThread( void ) const
{
return runAsThread;
}
void CSpeechQueue::RunAsThread( bool value )
{
runAsThread = value;
}
void CSpeechQueue::DumpInFile( void )
{
std::string speechFile = cwmWorldState->ServerData()->Directory( CSDDP_LOGS ) + "speechdump.txt";
std::ofstream speechDestination( speechFile.c_str() );
if( !speechDestination )
{
Console.Error( util::format( "Failed to open %s for writing", speechFile.c_str() ));
return;
}
SPEECHLIST_ITERATOR toWrite;
for( toWrite = speechList.begin(); toWrite != speechList.end(); ++toWrite )
{
speechDestination << "Time: " << ( *toWrite )->At() << std::endl;
speechDestination << "nColour: " << ( *toWrite )->Colour() << std::endl;
speechDestination << "nFont: " << ( *toWrite )->Font()<< std::endl;
speechDestination << "nLanguage: " << ( *toWrite )->Language() << std::endl;
speechDestination << "nSpeech: " << ( *toWrite )->Speech() << std::endl;
speechDestination << "nSpeaker: " << ( *toWrite )->SpeakerName() << std::endl << std::endl;
}
speechDestination.close();
}