diff options
Diffstat (limited to 'src/Chat.c')
-rw-r--r-- | src/Chat.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/src/Chat.c b/src/Chat.c new file mode 100644 index 0000000..5f45d78 --- /dev/null +++ b/src/Chat.c @@ -0,0 +1,311 @@ +#include "Chat.h" +#include "Commands.h" +#include "String.h" +#include "Stream.h" +#include "Event.h" +#include "Game.h" +#include "Logger.h" +#include "Server.h" +#include "Funcs.h" +#include "Utils.h" +#include "Options.h" +#include "Drawer2D.h" + +static char status[5][STRING_SIZE]; +static char bottom[3][STRING_SIZE]; +static char client[2][STRING_SIZE]; +static char announcement[STRING_SIZE]; +static char bigAnnouncement[STRING_SIZE]; +static char smallAnnouncement[STRING_SIZE]; + +cc_string Chat_Status[5] = { String_FromArray(status[0]), String_FromArray(status[1]), String_FromArray(status[2]), + String_FromArray(status[3]), String_FromArray(status[4]) }; +cc_string Chat_BottomRight[3] = { String_FromArray(bottom[0]), String_FromArray(bottom[1]), String_FromArray(bottom[2]) }; +cc_string Chat_ClientStatus[2] = { String_FromArray(client[0]), String_FromArray(client[1]) }; + +cc_string Chat_Announcement = String_FromArray(announcement); +cc_string Chat_BigAnnouncement = String_FromArray(bigAnnouncement); +cc_string Chat_SmallAnnouncement = String_FromArray(smallAnnouncement); + +double Chat_AnnouncementReceived; +double Chat_BigAnnouncementReceived; +double Chat_SmallAnnouncementReceived; + +struct StringsBuffer Chat_Log, Chat_InputLog; +cc_bool Chat_Logging; + +/*########################################################################################################################* +*-------------------------------------------------------Chat logging------------------------------------------------------* +*#########################################################################################################################*/ +double Chat_RecentLogTimes[CHATLOG_TIME_MASK + 1]; + +static void ClearChatLogs(void) { + Mem_Set(Chat_RecentLogTimes, 0, sizeof(Chat_RecentLogTimes)); + StringsBuffer_Clear(&Chat_Log); +} + +static char logNameBuffer[STRING_SIZE]; +static cc_string logName = String_FromArray(logNameBuffer); +static char logPathBuffer[FILENAME_SIZE]; +static cc_string logPath = String_FromArray(logPathBuffer); + +static struct Stream logStream; +static int lastLogDay, lastLogMonth, lastLogYear; + +/* Resets log name to empty and resets last log date */ +static void ResetLogFile(void) { + logName.length = 0; + lastLogYear = -123; +} + +/* Closes handle to the chat log file */ +static void CloseLogFile(void) { + cc_result res; + if (!logStream.meta.file) return; + + res = logStream.Close(&logStream); + if (res) { Logger_SysWarn2(res, "closing", &logPath); } +} + +/* Whether the given character is an allowed in a log filename */ +static cc_bool AllowedLogNameChar(char c) { + return + c == '{' || c == '}' || c == '[' || c == ']' || c == '(' || c == ')' || + (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +void Chat_SetLogName(const cc_string* name) { + char c; + int i; + if (logName.length) return; + + for (i = 0; i < name->length; i++) { + c = name->buffer[i]; + + if (AllowedLogNameChar(c)) { + String_Append(&logName, c); + } else if (c == '&') { + i++; /* skip over following color code */ + } + } +} + +void Chat_DisableLogging(void) { + Chat_Logging = false; + lastLogYear = -321; + Chat_AddRaw("&cDisabling chat logging"); + CloseLogFile(); +} + +static cc_bool CreateLogsDirectory(void) { + static const cc_string dir = String_FromConst("logs"); + cc_result res; + /* Utils_EnsureDirectory cannot be used here because it causes a stack overflow */ + /* when running the game and an error occurs when trying to create the directory */ + /* This happens because when running the game, Logger_WarnFunc is changed to log */ + /* a message in chat instead of showing a dialog box, which causes the following */ + /* functions to be called in a recursive loop: */ + /* */ + /* Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog -> OpenChatLog */ + /* --> Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog -> OpenChatLog */ + /* --> Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog -> OpenChatLog */ + /* --> Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog ... */ + /* and so on, until eventually the stack overflows */ + res = Directory_Create(&dir); + if (!res || res == ReturnCode_DirectoryExists) return true; + + Chat_DisableLogging(); + Logger_SysWarn2(res, "creating directory", &dir); + return false; +} + +static void OpenChatLog(struct DateTime* now) { + cc_result res; + int i; + if (Platform_ReadonlyFilesystem || !CreateLogsDirectory()) return; + + /* Ensure multiple instances do not end up overwriting each other's log entries. */ + for (i = 0; i < 20; i++) { + logPath.length = 0; + String_Format3(&logPath, "logs/%p4-%p2-%p2 ", &now->year, &now->month, &now->day); + + if (i > 0) { + String_Format2(&logPath, "%s _%i.txt", &logName, &i); + } else { + String_Format1(&logPath, "%s.txt", &logName); + } + + res = Stream_AppendFile(&logStream, &logPath); + if (res && res != ReturnCode_FileShareViolation) { + Chat_DisableLogging(); + Logger_SysWarn2(res, "appending to", &logPath); + return; + } + + if (res == ReturnCode_FileShareViolation) continue; + return; + } + + Chat_DisableLogging(); + Chat_Add1("&cFailed to open a chat log file after %i tries, giving up", &i); +} + +static void AppendChatLog(const cc_string* text) { + cc_string str; char strBuffer[DRAWER2D_MAX_TEXT_LENGTH]; + struct DateTime now; + cc_result res; + + if (!logName.length || !Chat_Logging) return; + DateTime_CurrentLocal(&now); + + if (now.day != lastLogDay || now.month != lastLogMonth || now.year != lastLogYear) { + CloseLogFile(); + OpenChatLog(&now); + } + + lastLogDay = now.day; lastLogMonth = now.month; lastLogYear = now.year; + if (!logStream.meta.file) return; + + /* [HH:mm:ss] text */ + String_InitArray(str, strBuffer); + String_Format3(&str, "[%p2:%p2:%p2] ", &now.hour, &now.minute, &now.second); + Drawer2D_WithoutColors(&str, text); + + res = Stream_WriteLine(&logStream, &str); + if (!res) return; + Chat_DisableLogging(); + Logger_SysWarn2(res, "writing to", &logPath); +} + +void Chat_Add1(const char* format, const void* a1) { + Chat_Add4(format, a1, NULL, NULL, NULL); +} +void Chat_Add2(const char* format, const void* a1, const void* a2) { + Chat_Add4(format, a1, a2, NULL, NULL); +} +void Chat_Add3(const char* format, const void* a1, const void* a2, const void* a3) { + Chat_Add4(format, a1, a2, a3, NULL); +} +void Chat_Add4(const char* format, const void* a1, const void* a2, const void* a3, const void* a4) { + cc_string msg; char msgBuffer[STRING_SIZE * 2]; + String_InitArray(msg, msgBuffer); + + String_Format4(&msg, format, a1, a2, a3, a4); + Chat_AddOf(&msg, MSG_TYPE_NORMAL); +} + +void Chat_AddRaw(const char* raw) { + cc_string str = String_FromReadonly(raw); + Chat_AddOf(&str, MSG_TYPE_NORMAL); +} +void Chat_Add(const cc_string* text) { Chat_AddOf(text, MSG_TYPE_NORMAL); } + +void Chat_AddOf(const cc_string* text, int msgType) { + cc_string str; + if (msgType == MSG_TYPE_NORMAL) { + /* Check for chat overflow (see issue #837) */ + /* This happens because Offset/Length are packed into a single 32 bit value, */ + /* with 9 bits used for length. Hence if offset exceeds 2^23 (8388608), it */ + /* overflows and earlier chat messages start wrongly appearing instead */ + if (Chat_Log.totalLength > 8388000) { + ClearChatLogs(); + Chat_AddRaw("&cChat log cleared as it hit 8.3 million character limit"); + } + + /* StringsBuffer_Add will abort game if try to add string > 511 characters */ + str = *text; + str.length = min(str.length, DRAWER2D_MAX_TEXT_LENGTH); + + Chat_GetLogTime(Chat_Log.count) = Game.Time; + AppendChatLog(&str); + StringsBuffer_Add(&Chat_Log, &str); + } else if (msgType >= MSG_TYPE_STATUS_1 && msgType <= MSG_TYPE_STATUS_3) { + /* Status[0] is for texture pack downloading message */ + /* Status[1] is for reduced performance mode message */ + String_Copy(&Chat_Status[2 + (msgType - MSG_TYPE_STATUS_1)], text); + } else if (msgType >= MSG_TYPE_BOTTOMRIGHT_1 && msgType <= MSG_TYPE_BOTTOMRIGHT_3) { + String_Copy(&Chat_BottomRight[msgType - MSG_TYPE_BOTTOMRIGHT_1], text); + } else if (msgType == MSG_TYPE_ANNOUNCEMENT) { + String_Copy(&Chat_Announcement, text); + Chat_AnnouncementReceived = Game.Time; + } else if (msgType == MSG_TYPE_BIGANNOUNCEMENT) { + String_Copy(&Chat_BigAnnouncement, text); + Chat_BigAnnouncementReceived = Game.Time; + } else if (msgType == MSG_TYPE_SMALLANNOUNCEMENT) { + String_Copy(&Chat_SmallAnnouncement, text); + Chat_SmallAnnouncementReceived = Game.Time; + } else if (msgType >= MSG_TYPE_CLIENTSTATUS_1 && msgType <= MSG_TYPE_CLIENTSTATUS_2) { + String_Copy(&Chat_ClientStatus[msgType - MSG_TYPE_CLIENTSTATUS_1], text); + } else if (msgType >= MSG_TYPE_EXTRASTATUS_1 && msgType <= MSG_TYPE_EXTRASTATUS_2) { + String_Copy(&Chat_Status[msgType - MSG_TYPE_EXTRASTATUS_1], text); + } + + Event_RaiseChat(&ChatEvents.ChatReceived, text, msgType); +} + + +/*########################################################################################################################* +*-------------------------------------------------------Generic chat------------------------------------------------------* +*#########################################################################################################################*/ +static void LogInputUsage(const cc_string* text) { + /* Simplify navigating through input history by not logging duplicate entries */ + if (Chat_InputLog.count) { + int lastIndex = Chat_InputLog.count - 1; + cc_string last = StringsBuffer_UNSAFE_Get(&Chat_InputLog, lastIndex); + + if (String_Equals(text, &last)) return; + } + StringsBuffer_Add(&Chat_InputLog, text); +} + +void Chat_Send(const cc_string* text, cc_bool logUsage) { + if (!text->length) return; + Event_RaiseChat(&ChatEvents.ChatSending, text, 0); + if (logUsage) LogInputUsage(text); + + if (!Commands_Execute(text)) { + Server.SendChat(text); + } +} + +static void OnInit(void) { +#if defined CC_BUILD_MOBILE || defined CC_BUILD_WEB + /* Better to not log chat by default on mobile/web, */ + /* since it's not easily visible to end users */ + Chat_Logging = Options_GetBool(OPT_CHAT_LOGGING, false); +#else + Chat_Logging = Options_GetBool(OPT_CHAT_LOGGING, true); +#endif +} + +static void ClearCPEMessages(void) { + Chat_AddOf(&String_Empty, MSG_TYPE_ANNOUNCEMENT); + Chat_AddOf(&String_Empty, MSG_TYPE_BIGANNOUNCEMENT); + Chat_AddOf(&String_Empty, MSG_TYPE_SMALLANNOUNCEMENT); + Chat_AddOf(&String_Empty, MSG_TYPE_STATUS_1); + Chat_AddOf(&String_Empty, MSG_TYPE_STATUS_2); + Chat_AddOf(&String_Empty, MSG_TYPE_STATUS_3); + Chat_AddOf(&String_Empty, MSG_TYPE_BOTTOMRIGHT_1); + Chat_AddOf(&String_Empty, MSG_TYPE_BOTTOMRIGHT_2); + Chat_AddOf(&String_Empty, MSG_TYPE_BOTTOMRIGHT_3); +} + +static void OnReset(void) { + CloseLogFile(); + ResetLogFile(); + ClearCPEMessages(); +} + +static void OnFree(void) { + CloseLogFile(); + ClearCPEMessages(); + + ClearChatLogs(); + StringsBuffer_Clear(&Chat_InputLog); +} + +struct IGameComponent Chat_Component = { + OnInit, /* Init */ + OnFree, /* Free */ + OnReset /* Reset */ +}; |