Cuberite
A lightweight, fast and extensible game server for Minecraft
CompositeChat.cpp
Go to the documentation of this file.
1 
2 // CompositeChat.cpp
3 
4 // Implements the cCompositeChat class used to wrap a chat message with multiple parts (text, url, cmd)
5 
6 #include "Globals.h"
7 #include "CompositeChat.h"
8 #include "ClientHandle.h"
9 
10 
11 
12 
13 
15 // cCompositeChat:
16 
18  m_MessageType(mtCustom)
19 {
20 }
21 
22 
23 
24 
25 
26 cCompositeChat::cCompositeChat(const AString & a_ParseText, eMessageType a_MessageType) :
27  m_MessageType(a_MessageType)
28 {
29  ParseText(a_ParseText);
30 }
31 
32 
33 
34 
35 
37 {
38  Clear();
39 }
40 
41 
42 
43 
44 
46 {
47  for (cParts::iterator itr = m_Parts.begin(), end = m_Parts.end(); itr != end; ++itr)
48  {
49  delete *itr;
50  } // for itr - m_Parts[]
51  m_Parts.clear();
52 }
53 
54 
55 
56 
57 
58 void cCompositeChat::AddTextPart(const AString & a_Message, const AString & a_Style)
59 {
60  m_Parts.push_back(new cTextPart(a_Message, a_Style));
61 }
62 
63 
64 
65 
66 
67 void cCompositeChat::AddClientTranslatedPart(const AString & a_TranslationID, const AStringVector & a_Parameters, const AString & a_Style)
68 {
69  m_Parts.push_back(new cClientTranslatedPart(a_TranslationID, a_Parameters, a_Style));
70 }
71 
72 
73 
74 
75 
76 void cCompositeChat::AddUrlPart(const AString & a_Text, const AString & a_Url, const AString & a_Style)
77 {
78  m_Parts.push_back(new cUrlPart(a_Text, a_Url, a_Style));
79 }
80 
81 
82 
83 
84 
85 void cCompositeChat::AddRunCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style)
86 {
87  m_Parts.push_back(new cRunCommandPart(a_Text, a_Command, a_Style));
88 }
89 
90 
91 
92 
93 
94 void cCompositeChat::AddSuggestCommandPart(const AString & a_Text, const AString & a_SuggestedCommand, const AString & a_Style)
95 {
96  m_Parts.push_back(new cSuggestCommandPart(a_Text, a_SuggestedCommand, a_Style));
97 }
98 
99 
100 
101 
102 
103 void cCompositeChat::AddShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style)
104 {
105  m_Parts.push_back(new cShowAchievementPart(a_PlayerName, a_Achievement, a_Style));
106 }
107 
108 
109 
110 
111 
112 void cCompositeChat::ParseText(const AString & a_ParseText)
113 {
114  size_t len = a_ParseText.length();
115  size_t first = 0; // First character of the currently parsed block
116  AString CurrentStyle;
117  AString CurrentText;
118  for (size_t i = 0; i < len; i++)
119  {
120  switch (a_ParseText[i])
121  {
122  case '@':
123  {
124  // Color code
125  i++;
126  if (i >= len)
127  {
128  // Not enough following text
129  break;
130  }
131  if (a_ParseText[i] == '@')
132  {
133  // "@@" escape, just put a "@" into the current text and keep parsing as text
134  if (i > first + 1)
135  {
136  CurrentText.append(a_ParseText.c_str() + first, i - first - 1);
137  }
138  first = i + 1;
139  continue;
140  }
141  else
142  {
143  // True color code. Create a part for the CurrentText and start parsing anew:
144  if (i >= first)
145  {
146  CurrentText.append(a_ParseText.c_str() + first, i - first - 1);
147  first = i + 1;
148  }
149  if (!CurrentText.empty())
150  {
151  m_Parts.push_back(new cTextPart(CurrentText, CurrentStyle));
152  CurrentText.clear();
153  }
154  AddStyle(CurrentStyle, a_ParseText.substr(i - 1, 2));
155  }
156  break;
157  }
158 
159  case ':':
160  {
161  const char * LinkPrefixes[] =
162  {
163  "http",
164  "https"
165  };
166  for (size_t Prefix = 0; Prefix < ARRAYCOUNT(LinkPrefixes); Prefix++)
167  {
168  size_t PrefixLen = strlen(LinkPrefixes[Prefix]);
169  if (
170  (i >= first + PrefixLen) && // There is enough space in front of the colon for the prefix
171  (strncmp(a_ParseText.c_str() + i - PrefixLen, LinkPrefixes[Prefix], PrefixLen) == 0) // the prefix matches
172  )
173  {
174  // Add everything before this as a text part:
175  if (i > first + PrefixLen)
176  {
177  CurrentText.append(a_ParseText.c_str() + first, i - first - PrefixLen);
178  first = i - PrefixLen;
179  }
180  if (!CurrentText.empty())
181  {
182  AddTextPart(CurrentText, CurrentStyle);
183  CurrentText.clear();
184  }
185 
186  // Go till the last non-whitespace char in the text:
187  for (; i < len; i++)
188  {
189  if (isspace(a_ParseText[i]))
190  {
191  break;
192  }
193  }
194  AddUrlPart(a_ParseText.substr(first, i - first), a_ParseText.substr(first, i - first), CurrentStyle);
195  first = i;
196  break;
197  }
198  } // for Prefix - LinkPrefix[]
199  break;
200  } // case ':'
201  } // switch (a_ParseText[i])
202  } // for i - a_ParseText[]
203  if (first < len)
204  {
205  AddTextPart(a_ParseText.substr(first, len - first), CurrentStyle);
206  }
207 }
208 
209 
210 
211 
212 
213 void cCompositeChat::SetMessageType(eMessageType a_MessageType, const AString & a_AdditionalMessageTypeData)
214 {
215  m_MessageType = a_MessageType;
216  m_AdditionalMessageTypeData = a_AdditionalMessageTypeData;
217 }
218 
219 
220 
221 
222 
224 {
225  for (cParts::iterator itr = m_Parts.begin(), end = m_Parts.end(); itr != end; ++itr)
226  {
227  if ((*itr)->m_PartType == ptUrl)
228  {
229  (*itr)->m_Style.append("u");
230  }
231  } // for itr - m_Parts[]
232 }
233 
234 
235 
236 
237 
239 {
240  AString Msg;
241  for (cParts::const_iterator itr = m_Parts.begin(), end = m_Parts.end(); itr != end; ++itr)
242  {
243  switch ((*itr)->m_PartType)
244  {
245  case ptText:
246  case ptClientTranslated:
247  case ptRunCommand:
248  case ptSuggestCommand:
249  {
250  Msg.append((*itr)->m_Text);
251  break;
252  }
253  case ptUrl:
254  {
255  Msg.append((static_cast<cUrlPart *>(*itr))->m_Url);
256  break;
257  }
258  case ptShowAchievement:
259  {
260  break;
261  }
262  } // switch (PartType)
263  } // for itr - m_Parts[]
264  return Msg;
265 }
266 
267 
268 
269 
270 
272 {
273  switch (a_MessageType)
274  {
275  case mtCustom: return cLogger::llRegular;
276  case mtFailure: return cLogger::llWarning;
277  case mtInformation: return cLogger::llInfo;
278  case mtSuccess: return cLogger::llRegular;
279  case mtWarning: return cLogger::llWarning;
280  case mtFatal: return cLogger::llError;
281  case mtDeath: return cLogger::llRegular;
283  case mtJoin: return cLogger::llRegular;
284  case mtLeave: return cLogger::llRegular;
285  case mtMaxPlusOne: break;
286  }
287  ASSERT(!"Unhandled MessageType");
288  return cLogger::llError;
289 }
290 
291 
292 
293 
294 
295 void cCompositeChat::AddStyle(AString & a_Style, const AString & a_AddStyle)
296 {
297  if (a_AddStyle.empty())
298  {
299  return;
300  }
301  if (a_AddStyle[0] == '@')
302  {
303  size_t idx = a_Style.find('@');
304  if ((idx != AString::npos) && (idx != a_Style.length()))
305  {
306  a_Style.erase(idx, 2);
307  }
308  a_Style.append(a_AddStyle);
309  return;
310  }
311  a_Style.append(a_AddStyle);
312 }
313 
314 
315 
316 
317 
318 AString cCompositeChat::CreateJsonString(bool a_ShouldUseChatPrefixes) const
319 {
320  Json::Value msg;
321  msg["text"] = cClientHandle::FormatMessageType(a_ShouldUseChatPrefixes, GetMessageType(), GetAdditionalMessageTypeData()); // The client crashes without this field being present
322  const cCompositeChat::cParts & Parts = GetParts();
323  for (cCompositeChat::cParts::const_iterator itr = Parts.begin(), end = Parts.end(); itr != end; ++itr)
324  {
325  Json::Value Part;
326  switch ((*itr)->m_PartType)
327  {
329  {
330  Part["text"] = (*itr)->m_Text;
331  AddChatPartStyle(Part, (*itr)->m_Style);
332  break;
333  }
334 
336  {
338  Part["translate"] = p.m_Text;
339  Json::Value With;
340  for (AStringVector::const_iterator itrW = p.m_Parameters.begin(), endW = p.m_Parameters.end(); itrW != endW; ++itr)
341  {
342  With.append(*itrW);
343  }
344  if (!p.m_Parameters.empty())
345  {
346  Part["with"] = With;
347  }
348  AddChatPartStyle(Part, p.m_Style);
349  break;
350  }
351 
353  {
354  const cCompositeChat::cUrlPart & p = static_cast<const cCompositeChat::cUrlPart &>(**itr);
355  Part["text"] = p.m_Text;
356  Json::Value Url;
357  Url["action"] = "open_url";
358  Url["value"] = p.m_Url;
359  Part["clickEvent"] = Url;
360  AddChatPartStyle(Part, p.m_Style);
361  break;
362  }
363 
366  {
367  const cCompositeChat::cCommandPart & p = static_cast<const cCompositeChat::cCommandPart &>(**itr);
368  Part["text"] = p.m_Text;
369  Json::Value Cmd;
370  Cmd["action"] = (p.m_PartType == cCompositeChat::ptRunCommand) ? "run_command" : "suggest_command";
371  Cmd["value"] = p.m_Command;
372  Part["clickEvent"] = Cmd;
373  AddChatPartStyle(Part, p.m_Style);
374  break;
375  }
376 
378  {
379  const cCompositeChat::cShowAchievementPart & p = static_cast<const cCompositeChat::cShowAchievementPart &>(**itr);
380  Part["translate"] = "chat.type.achievement";
381 
382  Json::Value Ach;
383  Ach["action"] = "show_achievement";
384  Ach["value"] = p.m_Text;
385 
386  Json::Value AchColourAndName;
387  AchColourAndName["color"] = "green";
388  AchColourAndName["translate"] = p.m_Text;
389  AchColourAndName["hoverEvent"] = Ach;
390 
391  Json::Value Extra;
392  Extra.append(AchColourAndName);
393 
394  Json::Value Name;
395  Name["text"] = p.m_PlayerName;
396 
397  Json::Value With;
398  With.append(Name);
399  With.append(Extra);
400 
401  Part["with"] = With;
402  AddChatPartStyle(Part, p.m_Style);
403  break;
404  }
405  }
406  msg["extra"].append(Part);
407  } // for itr - Parts[]
408 
409  #if 1
410  // Serialize as machine-readable string (no whitespace):
411  Json::FastWriter writer;
412  return writer.write(msg);
413  #else
414  // Serialize as human-readable string (pretty-printed):
415  return msg.toStyledString();
416  #endif
417 }
418 
419 
420 
421 
422 
423 void cCompositeChat::AddChatPartStyle(Json::Value & a_Value, const AString & a_PartStyle) const
424 {
425  size_t len = a_PartStyle.length();
426  for (size_t i = 0; i < len; i++)
427  {
428  switch (a_PartStyle[i])
429  {
430  case 'b':
431  {
432  // bold
433  a_Value["bold"] = Json::Value(true);
434  break;
435  }
436 
437  case 'i':
438  {
439  // italic
440  a_Value["italic"] = Json::Value(true);
441  break;
442  }
443 
444  case 'u':
445  {
446  // Underlined
447  a_Value["underlined"] = Json::Value(true);
448  break;
449  }
450 
451  case 's':
452  {
453  // strikethrough
454  a_Value["strikethrough"] = Json::Value(true);
455  break;
456  }
457 
458  case 'o':
459  {
460  // obfuscated
461  a_Value["obfuscated"] = Json::Value(true);
462  break;
463  }
464 
465  case '@':
466  {
467  // Color, specified by the next char:
468  i++;
469  if (i >= len)
470  {
471  // String too short, didn't contain a color
472  break;
473  }
474  switch (a_PartStyle[i])
475  {
476  case '0': a_Value["color"] = Json::Value("black"); break;
477  case '1': a_Value["color"] = Json::Value("dark_blue"); break;
478  case '2': a_Value["color"] = Json::Value("dark_green"); break;
479  case '3': a_Value["color"] = Json::Value("dark_aqua"); break;
480  case '4': a_Value["color"] = Json::Value("dark_red"); break;
481  case '5': a_Value["color"] = Json::Value("dark_purple"); break;
482  case '6': a_Value["color"] = Json::Value("gold"); break;
483  case '7': a_Value["color"] = Json::Value("gray"); break;
484  case '8': a_Value["color"] = Json::Value("dark_gray"); break;
485  case '9': a_Value["color"] = Json::Value("blue"); break;
486  case 'a': a_Value["color"] = Json::Value("green"); break;
487  case 'b': a_Value["color"] = Json::Value("aqua"); break;
488  case 'c': a_Value["color"] = Json::Value("red"); break;
489  case 'd': a_Value["color"] = Json::Value("light_purple"); break;
490  case 'e': a_Value["color"] = Json::Value("yellow"); break;
491  case 'f': a_Value["color"] = Json::Value("white"); break;
492  } // switch (color)
493  } // case '@'
494  } // switch (Style[i])
495  } // for i - a_PartStyle[]
496 }
497 
498 
499 
500 
501 
503 // cCompositeChat::cBasePart:
504 
506  m_PartType(a_PartType),
507  m_Text(a_Text),
508  m_Style(a_Style)
509 {
510 }
511 
512 
513 
514 
515 
517 // cCompositeChat::cTextPart:
518 
519 cCompositeChat::cTextPart::cTextPart(const AString & a_Text, const AString &a_Style) :
520  super(ptText, a_Text, a_Style)
521 {
522 }
523 
524 
525 
526 
527 
529 // cCompositeChat::cClientTranslatedPart:
530 
531 cCompositeChat::cClientTranslatedPart::cClientTranslatedPart(const AString & a_TranslationID, const AStringVector & a_Parameters, const AString & a_Style) :
532  super(ptClientTranslated, a_TranslationID, a_Style),
533  m_Parameters(a_Parameters)
534 {
535 }
536 
537 
538 
539 
540 
542 // cCompositeChat::cUrlPart:
543 
544 cCompositeChat::cUrlPart::cUrlPart(const AString & a_Text, const AString & a_Url, const AString & a_Style) :
545  super(ptUrl, a_Text, a_Style),
546  m_Url(a_Url)
547 {
548 }
549 
550 
551 
552 
553 
555 // cCompositeChat::cCommandPart:
556 
557 cCompositeChat::cCommandPart::cCommandPart(ePartType a_PartType, const AString & a_Text, const AString & a_Command, const AString & a_Style) :
558  super(a_PartType, a_Text, a_Style),
559  m_Command(a_Command)
560 {
561 }
562 
563 
564 
565 
566 
568 // cCompositeChat::cRunCommandPart:
569 
570 cCompositeChat::cRunCommandPart::cRunCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style) :
571  super(ptRunCommand, a_Text, a_Command, a_Style)
572 {
573 }
574 
575 
576 
577 
579 // cCompositeChat::cSuggestCommandPart:
580 
581 cCompositeChat::cSuggestCommandPart::cSuggestCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style) :
582  super(ptSuggestCommand, a_Text, a_Command, a_Style)
583 {
584 }
585 
586 
587 
588 
589 
591 // cCompositeChat::cShowAchievementPart:
592 
593 cCompositeChat::cShowAchievementPart::cShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style) :
594  super(ptShowAchievement, a_Achievement, a_Style),
595  m_PlayerName(a_PlayerName)
596 {
597 }
598 
599 
600 
601 
void AddChatPartStyle(Json::Value &a_Value, const AString &a_PartStyle) const
Adds the chat part&#39;s style (represented by the part&#39;s stylestring) into the Json object.
void SetMessageType(eMessageType a_MessageType, const AString &a_AdditionalMessageTypeData="")
Sets the message type, which is indicated by prefixes added to the message when serializing Takes opt...
void AddTextPart(const AString &a_Message, const AString &a_Style="")
Adds a plain text part, with optional style.
cUrlPart(const AString &a_Text, const AString &a_Url, const AString &a_Style="")
void UnderlineUrls(void)
Adds the "underline" style to each part that is an URL.
AString GetAdditionalMessageTypeData(void) const
Returns additional data pertaining to message type, for example, the name of a mtPrivateMsg sender...
void ParseText(const AString &a_ParseText)
Parses text into various parts, adds those.
static AString FormatMessageType(bool ShouldAppendChatPrefixes, eMessageType a_ChatPrefix, const AString &a_AdditionalData)
Formats the type of message with the proper color and prefix for sending to the client.
AString ExtractText(void) const
Returns the text from the parts that comprises the human-readable data.
void AddUrlPart(const AString &a_Text, const AString &a_Url, const AString &a_Style="u@c")
Adds a part that opens an URL when clicked.
void AddStyle(AString &a_Style, const AString &a_AddStyle)
Adds a_AddStyle to a_Style; overwrites the existing style if appropriate.
cClientTranslatedPart(const AString &a_TranslationID, const AStringVector &a_Parameters, const AString &a_Style="")
eMessageType GetMessageType(void) const
Returns the message type set previously by SetMessageType().
static cLogger::eLogLevel MessageTypeToLogLevel(eMessageType a_MessageType)
Converts the MessageType to a LogLevel value.
void AddSuggestCommandPart(const AString &a_Text, const AString &a_SuggestedCommand, const AString &a_Style="u@b")
Adds a part that suggests a command (enters it into the chat message area, but doesn&#39;t send) when cli...
std::vector< AString > AStringVector
Definition: StringUtils.h:14
cSuggestCommandPart(const AString &a_Text, const AString &a_Command, const AString &a_Style="")
cCompositeChat(void)
Creates a new empty chat message.
cRunCommandPart(const AString &a_Text, const AString &a_Command, const AString &a_Style="")
const cParts & GetParts(void) const
#define ASSERT(x)
Definition: Globals.h:335
cShowAchievementPart(const AString &a_PlayerName, const AString &a_Achievement, const AString &a_Style="")
cCommandPart(ePartType a_PartType, const AString &a_Text, const AString &a_Command, const AString &a_Style="")
void AddClientTranslatedPart(const AString &a_TranslationID, const AStringVector &a_Parameters, const AString &a_Style="")
Adds a part that is translated client-side, with the formatting parameters and optional style...
eMessageType m_MessageType
The message type, as indicated by prefixes.
std::string AString
Definition: StringUtils.h:13
void AddRunCommandPart(const AString &a_Text, const AString &a_Command, const AString &a_Style="u@a")
Adds a part that runs a command when clicked.
AString m_AdditionalMessageTypeData
Additional data pertaining to message type, for example, the name of a mtPrivateMsg sender...
void AddShowAchievementPart(const AString &a_PlayerName, const AString &a_Achievement, const AString &a_Style="")
Adds a part that fully formats a specified achievement using client translatable strings Takes achiev...
AString CreateJsonString(bool a_ShouldUseChatPrefixes=true) const
cParts m_Parts
All the parts that.
eMessageType
Definition: Defines.h:976
cBasePart(ePartType a_PartType, const AString &a_Text, const AString &a_Style="")
#define ARRAYCOUNT(X)
Evaluates to the number of elements in an array (compile-time!)
Definition: Globals.h:290
cTextPart(const AString &a_Text, const AString &a_Style="")
std::vector< cBasePart * > cParts
eLogLevel
Definition: Logger.h:9
void Clear(void)
Removes all parts from the object.