Latest compatible version of Classicube from the original GitHub repository (https://github.com/ClassiCube/ClassiCube) that can be compiled on Classicube for PowerMac PPC running Mac OS X 10.4.

This commit is contained in:
Andrei Alexandru
2025-12-17 13:17:57 +02:00
commit c71492f846
1248 changed files with 422858 additions and 0 deletions

313
src/Chat.c Normal file
View File

@@ -0,0 +1,313 @@
#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;
CC_BIG_VAR 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_filepath str;
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 */
Platform_EncodePath(&str, &dir);
res = Directory_Create(&str);
if (!res || res == ReturnCode_DirectoryExists) return true;
Chat_DisableLogging();
Logger_SysWarn2(res, "creating directory", &dir);
return false;
}
static void OpenChatLog(struct cc_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 cc_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 */
};