Added in-game chat

Fixes #35
This commit is contained in:
MysterD 2020-09-14 22:05:20 -07:00
parent 5fe5ffda45
commit 9a0c07e53c
11 changed files with 265 additions and 21 deletions

141
src/game/chat.c Normal file
View file

@ -0,0 +1,141 @@
#include <stdio.h>
#include <ultra64.h>
#include <string.h>
#include "chat.h"
#include "game_init.h"
#include "ingame_menu.h"
#include "segment2.h"
#include "gfx_dimensions.h"
#include "config.h"
#include "PR/gbi.h"
#include "pc/controller/controller_keyboard.h"
#include "pc/network/network.h"
#include "audio_defines.h"
#include "audio/external.h"
#define CHAT_DIALOG_MAX 96
#define CHAT_MESSAGES_MAX 16
#define CHAT_LIFE_MAX 400
struct ChatMessage {
u8 dialog[CHAT_DIALOG_MAX];
u8 isLocal;
u16 life;
};
static char inputMessage[CHAT_DIALOG_MAX] = { 0 };
static struct ChatMessage message[CHAT_MESSAGES_MAX] = { 0 };
static u8 onMessageIndex = 0;
static u8 sInChatInput = FALSE;
#define CHAT_SCALE 0.5f
#define CHAT_SPACE 10.0f
#define CHATBOX_PAD_X 0.0215f
#define CHATBOX_SCALE_X 0.00385f
#define CHATBOX_SCALE_Y 0.115f
#define CHATBOX_X 2.0f
#define CHATBOX_Y 11.0f
#define CHAT_X 4.0f
#define CHAT_Y -18.0f
static void render_chat_message(struct ChatMessage* chatMessage, u8 index) {
f32 textWidth = get_generic_dialog_width(chatMessage->dialog);
f32 alphaScale = ((f32)chatMessage->life / (f32)(CHAT_LIFE_MAX / 20.0f));
alphaScale *= alphaScale;
if (alphaScale > 1) { alphaScale = 1; }
f32 chatBoxWidth = CHATBOX_SCALE_X * textWidth + CHATBOX_PAD_X;
create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(CHATBOX_X), CHATBOX_Y + index * CHAT_SPACE, 0);
create_dl_scale_matrix(MENU_MTX_NOPUSH, chatBoxWidth, CHATBOX_SCALE_Y, 1.0f);
u8 boxR, boxG, boxB;
if (chatMessage->isLocal == 2) {
boxR = 150;
boxG = 150;
boxB = 255;
} else {
f32 rgbScale = (((f32)chatMessage->life - ((f32)CHAT_LIFE_MAX * 0.98f)) / ((f32)CHAT_LIFE_MAX * 0.02f));
if (chatMessage->isLocal || rgbScale < 0) { rgbScale = 0; }
boxR = 255 * rgbScale;
boxG = 255 * rgbScale;
boxB = 255 * rgbScale;
}
gDPSetEnvColor(gDisplayListHead++, boxR, boxG, boxB, 110 * alphaScale);
gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box);
create_dl_scale_matrix(MENU_MTX_NOPUSH, CHAT_SCALE / chatBoxWidth, CHAT_SCALE / CHATBOX_SCALE_Y, 1.0f);
u8 textR, textG, textB;
if (chatMessage->isLocal == 1) {
textR = 200;
textG = 200;
textB = 255;
} else if (chatMessage->isLocal == 2) {
textR = 0;
textG = 0;
textB = 0;
} else {
textR = 255;
textG = 255;
textB = 255;
}
gSPDisplayList(gDisplayListHead++, dl_ia_text_begin);
gDPSetEnvColor(gDisplayListHead++, textR, textG, textB, 255 * alphaScale);
print_generic_string(CHAT_X, CHAT_Y, chatMessage->dialog);
gSPDisplayList(gDisplayListHead++, dl_ia_text_end);
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
}
void chat_add_message(char* ascii, u8 isLocal) {
message[onMessageIndex].dialog[0] = isLocal ? 0xFD : 0xFA;
message[onMessageIndex].dialog[1] = 0x9E;
str_ascii_to_dialog(ascii, &message[onMessageIndex].dialog[2], MIN(strlen(ascii), CHAT_DIALOG_MAX - 3));
message[onMessageIndex].life = CHAT_LIFE_MAX;
message[onMessageIndex].isLocal = isLocal ? 1 : 0;
onMessageIndex = (onMessageIndex + 1) % CHAT_MESSAGES_MAX;
play_sound(isLocal ? SOUND_MENU_MESSAGE_DISAPPEAR : SOUND_MENU_MESSAGE_APPEAR, gDefaultSoundArgs);
}
static void chat_stop_input(void) {
sInChatInput = FALSE;
keyboard_stop_text_input();
}
static void chat_send_input(void) {
sInChatInput = FALSE;
keyboard_stop_text_input();
if (strlen(gTextInput) == 0) { return; }
chat_add_message(gTextInput, TRUE);
network_send_chat(gTextInput);
}
void chat_start_input(void) {
sInChatInput = TRUE;
keyboard_start_text_input(TIM_SINGLE_LINE, CHAT_DIALOG_MAX - 3, chat_stop_input, chat_send_input);
}
void render_chat(void) {
u8 count = 0;
if (sInChatInput) {
struct ChatMessage inputMessage = { 0 };
inputMessage.dialog[0] = 0xFD;
inputMessage.dialog[1] = 0x9E;
str_ascii_to_dialog(gTextInput, &inputMessage.dialog[2], MIN(strlen(gTextInput), CHAT_DIALOG_MAX - 3));
inputMessage.isLocal = 2;
inputMessage.life = CHAT_LIFE_MAX;
render_chat_message(&inputMessage, count++);
}
u8 index = onMessageIndex;
for (int i = 0; i < CHAT_MESSAGES_MAX; i++) {
if (--index >= CHAT_MESSAGES_MAX) { index = CHAT_MESSAGES_MAX - 1; }
if (message[index].life == 0) { continue; }
render_chat_message(&message[index], count++);
message[index].life--;
}
}

