Pol  Revision:cb584c9
speech.cpp
Go to the documentation of this file.
1 
13 #include <cctype>
14 #include <cstddef>
15 #include <iostream>
16 #include <string>
17 
18 #include "../bscript/bobject.h"
19 #include "../bscript/impstr.h"
20 #include "../clib/clib_endian.h"
21 #include "../clib/logfacility.h"
22 #include "../clib/random.h"
23 #include "../clib/rawtypes.h"
24 #include "../plib/systemstate.h"
25 #include "globals/settings.h"
26 #include "globals/uvars.h"
27 #include "guilds.h"
28 #include "listenpt.h"
29 #include "mkscrobj.h"
30 #include "mobile/charactr.h"
31 #include "mobile/npc.h"
32 #include "network/client.h"
33 #include "network/packethelper.h"
34 #include "network/packets.h"
35 #include "pktdef.h"
36 #include "pktin.h"
37 #include "syshook.h"
38 #include "textcmd.h"
39 #include "tildecmd.h"
40 #include "ufuncstd.h"
41 #include "uworld.h"
42 
43 namespace Pol
44 {
45 namespace Core
46 {
47 void handle_processed_speech( Network::Client* client, char* textbuf, int textbuflen,
48  char firstchar, u8 type, u16 color, u16 font )
49 {
50  // ENHANCE: if (intextlen+1) != textbuflen, then the input line was 'dirty'. May want to log this
51  // fact.
52 
53  if ( textbuflen == 1 )
54  return;
55 
56  Mobile::Character* chr = client->chr;
57 
58  // validate text color
59  u16 textcol = cfBEu16( color );
60  if ( textcol < 2 || textcol > 1001 )
61  {
62  textcol = 1001;
63  }
64  chr->last_textcolor( textcol );
65 
66  if ( textbuf[0] == '.' || textbuf[0] == '=' )
67  {
68  if ( !process_command( client, textbuf ) )
69  send_sysmessage( client, std::string( "Unknown command: " ) + textbuf );
70  return;
71  }
72 
73  if ( firstchar == '~' ) // we strip tildes out
74  {
75  process_tildecommand( client, textbuf );
76  return;
77  }
78 
79  if ( chr->squelched() )
80  return;
81 
82  if ( chr->hidden() )
83  chr->unhide();
84 
85  if ( Plib::systemstate.config.show_speech_colors )
86  {
87  INFO_PRINT << chr->name() << " speaking w/ color 0x" << fmt::hexu( cfBEu16( color ) ) << "\n";
88  }
89 
90  u16 textlen = static_cast<u16>( textbuflen + 1 );
91  if ( textlen > SPEECH_MAX_LEN + 1 )
92  textlen = SPEECH_MAX_LEN + 1;
93 
95  talkmsg->offset += 2;
96  talkmsg->Write<u32>( chr->serial_ext );
97  talkmsg->WriteFlipped<u16>( chr->graphic );
98  talkmsg->Write<u8>( type ); // FIXME authorize
99  talkmsg->WriteFlipped<u16>( textcol );
100  talkmsg->WriteFlipped<u16>( font );
101  talkmsg->Write( chr->name().c_str(), 30 );
102  talkmsg->Write( textbuf, textlen );
103  u16 len = talkmsg->offset;
104  talkmsg->offset = 1;
105  talkmsg->WriteFlipped<u16>( len );
106  talkmsg.Send( client, len );
107 
109  if ( chr->dead() && !chr->can_be_heard_as_ghost() )
110  {
111  memcpy( &ghostmsg->buffer, &talkmsg->buffer, sizeof ghostmsg->buffer );
112  ghostmsg->offset = 44;
113  char* t = &ghostmsg->buffer[ghostmsg->offset];
114  while ( ghostmsg->offset < len )
115  {
116  if ( !isspace( *t ) )
117  {
118  if ( Clib::random_int( 3 ) == 0 )
119  *t = 'o';
120  else
121  *t = 'O';
122  }
123  ++t;
124  ghostmsg->offset++;
125  }
126  }
127  // send to those nearby
128  u16 range;
129  if ( type == Core::TEXTTYPE_WHISPER )
131  else if ( type == Core::TEXTTYPE_YELL )
133  else
136  chr->x, chr->y, chr->realm, range, [&]( Mobile::Character* other_chr ) {
137  Network::Client* client2 = other_chr->client;
138  if ( client == client2 )
139  return;
140  if ( !other_chr->is_visible_to_me( chr ) )
141  return;
142  if ( other_chr->deafened() )
143  return;
144 
145  if ( !chr->dead() || other_chr->dead() || other_chr->can_hearghosts() ||
146  chr->can_be_heard_as_ghost() )
147  {
148  talkmsg.Send( client2, len );
149  }
150  else
151  {
152  ghostmsg.Send( client2, len );
153  }
154  } );
155 
156  if ( !chr->dead() )
157  {
159  chr->x, chr->y, chr->realm, range, [&]( Mobile::Character* otherchr ) {
160  Mobile::NPC* npc = static_cast<Mobile::NPC*>( otherchr );
161  npc->on_pc_spoke( chr, textbuf, type );
162  } );
163  }
164  else
165  {
167  chr->x, chr->y, chr->realm, range, [&]( Mobile::Character* otherchr ) {
168  Mobile::NPC* npc = static_cast<Mobile::NPC*>( otherchr );
169  npc->on_ghost_pc_spoke( chr, textbuf, type );
170  } );
171  }
172 
173  sayto_listening_points( client->chr, textbuf, textbuflen, type );
174 }
175 
176 
177 void SpeechHandler( Network::Client* client, PKTIN_03* mymsg )
178 {
179  int i;
180  int intextlen;
181 
182  char textbuf[SPEECH_MAX_LEN + 1];
183  int textbuflen;
184 
185  intextlen = cfBEu16( mymsg->msglen ) - offsetof( PKTIN_03, text ) - 1;
186 
187  // Preprocess the text into a sanity-checked, printable, null-terminated form in textbuf
188  if ( intextlen < 0 )
189  intextlen = 0;
190  if ( intextlen > SPEECH_MAX_LEN )
191  intextlen = SPEECH_MAX_LEN; // ENHANCE: May want to log this
192 
193  for ( i = 0, textbuflen = 0; i < intextlen; i++ )
194  {
195  char ch = mymsg->text[i];
196 
197  if ( ch == 0 )
198  break;
199  if ( ch == '~' )
200  continue; // skip unprintable tildes. Probably not a reportable offense.
201 
202  if ( isprint( ch ) )
203  textbuf[textbuflen++] = ch;
204  // ENHANCE: else report client data error? Just log?
205  }
206  textbuf[textbuflen++] = 0;
207 
208  handle_processed_speech( client, textbuf, textbuflen, mymsg->text[0], mymsg->type, mymsg->color,
209  mymsg->font );
210 }
211 
212 void SendUnicodeSpeech( Network::Client* client, PKTIN_AD* msgin, u16* wtext, size_t wtextlen,
213  char* ntext, size_t ntextlen, Bscript::ObjArray* speechtokens )
214 {
215  // validate text color
216  u16 textcol = cfBEu16( msgin->color );
217  if ( textcol < 2 || textcol > 1001 )
218  {
219  // 3/8/2009 MuadDib Changed to default color instead of complain.
220  textcol = 1001;
221  }
222 
223  Mobile::Character* chr = client->chr;
224 
225  chr->last_textcolor( textcol );
226 
227  using std::wstring;
228 
229  if ( wtext[0] == ctBEu16( L'.' ) || wtext[0] == ctBEu16( L'=' ) )
230  {
231  if ( !process_command( client, ntext, wtext, msgin->lang ) )
232  {
233  wstring wtmp( L"Unknown command: " );
234  // Needs to be done char-by-char due to linux's 4-byte unicode!
235  for ( size_t i = 0; i < wtextlen; i++ )
236  wtmp += static_cast<wchar_t>( cfBEu16( wtext[i] ) );
237  send_sysmessage( client, wtmp, msgin->lang );
238  }
239  return;
240  }
241 
242  if ( cfBEu16( msgin->wtext[0] ) == L'~' ) // we strip tildes out
243  {
244  process_tildecommand( client, wtext );
245  return;
246  }
247 
248  if ( chr->squelched() )
249  return;
250 
251  if ( chr->hidden() )
252  chr->unhide();
253 
254  if ( Plib::systemstate.config.show_speech_colors )
255  {
256  INFO_PRINT << chr->name() << " speaking w/ color 0x" << fmt::hexu( cfBEu16( msgin->color ) )
257  << "\n";
258  }
259 
262  talkmsg->offset += 2;
263  talkmsg->Write<u32>( chr->serial_ext );
264  talkmsg->WriteFlipped<u16>( chr->graphic );
265  talkmsg->Write<u8>( msgin->type ); // FIXME authorize
266  talkmsg->WriteFlipped<u16>( textcol );
267  talkmsg->WriteFlipped<u16>( msgin->font );
268  talkmsg->Write( msgin->lang, 4 );
269  talkmsg->Write( chr->name().c_str(), 30 );
270  talkmsg->Write( &wtext[0], static_cast<u16>( wtextlen ), false ); // nullterm already included
271  u16 len = talkmsg->offset;
272  talkmsg->offset = 1;
273  talkmsg->WriteFlipped<u16>( len );
274 
275 
276  if ( msgin->type == 0x0d )
277  {
278  auto thisguild = chr->guild();
279  if ( settingsManager.ssopt.core_sends_guildmsgs && thisguild != nullptr )
280  {
281  for ( unsigned cli = 0; cli < networkManager.clients.size(); cli++ )
282  {
283  Network::Client* client2 = networkManager.clients[cli];
284  if ( !client2->ready )
285  continue;
286  if ( thisguild->guildid() == client2->chr->guildid() )
287  talkmsg.Send( client2, len );
288  }
289  }
290  }
291  else if ( msgin->type == 0x0e )
292  {
293  auto thisguild = chr->guild();
294  if ( settingsManager.ssopt.core_sends_guildmsgs && thisguild != nullptr )
295  {
296  for ( unsigned cli = 0; cli < networkManager.clients.size(); cli++ )
297  {
298  Network::Client* client2 = networkManager.clients[cli];
299  if ( !client2->ready )
300  continue;
301  auto otherguild = client2->chr->guild();
302  if ( otherguild != nullptr )
303  {
304  if ( thisguild->guildid() == otherguild->guildid() ||
305  ( thisguild->hasAlly( otherguild ) ) )
306  talkmsg.Send( client2, len );
307  }
308  }
309  }
310  }
311  else
312  {
313  talkmsg.Send( client, len ); // self
314  if ( chr->dead() && !chr->can_be_heard_as_ghost() )
315  {
316  memcpy( &ghostmsg->buffer, &talkmsg->buffer, sizeof ghostmsg->buffer );
317 
318  ghostmsg->offset = 48;
319  u16* t = ( (u16*)&ghostmsg->buffer[ghostmsg->offset] );
320  while ( ghostmsg->offset < len - 2 ) // dont convert nullterm
321  {
322  wchar_t wch = ( *t );
323  if ( !iswspace( wch ) )
324  {
325  if ( Clib::random_int( 3 ) == 0 )
326  *t = ctBEu16( L'o' );
327  else
328  *t = ctBEu16( L'O' );
329  }
330  ++t;
331  ghostmsg->offset += 2;
332  }
333  }
334  // send to those nearby
335  u16 range;
336  if ( msgin->type == Core::TEXTTYPE_WHISPER )
338  else if ( msgin->type == Core::TEXTTYPE_YELL )
340  else
343  chr->x, chr->y, chr->realm, range, [&]( Mobile::Character* otherchr ) {
344  Network::Client* client2 = otherchr->client;
345  if ( client == client2 )
346  return;
347  if ( !otherchr->is_visible_to_me( chr ) )
348  return;
349  if ( otherchr->deafened() )
350  return;
351 
352  if ( !chr->dead() || otherchr->dead() || otherchr->can_hearghosts() ||
353  chr->can_be_heard_as_ghost() )
354  {
355  talkmsg.Send( client2, len );
356  }
357  else
358  {
359  ghostmsg.Send( client2, len );
360  }
361  } );
362 
363  if ( !chr->dead() )
364  {
366  chr->x, chr->y, chr->realm, range, [&]( Mobile::Character* otherchr ) {
367  Mobile::NPC* npc = static_cast<Mobile::NPC*>( otherchr );
368  npc->on_pc_spoke( chr, ntext, msgin->type, wtext, msgin->lang, speechtokens );
369  } );
370  }
371  else
372  {
374  chr->x, chr->y, chr->realm, range, [&]( Mobile::Character* otherchr ) {
375  Mobile::NPC* npc = static_cast<Mobile::NPC*>( otherchr );
376  npc->on_ghost_pc_spoke( chr, ntext, msgin->type, wtext, msgin->lang, speechtokens );
377  } );
378  }
379  sayto_listening_points( client->chr, ntext, static_cast<int>( ntextlen ), msgin->type, wtext,
380  msgin->lang, static_cast<int>( wtextlen ), speechtokens );
381  }
382 }
383 u16 Get12BitNumber( u8* thearray, u16 theindex )
384 {
385  u16 theresult = 0;
386  int thenibble = theindex * 3;
387  int thebyte = thenibble / 2;
388  if ( thenibble % 2 )
389  theresult = cfBEu16( *( (u16*)( thearray + thebyte ) ) ) & 0x0FFF;
390  else
391  theresult = cfBEu16( *( (u16*)( thearray + thebyte ) ) ) >> 4;
392  return theresult;
393 }
394 
395 int GetNextUTF8( u8* bytemsg, int i, u16& unicodeChar )
396 {
397  u16 result = 0;
398 
399  if ( ( bytemsg[i] & 0x80 ) == 0 )
400  {
401  unicodeChar = bytemsg[i];
402  return i + 1;
403  }
404 
405  if ( ( bytemsg[i] & 0xE0 ) == 0xC0 )
406  {
407  // two byte sequence :
408  if ( ( bytemsg[i + 1] & 0xC0 ) == 0x80 )
409  {
410  result = ( ( bytemsg[i] & 0x1F ) << 6 ) | ( bytemsg[i + 1] & 0x3F );
411  unicodeChar = result;
412  return i + 2;
413  }
414  }
415  else if ( ( bytemsg[i] & 0xF0 ) == 0xE0 )
416  {
417  // three byte sequence
418  if ( ( ( bytemsg[i + 1] & 0xC0 ) == 0x80 ) && ( ( bytemsg[i + 2] & 0xC0 ) == 0x80 ) )
419  {
420  result = ( ( bytemsg[i] & 0x0F ) << 12 ) | ( ( bytemsg[i + 1] & 0x3F ) < 6 ) |
421  ( bytemsg[i + 2] & 0x3F );
422  unicodeChar = result;
423  return i + 3;
424  }
425  }
426 
427  // An error occurred in the sequence(or sequence > 16 bits) :
428  unicodeChar = 0x20; // Set unicode char to a "space" character instead"
429  return i + 1;
430 }
431 
433 {
434  using std::wcout; // wcout.narrow() function r0x! :-)
435 
436  int intextlen;
437  u16 numtokens = 0;
438  u16* themsg = msgin->wtext;
439  u8* bytemsg;
440  int wtextoffset = 0;
441  std::unique_ptr<Bscript::ObjArray> speechtokens( nullptr );
442  int i;
443 
444  u16 tempbuf[SPEECH_MAX_LEN + 1];
445 
446  u16 wtextbuf[SPEECH_MAX_LEN + 1];
447  size_t wtextbuflen;
448 
449  char ntextbuf[SPEECH_MAX_LEN + 1];
450  size_t ntextbuflen;
451 
452  if ( msgin->type & 0xc0 )
453  {
454  numtokens = Get12BitNumber( (u8*)( msgin->wtext ), 0 );
455  wtextoffset = ( ( ( ( numtokens + 1 ) * 3 ) / 2 ) + ( ( numtokens + 1 ) % 2 ) );
456  bytemsg = ( ( (u8*)themsg ) + wtextoffset );
457  int bytemsglen = cfBEu16( msgin->msglen ) - wtextoffset - offsetof( PKTIN_AD, wtext ) - 1;
458  intextlen = 0;
459 
460  i = 0;
461  int j = 0;
462  u16 unicodeChar;
463  while ( ( i < bytemsglen ) && ( i < SPEECH_MAX_LEN ) )
464  {
465  i = GetNextUTF8( bytemsg, i, unicodeChar );
466  tempbuf[j++] = cfBEu16( unicodeChar );
467  intextlen++;
468  }
469 
470  themsg = tempbuf;
471  }
472  else
473  intextlen =
474  ( cfBEu16( msgin->msglen ) - offsetof( PKTIN_AD, wtext ) ) / sizeof( msgin->wtext[0] ) - 1;
475 
476  // Preprocess the text into a sanity-checked, printable, null-terminated form in textbuf
477  if ( intextlen < 0 )
478  intextlen = 0;
479  if ( intextlen > SPEECH_MAX_LEN )
480  intextlen = SPEECH_MAX_LEN; // ENHANCE: May want to log this
481 
482  // Preprocess the text into a sanity-checked, printable, null-terminated form
483  // in 'wtextbuf' and 'ntextbuf'
484  ntextbuflen = 0;
485  wtextbuflen = 0;
486  for ( i = 0; i < intextlen; i++ )
487  {
488  u16 wc = cfBEu16( themsg[i] );
489  if ( wc == 0 )
490  break; // quit early on embedded nulls
491  if ( wc == L'~' )
492  continue; // skip unprintable tildes.
493  wtextbuf[wtextbuflen++] = ctBEu16( wc );
494  ntextbuf[ntextbuflen++] = wcout.narrow( (wchar_t)wc, '?' );
495  }
496  wtextbuf[wtextbuflen++] = (u16)0;
497  ntextbuf[ntextbuflen++] = 0;
498 
499  if ( msgin->type & 0xc0 )
500  {
501  Bscript::BLong* atoken = nullptr;
502  if ( speechtokens.get() == nullptr )
503  speechtokens.reset( new Bscript::ObjArray() );
504  for ( u16 j = 0; j < numtokens; j++ )
505  {
506  atoken = new Bscript::BLong( Get12BitNumber( (u8*)( msgin->wtext ), j + 1 ) );
507  speechtokens->addElement( atoken );
508  }
509  if ( gamestate.system_hooks.speechmul_hook != nullptr )
510  {
512  new Bscript::ObjArray( *speechtokens.get() ),
513  new Bscript::String( ntextbuf ) );
514  }
515  msgin->type &= ( ~0xC0 ); // Client won't accept C0 text type messages, so must set to 0
516  }
517 
518  SendUnicodeSpeech( client, msgin, wtextbuf, wtextbuflen, ntextbuf, ntextbuflen,
519  speechtokens.release() );
520 }
521 }
522 }
unsigned char u8
Definition: rawtypes.h:25
unsigned int guildid() const
Definition: charactr.cpp:4176
bool process_command(Network::Client *client, const char *text, const u16 *wtext, const char *lang)
Definition: textcmd.cpp:675
ExportedFunction * speechmul_hook
Definition: syshook.h:74
SystemState systemstate
Definition: systemstate.cpp:12
bool call(Bscript::BObjectImp *p0)
Definition: syshook.cpp:44
bool process_tildecommand(Network::Client *client, const char *textbuf)
Definition: tildecmd.cpp:38
unsigned short whisper_range
Definition: ssopt.h:75
#define SPEECH_MAX_LEN
Definition: pktdef.h:27
SystemHooks system_hooks
Definition: uvars.h:190
unsigned short yell_range
Definition: ssopt.h:76
void sayto_listening_points(Mobile::Character *speaker, const char *p_text, int, u8 texttype, const u16 *p_wtext, const char *p_lang, int p_wtextlen, Bscript::ObjArray *speechtokens)
Definition: listenpt.cpp:48
#define ctBEu16(x)
Definition: clib_endian.h:46
static void InRange(u16 x, u16 y, const Realms::Realm *realm, unsigned range, F &&f)
Definition: uworld.h:235
void handle_processed_speech(Network::Client *client, char *textbuf, int textbuflen, char firstchar, u8 type, u16 color, u16 font)
Definition: speech.cpp:47
char text[SPEECH_MAX_LEN+1]
Definition: pktin.h:117
Mobile::Character * chr
Definition: client.h:182
unsigned short u16
Definition: rawtypes.h:26
bool core_sends_guildmsgs
Definition: ssopt.h:94
unsigned int u32
Definition: rawtypes.h:27
u16 Get12BitNumber(u8 *thearray, u16 theindex)
Definition: speech.cpp:383
NetworkManager networkManager
Definition: network.cpp:28
int random_int(int i)
Definition: random.cpp:34
void UnicodeSpeechHandler(Network::Client *client, PKTIN_AD *msgin)
Definition: speech.cpp:432
void Send(Client *client, int len=-1) const
Definition: packethelper.h:69
#define cfBEu16(x)
Definition: clib_endian.h:44
unsigned short speech_range
Definition: ssopt.h:74
GameState gamestate
Definition: uvars.cpp:74
bool can_be_heard_as_ghost() const
Definition: charactr.cpp:1251
SettingsManager settingsManager
Definition: settings.cpp:14
char lang[4]
Definition: pktin.h:400
int GetNextUTF8(u8 *bytemsg, int i, u16 &unicodeChar)
Definition: speech.cpp:395
Realms::Realm * realm
Definition: baseobject.h:56
void SpeechHandler(Network::Client *client, PKTIN_03 *mymsg)
Definition: speech.cpp:177
void SendUnicodeSpeech(Network::Client *client, PKTIN_AD *msgin, u16 *wtext, size_t wtextlen, char *ntext, size_t ntextlen, Bscript::ObjArray *speechtokens)
Definition: speech.cpp:212
u16 last_textcolor() const
Definition: charactr.cpp:4166
#define INFO_PRINT
Definition: logfacility.h:223
virtual std::string name() const
Definition: uobject.cpp:196
Bscript::BObjectImp * make_mobileref(Mobile::Character *chr)
Definition: mkscrobj.cpp:14
bool squelched() const
Definition: charactr.cpp:4042
Definition: berror.cpp:12
bool dead() const
Definition: charactr.h:931
void send_sysmessage(Network::Client *client, const char *text, unsigned short font, unsigned short color)
Definition: ufunc.cpp:1147
bool hidden() const
Definition: charactr.h:941