8
src/game/chat.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef CHAT_H
#define CHAT_H
void render_chat(void);
void chat_add_message(char* ascii, u8 isLocal);
void chat_start_input(void);
#endif

View file

@ -31,6 +31,7 @@
#ifdef EXT_OPTIONS_MENU #ifdef EXT_OPTIONS_MENU
#include "options_menu.h" #include "options_menu.h"
#endif #endif
#include "chat.h"
u16 gDialogColorFadeTimer; u16 gDialogColorFadeTimer;
s8 gLastDialogLineNum; s8 gLastDialogLineNum;
@ -415,27 +416,43 @@ void render_multi_text_string(s16 *xPos, s16 *yPos, s8 multiTextID)
void str_ascii_to_dialog(const char* string, u8* dialog, u16 length) { void str_ascii_to_dialog(const char* string, u8* dialog, u16 length) {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
switch (string[i]) { switch (string[i]) {
case '\'': dialog[i] = 0x3E; break; case '\'': dialog[i] = 0x3E; break;
case '.': dialog[i] = 0x3F; break; case '.': dialog[i] = 0x3F; break;
case ',': dialog[i] = DIALOG_CHAR_COMMA; break; case ',': dialog[i] = DIALOG_CHAR_COMMA; break;
case '-': dialog[i] = 0x9F; break; case '-': dialog[i] = 0x9F; break;
case '(': dialog[i] = 0xE1; break; case '(': dialog[i] = 0xE1; break;
case ')': dialog[i] = 0xE3; break; case ')': dialog[i] = 0xE3; break;
case '&': dialog[i] = 0xE5; break; case '&': dialog[i] = 0xE5; break;
case '!': dialog[i] = 0xF2; break; case '!': dialog[i] = 0xF2; break;
case '%': dialog[i] = 0xF3; break; case '%': dialog[i] = 0xF3; break;
case '?': dialog[i] = 0xF4; break; case '?': dialog[i] = 0xF4; break;
case '"': dialog[i] = 0xF6; break; // 0xF5 is opening quote case '"': dialog[i] = 0xF6; break; // 0xF5 is opening quote
case '~': dialog[i] = 0xF7; break; case '~': dialog[i] = 0xF7; break;
case '*': dialog[i] = 0xFB; break; case '*': dialog[i] = 0xFB; break;
case ' ': dialog[i] = DIALOG_CHAR_SPACE; break; case ' ': dialog[i] = DIALOG_CHAR_SPACE; break;
case '\n': dialog[i] = DIALOG_CHAR_NEWLINE; break; case '\n': dialog[i] = DIALOG_CHAR_NEWLINE; break;
default: dialog[i] = ASCII_TO_DIALOG(string[i]); default: dialog[i] = ((u8)string[i] < 0xF0) ? ASCII_TO_DIALOG(string[i]) : string[i];
} }
} }
dialog[length] = DIALOG_CHAR_TERMINATOR; dialog[length] = DIALOG_CHAR_TERMINATOR;
} }
f32 get_generic_dialog_width(u8* dialog) {
f32 width = 0;
u8* d = dialog;
while (*d != DIALOG_CHAR_TERMINATOR) {
width += (f32)(gDialogCharWidths[*d]);
d++;
}
return width;
}
f32 get_generic_ascii_string_width(const char* ascii) {
u8 dialog[256] = { DIALOG_CHAR_TERMINATOR };
str_ascii_to_dialog(ascii, dialog, strlen(ascii));
return get_generic_dialog_width(dialog);
}
void print_generic_ascii_string(s16 x, s16 y, const char* ascii) { void print_generic_ascii_string(s16 x, s16 y, const char* ascii) {
u8 dialog[256] = { DIALOG_CHAR_TERMINATOR }; u8 dialog[256] = { DIALOG_CHAR_TERMINATOR };
str_ascii_to_dialog(ascii, dialog, strlen(ascii)); str_ascii_to_dialog(ascii, dialog, strlen(ascii));
@ -3178,6 +3195,8 @@ s16 render_menus_and_dialogs() {
create_dl_ortho_matrix(); create_dl_ortho_matrix();
render_chat();
if (gMenuMode != -1) { if (gMenuMode != -1) {
switch (gMenuMode) { switch (gMenuMode) {
case 0: case 0:

View file

@ -117,6 +117,8 @@ void create_dl_identity_matrix(void);
void create_dl_translation_matrix(s8 pushOp, f32 x, f32 y, f32 z); void create_dl_translation_matrix(s8 pushOp, f32 x, f32 y, f32 z);
void create_dl_ortho_matrix(void); void create_dl_ortho_matrix(void);
void str_ascii_to_dialog(const char* string, u8* dialog, u16 length); void str_ascii_to_dialog(const char* string, u8* dialog, u16 length);
f32 get_generic_dialog_width(u8* dialog);
f32 get_generic_ascii_string_width(const char* ascii);
void print_generic_ascii_string(s16 x, s16 y, const char* ascii); void print_generic_ascii_string(s16 x, s16 y, const char* ascii);
void print_generic_string(s16 x, s16 y, const u8 *str); void print_generic_string(s16 x, s16 y, const u8 *str);
void print_hud_lut_string(s8 hudLUT, s16 x, s16 y, const u8 *str); void print_hud_lut_string(s8 hudLUT, s16 x, s16 y, const u8 *str);
@ -150,5 +152,6 @@ void render_hud_cannon_reticle(void);
void reset_red_coins_collected(void); void reset_red_coins_collected(void);
s16 render_menus_and_dialogs(void); s16 render_menus_and_dialogs(void);
s16 render_sync_level_screen(void); s16 render_sync_level_screen(void);
void create_dl_scale_matrix(s8 pushOp, f32 x, f32 y, f32 z);
#endif // INGAME_MENU_H #endif // INGAME_MENU_H

View file

@ -178,7 +178,7 @@ static void connect_menu_on_connection_attempt(void) {
static void connect_menu_on_click(void) { static void connect_menu_on_click(void) {
gConnectionJoinError[0] = '\0'; gConnectionJoinError[0] = '\0';
keyboard_start_text_input(TIM_IP, custom_menu_close, connect_menu_on_connection_attempt); keyboard_start_text_input(TIM_IP, MAX_TEXT_INPUT, custom_menu_close, connect_menu_on_connection_attempt);
// fill in our last attempt // fill in our last attempt
if (configJoinPort == 0) { configJoinPort = DEFAULT_PORT; } if (configJoinPort == 0) { configJoinPort = DEFAULT_PORT; }

View file

@ -15,6 +15,8 @@
#include "pc/gfx/gfx_window_manager_api.h" #include "pc/gfx/gfx_window_manager_api.h"
#include "pc/pc_main.h" #include "pc/pc_main.h"
#include "engine/math_util.h" #include "engine/math_util.h"
#include "menu/file_select.h"
#include "game/chat.h"
// TODO: use some common lookup header // TODO: use some common lookup header
#define SCANCODE_BACKSPACE 0x0E #define SCANCODE_BACKSPACE 0x0E
@ -39,6 +41,7 @@ static u32 keyboard_lastkey = VK_INVALID;
char gTextInput[MAX_TEXT_INPUT]; char gTextInput[MAX_TEXT_INPUT];
static bool inTextInput = false; static bool inTextInput = false;
static u8 maxTextInput = 0;
u8 held_ctrl, held_shift, held_alt; u8 held_ctrl, held_shift, held_alt;
static enum TextInputMode textInputMode; static enum TextInputMode textInputMode;
@ -110,6 +113,13 @@ bool keyboard_on_key_down(int scancode) {
return FALSE; return FALSE;
} }
if (scancode == SCANCODE_ENTER) {
if (sSelectedFileNum != 0) {
chat_start_input();
return FALSE;
}
}
int mapped = keyboard_map_scancode(scancode); int mapped = keyboard_map_scancode(scancode);
keyboard_buttons_down |= mapped; keyboard_buttons_down |= mapped;
keyboard_lastkey = scancode; keyboard_lastkey = scancode;
@ -136,10 +146,11 @@ void keyboard_on_all_keys_up(void) {
keyboard_buttons_down = 0; keyboard_buttons_down = 0;
} }
char* keyboard_start_text_input(enum TextInputMode inInputMode, void (*onEscape)(void), void (*onEnter)(void)) { char* keyboard_start_text_input(enum TextInputMode inInputMode, u8 inMaxTextInput, void (*onEscape)(void), void (*onEnter)(void)) {
// set text-input events // set text-input events
textInputOnEscape = onEscape; textInputOnEscape = onEscape;
textInputOnEnter = onEnter; textInputOnEnter = onEnter;
maxTextInput = inMaxTextInput;
// clear buffer // clear buffer
for (int i = 0; i < MAX_TEXT_INPUT; i++) { gTextInput[i] = '\0'; } for (int i = 0; i < MAX_TEXT_INPUT; i++) { gTextInput[i] = '\0'; }
@ -202,6 +213,7 @@ void keyboard_on_text_input(char* text) {
while (*text != '\0') { while (*text != '\0') {
// make sure we don't overrun the buffer // make sure we don't overrun the buffer
if (i >= MAX_TEXT_INPUT) { break; } if (i >= MAX_TEXT_INPUT) { break; }
if (i >= maxTextInput) { break; }
// copy over character if we're allowed to input it // copy over character if we're allowed to input it
if (keyboard_allow_character_input(*text)) { if (keyboard_allow_character_input(*text)) {

View file

@ -10,7 +10,7 @@
extern "C" { extern "C" {
#endif #endif
#define MAX_TEXT_INPUT 256 #define MAX_TEXT_INPUT 255
extern char gTextInput[]; extern char gTextInput[];
enum TextInputMode { enum TextInputMode {
@ -23,7 +23,7 @@ bool keyboard_on_key_down(int scancode);
bool keyboard_on_key_up(int scancode); bool keyboard_on_key_up(int scancode);
void keyboard_on_all_keys_up(void); void keyboard_on_all_keys_up(void);
void keyboard_on_text_input(char* text); void keyboard_on_text_input(char* text);
char* keyboard_start_text_input(enum TextInputMode, void (*onEscape)(void), void (*onEnter)(void)); char* keyboard_start_text_input(enum TextInputMode, u8 inMaxTextInput, void (*onEscape)(void), void (*onEnter)(void));
void keyboard_stop_text_input(void); void keyboard_stop_text_input(void);
bool keyboard_in_text_input(void); bool keyboard_in_text_input(void);

View file

@ -49,7 +49,11 @@ static void register_launch_command(void) {
} }
#endif #endif
strncat(cmd, " --discord 1", MAX_LAUNCH_CMD - 1); strncat(cmd, " --discord 1", MAX_LAUNCH_CMD - 1);
DISCORD_REQUIRE(app.activities->register_command(app.activities, cmd)); int rc = app.activities->register_command(app.activities, cmd);
if (rc != DiscordResult_Ok) {
LOG_ERROR("register command failed %d", rc);
return;
}
LOG_INFO("cmd: %s", cmd); LOG_INFO("cmd: %s", cmd);
} }

View file

@ -126,6 +126,7 @@ void network_receive(u8* data, u16 dataLength) {
case PACKET_RESERVATION: network_receive_reservation(&p); break; case PACKET_RESERVATION: network_receive_reservation(&p); break;
case PACKET_SAVE_FILE_REQUEST: network_receive_save_file_request(&p); break; case PACKET_SAVE_FILE_REQUEST: network_receive_save_file_request(&p); break;
case PACKET_SAVE_FILE: network_receive_save_file(&p); break; case PACKET_SAVE_FILE: network_receive_save_file(&p); break;
case PACKET_CHAT: network_receive_chat(&p); break;
case PACKET_CUSTOM: network_receive_custom(&p); break; case PACKET_CUSTOM: network_receive_custom(&p); break;
default: LOG_ERROR("received unknown packet: %d", p.buffer[0]); default: LOG_ERROR("received unknown packet: %d", p.buffer[0]);
} }

View file

@ -47,6 +47,7 @@ enum PacketType {
PACKET_RESERVATION, PACKET_RESERVATION,
PACKET_SAVE_FILE_REQUEST, PACKET_SAVE_FILE_REQUEST,
PACKET_SAVE_FILE, PACKET_SAVE_FILE,
PACKET_CHAT,
PACKET_CUSTOM = 255, PACKET_CUSTOM = 255,
}; };
@ -184,4 +185,8 @@ u8 network_register_custom_packet(void (*send_callback)(struct Packet* p, void*
void network_send_custom(u8 customId, bool reliable, void* params); void network_send_custom(u8 customId, bool reliable, void* params);
void network_receive_custom(struct Packet* p); void network_receive_custom(struct Packet* p);
// packet_chat.c
void network_send_chat(char* message);
void network_receive_chat(struct Packet* p);
#endif #endif

View file

@ -0,0 +1,51 @@
#include <stdio.h>
#include "../network.h"
#include "game/chat.h"
static u8 localChatId = 1;
// two-player hack: the remoteChatIds stuff is only valid for the one remote player
// will need to be extended if MAX_PLAYERS is ever increased
#define MAX_CHAT_IDS 16
static u8 remoteChatIds[MAX_CHAT_IDS] = { 0 };
static u8 onRemoteChatId = 0;
void network_send_chat(char* message) {
u16 messageLength = strlen(message);
struct Packet p;
packet_init(&p, PACKET_CHAT, true);
packet_write(&p, &localChatId, sizeof(u8));
packet_write(&p, &messageLength, sizeof(u16));
packet_write(&p, message, messageLength * sizeof(u8));
network_send(&p);
localChatId++;
}
void network_receive_chat(struct Packet* p) {
u8 remoteChatId = 0;
u16 remoteMessageLength = 0;
char remoteMessage[255] = { 0 };
packet_read(p, &remoteChatId, sizeof(u8));
packet_read(p, &remoteMessageLength, sizeof(u16));
if (remoteMessageLength > 255) { remoteMessageLength = 254; }
packet_read(p, &remoteMessage, remoteMessageLength * sizeof(u8));
// check if remote chat id has already been seen
for (u16 i = 0; i < MAX_CHAT_IDS; i++) {
if (remoteChatIds[i] == remoteChatId) {
// we already saw this message!
return;
}
}
// cache the seen id
remoteChatIds[onRemoteChatId] = remoteChatId;
onRemoteChatId = (onRemoteChatId + 1) % MAX_CHAT_IDS;
// add the message
chat_add_message(remoteMessage, FALSE);
return;
}