diff options
-rw-r--r-- | projects/openttd_vs80.vcproj | 4 | ||||
-rw-r--r-- | projects/openttd_vs90.vcproj | 4 | ||||
-rw-r--r-- | source.list | 1 | ||||
-rw-r--r-- | src/date.cpp | 13 | ||||
-rw-r--r-- | src/gfx.cpp | 11 | ||||
-rw-r--r-- | src/misc.cpp | 5 | ||||
-rw-r--r-- | src/network/network.cpp | 2 | ||||
-rw-r--r-- | src/network/network.h | 2 | ||||
-rw-r--r-- | src/network/network_chat_gui.cpp | 487 | ||||
-rw-r--r-- | src/network/network_func.h | 5 | ||||
-rw-r--r-- | src/network/network_gui.cpp | 248 | ||||
-rw-r--r-- | src/texteff.cpp | 226 | ||||
-rw-r--r-- | src/texteff.hpp | 5 | ||||
-rw-r--r-- | src/video/cocoa/event.mm | 2 | ||||
-rw-r--r-- | src/video/sdl_v.cpp | 2 | ||||
-rw-r--r-- | src/video/win32_v.cpp | 2 | ||||
-rw-r--r-- | src/window.cpp | 3 |
17 files changed, 531 insertions, 491 deletions
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index 1e91ec906..0e595590d 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -1768,6 +1768,10 @@ > </File> <File + RelativePath=".\..\src\network\network_chat_gui.cpp" + > + </File> + <File RelativePath=".\..\src\network\network_gui.cpp" > </File> diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj index 8681cda4d..1585e40da 100644 --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -1765,6 +1765,10 @@ > </File> <File + RelativePath=".\..\src\network\network_chat_gui.cpp" + > + </File> + <File RelativePath=".\..\src\network\network_gui.cpp" > </File> diff --git a/source.list b/source.list index a20900f38..42b22b061 100644 --- a/source.list +++ b/source.list @@ -385,6 +385,7 @@ intro_gui.cpp main_gui.cpp misc_gui.cpp music_gui.cpp +network/network_chat_gui.cpp network/network_gui.cpp newgrf_gui.cpp news_gui.cpp diff --git a/src/date.cpp b/src/date.cpp index f6f4f19aa..0e99e314a 100644 --- a/src/date.cpp +++ b/src/date.cpp @@ -33,10 +33,6 @@ void SetDate(Date date) ConvertDateToYMD(date, &ymd); _cur_year = ymd.year; _cur_month = ymd.month; -#ifdef ENABLE_NETWORK - _network_last_advertise_frame = 0; - _network_need_advertise = true; -#endif /* ENABLE_NETWORK */ } #define M(a, b) ((a << 5) | b) @@ -161,7 +157,6 @@ Date ConvertYMDToDate(Year year, Month month, Day day) /** Functions used by the IncreaseDate function */ extern void WaypointsDailyLoop(); -extern void ChatMessageDailyLoop(); extern void EnginesDailyLoop(); extern void DisasterDailyLoop(); @@ -228,7 +223,9 @@ void IncreaseDate() /* yeah, increase day counter and call various daily loops */ _date++; - ChatMessageDailyLoop(); +#ifdef ENABLE_NETWORK + NetworkChatMessageDailyLoop(); +#endif /* ENABLE_NETWORK */ DisasterDailyLoop(); WaypointsDailyLoop(); @@ -296,9 +293,11 @@ void IncreaseDate() _date -= days_this_year; FOR_ALL_VEHICLES(v) v->date_of_last_service -= days_this_year; +#ifdef ENABLE_NETWORK /* Because the _date wraps here, and text-messages expire by game-days, we have to clean out * all of them if the date is set back, else those messages will hang for ever */ - InitChatMessage(); + NetworkInitChatMessage(); +#endif /* ENABLE_NETWORK */ } if (_settings_client.gui.auto_euro) CheckSwitchToEuro(); diff --git a/src/gfx.cpp b/src/gfx.cpp index 3da6d25a2..79e67586e 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -20,6 +20,7 @@ #include "core/alloc_func.hpp" #include "core/sort_func.hpp" #include "landscape_type.h" +#include "network/network_func.h" #include "table/palettes.h" #include "table/sprites.h" @@ -81,7 +82,10 @@ void GfxScroll(int left, int top, int width, int height, int xo, int yo) if (xo == 0 && yo == 0) return; if (_cursor.visible) UndrawMouseCursor(); - UndrawChatMessage(); + +#ifdef ENABLE_NETWORK + NetworkUndrawChatMessage(); +#endif /* ENABLE_NETWORK */ blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo); /* This part of the screen is now dirty. */ @@ -1252,7 +1256,10 @@ void RedrawScreenRect(int left, int top, int right, int bottom) UndrawMouseCursor(); } } - UndrawChatMessage(); + +#ifdef ENABLE_NETWORK + NetworkUndrawChatMessage(); +#endif /* ENABLE_NETWORK */ DrawOverlappedWindowForAll(left, top, right, bottom); diff --git a/src/misc.cpp b/src/misc.cpp index 3e89366b9..463b05707 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -29,6 +29,7 @@ #include "animated_tile_func.h" #include "tilehighlight_func.h" #include "core/bitmath_func.hpp" +#include "network/network_func.h" #include "table/strings.h" #include "table/sprites.h" @@ -105,7 +106,9 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date) InitializeCheats(); InitTextEffects(); - InitChatMessage(); +#ifdef ENABLE_NETWORK + NetworkInitChatMessage(); +#endif /* ENABLE_NETWORK */ InitializeAnimatedTiles(); InitializeLandscapeVariables(false); diff --git a/src/network/network.cpp b/src/network/network.cpp index 437023636..54f6dd1b4 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -230,7 +230,7 @@ void CDECL NetworkTextMessage(NetworkAction action, ConsoleColour color, bool se DebugDumpCommands("ddc:cmsg:%d;%d;%s\n", _date, _date_fract, message); IConsolePrintF(color, "%s", message); - AddChatMessage(color, duration, "%s", message); + NetworkAddChatMessage(color, duration, "%s", message); } // Calculate the frame-lag of a client diff --git a/src/network/network.h b/src/network/network.h index 8de34f112..9c913ceb6 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -11,6 +11,7 @@ void NetworkStartUp(); void NetworkShutDown(); +void NetworkDrawChatMessage(); extern bool _networking; ///< are we in networking mode? extern bool _network_server; ///< network-server is active @@ -23,6 +24,7 @@ extern bool _is_network_server; ///< Does this client wants to be a network-ser static inline void NetworkStartUp() {} static inline void NetworkShutDown() {} +static inline void NetworkDrawChatMessage() {} #define _networking 0 #define _network_server 0 diff --git a/src/network/network_chat_gui.cpp b/src/network/network_chat_gui.cpp new file mode 100644 index 000000000..2d4289ffb --- /dev/null +++ b/src/network/network_chat_gui.cpp @@ -0,0 +1,487 @@ +/* $Id$ */ + +/** @file network_chat_gui.cpp GUI for handling chat messages. */ + +#include <stdarg.h> /* va_list */ + +#ifdef ENABLE_NETWORK + +#include "../stdafx.h" +#include "network.h" +#include "network_type.h" +#include "../date_func.h" +#include "../gfx_func.h" +#include "../strings_func.h" +#include "../blitter/factory.hpp" +#include "../console_func.h" +#include "../video/video_driver.hpp" +#include "../table/sprites.h" +#include "../window_gui.h" +#include "../textbuf_gui.h" +#include "../querystring_gui.h" +#include "../town.h" +#include "../window_func.h" +#include "network_internal.h" +#include "network_client.h" + +#include "table/strings.h" + +enum { + MAX_CHAT_MESSAGES = 10, +}; + +struct ChatMessage { + char message[NETWORK_CHAT_LENGTH]; + uint16 color; + Date end_date; +}; + +/* used for chat window */ +static ChatMessage _chatmsg_list[MAX_CHAT_MESSAGES]; +static bool _chatmessage_dirty = false; +static bool _chatmessage_visible = false; +static bool _chat_tab_completion_active; + +/* The chatbox grows from the bottom so the coordinates are pixels from + * the left and pixels from the bottom. The height is the maximum height */ +static const PointDimension _chatmsg_box = {10, 30, 500, 150}; +static uint8 _chatmessage_backup[150 * 500 * 6]; // (height * width) + +static inline uint GetChatMessageCount() +{ + uint i = 0; + for (; i < MAX_CHAT_MESSAGES; i++) { + if (_chatmsg_list[i].message[0] == '\0') break; + } + + return i; +} + +/** + * Add a text message to the 'chat window' to be shown + * @param color The colour this message is to be shown in + * @param duration The duration of the chat message in game-days + * @param message message itself in printf() style + */ +void CDECL NetworkAddChatMessage(uint16 color, uint8 duration, const char *message, ...) +{ + char buf[NETWORK_CHAT_LENGTH]; + const char *bufp; + va_list va; + uint msg_count; + uint16 lines; + + va_start(va, message); + vsnprintf(buf, lengthof(buf), message, va); + va_end(va); + + + Utf8TrimString(buf, NETWORK_CHAT_LENGTH); + + /* Force linebreaks for strings that are too long */ + lines = GB(FormatStringLinebreaks(buf, _chatmsg_box.width - 8), 0, 16) + 1; + if (lines >= MAX_CHAT_MESSAGES) return; + + msg_count = GetChatMessageCount(); + /* We want to add more chat messages than there is free space for, remove 'old' */ + if (lines > MAX_CHAT_MESSAGES - msg_count) { + int i = lines - (MAX_CHAT_MESSAGES - msg_count); + memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i)); + msg_count = MAX_CHAT_MESSAGES - lines; + } + + for (bufp = buf; lines != 0; lines--) { + ChatMessage *cmsg = &_chatmsg_list[msg_count++]; + ttd_strlcpy(cmsg->message, bufp, sizeof(cmsg->message)); + + /* The default colour for a message is player colour. Replace this with + * white for any additional lines */ + cmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR; + cmsg->end_date = _date + duration; + + bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string + } + + _chatmessage_dirty = true; +} + +void NetworkInitChatMessage() +{ + for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) { + _chatmsg_list[i].message[0] = '\0'; + } +} + +/** Hide the chatbox */ +void NetworkUndrawChatMessage() +{ + if (_chatmessage_visible) { + Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); + /* Sometimes we also need to hide the cursor + * This is because both textmessage and the cursor take a shot of the + * screen before drawing. + * Now the textmessage takes his shot and paints his data before the cursor + * does, so in the shot of the cursor is the screen-data of the textmessage + * included when the cursor hangs somewhere over the textmessage. To + * avoid wrong repaints, we undraw the cursor in that case, and everything + * looks nicely ;) + * (and now hope this story above makes sense to you ;)) + */ + + if (_cursor.visible) { + if (_cursor.draw_pos.x + _cursor.draw_size.x >= _chatmsg_box.x && + _cursor.draw_pos.x <= _chatmsg_box.x + _chatmsg_box.width && + _cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _chatmsg_box.y - _chatmsg_box.height && + _cursor.draw_pos.y <= _screen.height - _chatmsg_box.y) { + UndrawMouseCursor(); + } + } + + int x = _chatmsg_box.x; + int y = _screen.height - _chatmsg_box.y - _chatmsg_box.height; + int width = _chatmsg_box.width; + int height = _chatmsg_box.height; + if (y < 0) { + height = max(height + y, min(_chatmsg_box.height, _screen.height)); + y = 0; + } + if (x + width >= _screen.width) { + width = _screen.width - x; + } + if (width <= 0 || height <= 0) return; + + _chatmessage_visible = false; + /* Put our 'shot' back to the screen */ + blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height); + /* And make sure it is updated next time */ + _video_driver->MakeDirty(x, y, width, height); + + _chatmessage_dirty = true; + } +} + +/** Check if a message is expired every day */ +void NetworkChatMessageDailyLoop() +{ + for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) { + ChatMessage *cmsg = &_chatmsg_list[i]; + if (cmsg->message[0] == '\0') continue; + + /* Message has expired, remove from the list */ + if (cmsg->end_date < _date) { + /* Move the remaining messages over the current message */ + if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1)); + + /* Mark the last item as empty */ + _chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0'; + _chatmessage_dirty = true; + + /* Go one item back, because we moved the array 1 to the left */ + i--; + } + } +} + +/** Draw the chat message-box */ +void NetworkDrawChatMessage() +{ + Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); + if (!_chatmessage_dirty) return; + + /* First undraw if needed */ + NetworkUndrawChatMessage(); + + if (_iconsole_mode == ICONSOLE_FULL) return; + + /* Check if we have anything to draw at all */ + uint count = GetChatMessageCount(); + if (count == 0) return; + + int x = _chatmsg_box.x; + int y = _screen.height - _chatmsg_box.y - _chatmsg_box.height; + int width = _chatmsg_box.width; + int height = _chatmsg_box.height; + if (y < 0) { + height = max(height + y, min(_chatmsg_box.height, _screen.height)); + y = 0; + } + if (x + width >= _screen.width) { + width = _screen.width - x; + } + if (width <= 0 || height <= 0) return; + + assert(blitter->BufferSize(width, height) < (int)sizeof(_chatmessage_backup)); + + /* Make a copy of the screen as it is before painting (for undraw) */ + blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height); + + _cur_dpi = &_screen; // switch to _screen painting + + /* Paint a half-transparent box behind the chat messages */ + GfxFillRect( + _chatmsg_box.x, + _screen.height - _chatmsg_box.y - count * 13 - 2, + _chatmsg_box.x + _chatmsg_box.width - 1, + _screen.height - _chatmsg_box.y - 2, + PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOR // black, but with some alpha for background + ); + + /* Paint the chat messages starting with the lowest at the bottom */ + for (uint y = 13; count-- != 0; y += 13) { + DoDrawString(_chatmsg_list[count].message, _chatmsg_box.x + 3, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].color); + } + + /* Make sure the data is updated next flush */ + _video_driver->MakeDirty(x, y, width, height); + + _chatmessage_visible = true; + _chatmessage_dirty = false; +} + + +static void SendChat(const char *buf, DestType type, int dest) +{ + if (StrEmpty(buf)) return; + if (!_network_server) { + SEND_COMMAND(PACKET_CLIENT_CHAT)((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf); + } else { + NetworkServerSendChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, NETWORK_SERVER_INDEX); + } +} + + +struct NetworkChatWindow : public QueryStringBaseWindow { + DestType dtype; + int dest; + + NetworkChatWindow (const WindowDesc *desc, DestType type, int dest) : QueryStringBaseWindow(NETWORK_CHAT_LENGTH, desc) + { + this->LowerWidget(2); + this->dtype = type; + this->dest = dest; + this->afilter = CS_ALPHANUMERAL; + InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 0); + + InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height); + SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys + + _chat_tab_completion_active = false; + + this->FindWindowPlacementAndResize(desc); + } + + ~NetworkChatWindow () + { + InvalidateWindowData(WC_NEWS_WINDOW, 0, 0); + ClrBit(_no_scroll, SCROLL_CHAT); + } + + /** + * Find the next item of the list of things that can be auto-completed. + * @param item The current indexed item to return. This function can, and most + * likely will, alter item, to skip empty items in the arrays. + * @return Returns the char that matched to the index. + */ + const char *ChatTabCompletionNextItem(uint *item) + { + static char chat_tab_temp_buffer[64]; + + /* First, try clients */ + if (*item < MAX_CLIENT_INFO) { + /* Skip inactive clients */ + while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++; + if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name; + } + + /* Then, try townnames */ + /* Not that the following assumes all town indices are adjacent, ie no + * towns have been deleted. */ + if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) { + const Town *t; + + FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) { + /* Get the town-name via the string-system */ + SetDParam(0, t->index); + GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer)); + return &chat_tab_temp_buffer[0]; + } + } + + return NULL; + } + + /** + * Find what text to complete. It scans for a space from the left and marks + * the word right from that as to complete. It also writes a \0 at the + * position of the space (if any). If nothing found, buf is returned. + */ + static char *ChatTabCompletionFindText(char *buf) + { + char *p = strrchr(buf, ' '); + if (p == NULL) return buf; + + *p = '\0'; + return p + 1; + } + + /** + * See if we can auto-complete the current text of the user. + */ + void ChatTabCompletion() + { + static char _chat_tab_completion_buf[NETWORK_CHAT_LENGTH]; + assert(this->edit_str_size == lengthof(_chat_tab_completion_buf)); + + Textbuf *tb = &this->text; + size_t len, tb_len; + uint item; + char *tb_buf, *pre_buf; + const char *cur_name; + bool second_scan = false; + + item = 0; + + /* Copy the buffer so we can modify it without damaging the real data */ + pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf); + + tb_buf = ChatTabCompletionFindText(pre_buf); + tb_len = strlen(tb_buf); + + while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) { + item++; + + if (_chat_tab_completion_active) { + /* We are pressing TAB again on the same name, is there an other name + * that starts with this? */ + if (!second_scan) { + size_t offset; + size_t length; + + /* If we are completing at the begin of the line, skip the ': ' we added */ + if (tb_buf == pre_buf) { + offset = 0; + length = tb->length - 2; + } else { + /* Else, find the place we are completing at */ + offset = strlen(pre_buf) + 1; + length = tb->length - offset; + } + + /* Compare if we have a match */ + if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true; + + continue; + } + + /* Now any match we make on _chat_tab_completion_buf after this, is perfect */ + } + + len = strlen(cur_name); + if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) { + /* Save the data it was before completion */ + if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf); + _chat_tab_completion_active = true; + + /* Change to the found name. Add ': ' if we are at the start of the line (pretty) */ + if (pre_buf == tb_buf) { + snprintf(tb->buf, this->edit_str_size, "%s: ", cur_name); + } else { + snprintf(tb->buf, this->edit_str_size, "%s %s", pre_buf, cur_name); + } + + /* Update the textbuffer */ + UpdateTextBufferSize(&this->text); + + this->SetDirty(); + free(pre_buf); + return; + } + } + + if (second_scan) { + /* We walked all posibilities, and the user presses tab again.. revert to original text */ + strcpy(tb->buf, _chat_tab_completion_buf); + _chat_tab_completion_active = false; + + /* Update the textbuffer */ + UpdateTextBufferSize(&this->text); + + this->SetDirty(); + } + free(pre_buf); + } + + virtual void OnPaint() + { + static const StringID chat_captions[] = { + STR_NETWORK_CHAT_ALL_CAPTION, + STR_NETWORK_CHAT_COMPANY_CAPTION, + STR_NETWORK_CHAT_CLIENT_CAPTION + }; + + this->DrawWidgets(); + + assert((uint)this->dtype < lengthof(chat_captions)); + DrawStringRightAligned(this->widget[2].left - 2, this->widget[2].top + 1, chat_captions[this->dtype], TC_BLACK); + this->DrawEditBox(2); + } + + virtual void OnClick(Point pt, int widget) + { + switch (widget) { + case 2: + ShowOnScreenKeyboard(this, 2, 0, 3); + break; + + case 3: /* Send */ + SendChat(this->text.buf, this->dtype, this->dest); + /* FALLTHROUGH */ + case 0: /* Cancel */ delete this; break; + } + } + + virtual void OnMouseLoop() + { + this->HandleEditBox(2); + } + + virtual EventState OnKeyPress(uint16 key, uint16 keycode) + { + EventState state = ES_NOT_HANDLED; + if (keycode == WKC_TAB) { + ChatTabCompletion(); + } else { + _chat_tab_completion_active = false; + switch (this->HandleEditBoxKey(2, key, keycode, state)) { + case 1: /* Return */ + SendChat(this->text.buf, this->dtype, this->dest); + /* FALLTHROUGH */ + case 2: /* Escape */ delete this; break; + } + } + return state; + } +}; + +static const Widget _chat_window_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, COLOUR_GREY, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_PANEL, RESIZE_RIGHT, COLOUR_GREY, 11, 319, 0, 13, 0x0, STR_NULL}, // background +{ WWT_EDITBOX, RESIZE_RIGHT, COLOUR_GREY, 75, 257, 1, 12, STR_NETWORK_CHAT_OSKTITLE, STR_NULL}, // text box +{ WWT_PUSHTXTBTN, RESIZE_LR, COLOUR_GREY, 258, 319, 1, 12, STR_NETWORK_SEND, STR_NULL}, // send button +{ WIDGETS_END}, +}; + +static const WindowDesc _chat_window_desc = { + WDP_CENTER, -26, 320, 14, 640, 14, // x, y, width, height + WC_SEND_NETWORK_MSG, WC_NONE, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET, + _chat_window_widgets, +}; + +void ShowNetworkChatQueryWindow(DestType type, int dest) +{ + DeleteWindowById(WC_SEND_NETWORK_MSG, 0); + new NetworkChatWindow (&_chat_window_desc, type, dest); +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_func.h b/src/network/network_func.h index 3f7612751..cd1a5aa5d 100644 --- a/src/network/network_func.h +++ b/src/network/network_func.h @@ -57,6 +57,11 @@ void NetworkServerSendRcon(uint16 client_index, ConsoleColour colour_code, const void NetworkServerSendError(uint16 client_index, NetworkErrorCode error); void NetworkServerSendChat(NetworkAction action, DestType type, int dest, const char *msg, uint16 from_index); +void NetworkInitChatMessage(); +void CDECL NetworkAddChatMessage(uint16 color, uint8 duration, const char *message, ...); +void NetworkUndrawChatMessage(); +void NetworkChatMessageDailyLoop(); + #define FOR_ALL_ACTIVE_CLIENT_INFOS(ci) for (ci = _network_client_info; ci != endof(_network_client_info); ci++) if (ci->client_index != NETWORK_EMPTY_INDEX) #endif /* ENABLE_NETWORK */ diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 70ddccd92..d8ebfffea 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -15,11 +15,9 @@ #include "network_gamelist.h" #include "../gui.h" #include "../window_gui.h" -#include "../textbuf_gui.h" #include "../variables.h" #include "network_server.h" #include "network_udp.h" -#include "../town.h" #include "../newgrf.h" #include "../functions.h" #include "../window_func.h" @@ -37,8 +35,6 @@ #include "../table/sprites.h" -static bool _chat_tab_completion_active; - static void ShowNetworkStartServerWindow(); static void ShowNetworkLobbyWindow(NetworkGameList *ngl); extern void SwitchMode(int new_mode); @@ -1732,250 +1728,6 @@ void ShowJoinStatusWindow() new NetworkJoinStatusWindow(&_network_join_status_window_desc); } -static void SendChat(const char *buf, DestType type, int dest) -{ - if (StrEmpty(buf)) return; - if (!_network_server) { - SEND_COMMAND(PACKET_CLIENT_CHAT)((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf); - } else { - NetworkServerSendChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, NETWORK_SERVER_INDEX); - } -} - - -struct NetworkChatWindow : public QueryStringBaseWindow { - DestType dtype; - int dest; - - NetworkChatWindow (const WindowDesc *desc, DestType type, int dest) : QueryStringBaseWindow(NETWORK_CHAT_LENGTH, desc) - { - this->LowerWidget(2); - this->dtype = type; - this->dest = dest; - this->afilter = CS_ALPHANUMERAL; - InitializeTextBuffer(&this->text, this->edit_str_buf, this->edit_str_size, 0); - - InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height); - SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys - - _chat_tab_completion_active = false; - - this->FindWindowPlacementAndResize(desc); - } - - ~NetworkChatWindow () - { - InvalidateWindowData(WC_NEWS_WINDOW, 0, 0); - ClrBit(_no_scroll, SCROLL_CHAT); - } - - /** - * Find the next item of the list of things that can be auto-completed. - * @param item The current indexed item to return. This function can, and most - * likely will, alter item, to skip empty items in the arrays. - * @return Returns the char that matched to the index. - */ - const char *ChatTabCompletionNextItem(uint *item) - { - static char chat_tab_temp_buffer[64]; - - /* First, try clients */ - if (*item < MAX_CLIENT_INFO) { - /* Skip inactive clients */ - while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++; - if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name; - } - - /* Then, try townnames */ - /* Not that the following assumes all town indices are adjacent, ie no - * towns have been deleted. */ - if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) { - const Town *t; - - FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) { - /* Get the town-name via the string-system */ - SetDParam(0, t->index); - GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer)); - return &chat_tab_temp_buffer[0]; - } - } - - return NULL; - } - - /** - * Find what text to complete. It scans for a space from the left and marks - * the word right from that as to complete. It also writes a \0 at the - * position of the space (if any). If nothing found, buf is returned. - */ - static char *ChatTabCompletionFindText(char *buf) - { - char *p = strrchr(buf, ' '); - if (p == NULL) return buf; - - *p = '\0'; - return p + 1; - } - - /** - * See if we can auto-complete the current text of the user. - */ - void ChatTabCompletion() - { - static char _chat_tab_completion_buf[NETWORK_CHAT_LENGTH]; - assert(this->edit_str_size == lengthof(_chat_tab_completion_buf)); - - Textbuf *tb = &this->text; - size_t len, tb_len; - uint item; - char *tb_buf, *pre_buf; - const char *cur_name; - bool second_scan = false; - - item = 0; - - /* Copy the buffer so we can modify it without damaging the real data */ - pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf); - - tb_buf = ChatTabCompletionFindText(pre_buf); - tb_len = strlen(tb_buf); - - while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) { - item++; - - if (_chat_tab_completion_active) { - /* We are pressing TAB again on the same name, is there an other name - * that starts with this? */ - if (!second_scan) { - size_t offset; - size_t length; - - /* If we are completing at the begin of the line, skip the ': ' we added */ - if (tb_buf == pre_buf) { - offset = 0; - length = tb->length - 2; - } else { - /* Else, find the place we are completing at */ - offset = strlen(pre_buf) + 1; - length = tb->length - offset; - } - - /* Compare if we have a match */ - if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true; - - continue; - } - - /* Now any match we make on _chat_tab_completion_buf after this, is perfect */ - } - - len = strlen(cur_name); - if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) { - /* Save the data it was before completion */ - if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf); - _chat_tab_completion_active = true; - - /* Change to the found name. Add ': ' if we are at the start of the line (pretty) */ - if (pre_buf == tb_buf) { - snprintf(tb->buf, this->edit_str_size, "%s: ", cur_name); - } else { - snprintf(tb->buf, this->edit_str_size, "%s %s", pre_buf, cur_name); - } - - /* Update the textbuffer */ - UpdateTextBufferSize(&this->text); - - this->SetDirty(); - free(pre_buf); - return; - } - } - - if (second_scan) { - /* We walked all posibilities, and the user presses tab again.. revert to original text */ - strcpy(tb->buf, _chat_tab_completion_buf); - _chat_tab_completion_active = false; - - /* Update the textbuffer */ - UpdateTextBufferSize(&this->text); - - this->SetDirty(); - } - free(pre_buf); - } - - virtual void OnPaint() - { - static const StringID chat_captions[] = { - STR_NETWORK_CHAT_ALL_CAPTION, - STR_NETWORK_CHAT_COMPANY_CAPTION, - STR_NETWORK_CHAT_CLIENT_CAPTION - }; - - this->DrawWidgets(); - - assert((uint)this->dtype < lengthof(chat_captions)); - DrawStringRightAligned(this->widget[2].left - 2, this->widget[2].top + 1, chat_captions[this->dtype], TC_BLACK); - this->DrawEditBox(2); - } - - virtual void OnClick(Point pt, int widget) - { - switch (widget) { - case 2: - ShowOnScreenKeyboard(this, 2, 0, 3); - break; - - case 3: /* Send */ - SendChat(this->text.buf, this->dtype, this->dest); - /* FALLTHROUGH */ - case 0: /* Cancel */ delete this; break; - } - } - - virtual void OnMouseLoop() - { - this->HandleEditBox(2); - } - - virtual EventState OnKeyPress(uint16 key, uint16 keycode) - { - EventState state = ES_NOT_HANDLED; - if (keycode == WKC_TAB) { - ChatTabCompletion(); - } else { - _chat_tab_completion_active = false; - switch (this->HandleEditBoxKey(2, key, keycode, state)) { - case 1: /* Return */ - SendChat(this->text.buf, this->dtype, this->dest); - /* FALLTHROUGH */ - case 2: /* Escape */ delete this; break; - } - } - return state; - } -}; - -static const Widget _chat_window_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, COLOUR_GREY, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_PANEL, RESIZE_RIGHT, COLOUR_GREY, 11, 319, 0, 13, 0x0, STR_NULL}, // background -{ WWT_EDITBOX, RESIZE_RIGHT, COLOUR_GREY, 75, 257, 1, 12, STR_NETWORK_CHAT_OSKTITLE, STR_NULL}, // text box -{ WWT_PUSHTXTBTN, RESIZE_LR, COLOUR_GREY, 258, 319, 1, 12, STR_NETWORK_SEND, STR_NULL}, // send button -{ WIDGETS_END}, -}; - -static const WindowDesc _chat_window_desc = { - WDP_CENTER, -26, 320, 14, 640, 14, // x, y, width, height - WC_SEND_NETWORK_MSG, WC_NONE, - WDF_STD_TOOLTIPS | WDF_DEF_WIDGET, - _chat_window_widgets, -}; - -void ShowNetworkChatQueryWindow(DestType type, int dest) -{ - DeleteWindowById(WC_SEND_NETWORK_MSG, 0); - new NetworkChatWindow (&_chat_window_desc, type, dest); -} /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */ enum NetworkCompanyPasswordWindowWidgets { diff --git a/src/texteff.cpp b/src/texteff.cpp index c6b8daa47..cf71d9c5d 100644 --- a/src/texteff.cpp +++ b/src/texteff.cpp @@ -6,27 +6,18 @@ #include "openttd.h" #include "landscape.h" #include "gfx_func.h" -#include "console_func.h" #include "variables.h" -#include "blitter/factory.hpp" #include "texteff.hpp" -#include "video/video_driver.hpp" +#include "core/bitmath_func.hpp" #include "transparency.h" #include "strings_func.h" #include "core/alloc_func.hpp" -#include "date_func.h" #include "functions.h" #include "viewport_func.h" #include "settings_type.h" -#include "table/sprites.h" - -#include <stdarg.h> /* va_list */ - enum { - MAX_TEXTMESSAGE_LENGTH = 200, - INIT_NUM_TEXT_MESSAGES = 20, - MAX_CHAT_MESSAGES = 10, + INIT_NUM_TEXT_EFFECTS = 20, }; struct TextEffect { @@ -41,220 +32,9 @@ struct TextEffect { TextEffectMode mode; }; - -struct ChatMessage { - char message[MAX_TEXTMESSAGE_LENGTH]; - uint16 color; - Date end_date; -}; - /* used for text effects */ static TextEffect *_text_effect_list = NULL; -static uint16 _num_text_effects = INIT_NUM_TEXT_MESSAGES; - -/* used for chat window */ -static ChatMessage _chatmsg_list[MAX_CHAT_MESSAGES]; -static bool _chatmessage_dirty = false; -static bool _chatmessage_visible = false; - -/* The chatbox grows from the bottom so the coordinates are pixels from - * the left and pixels from the bottom. The height is the maximum height */ -static const PointDimension _chatmsg_box = {10, 30, 500, 150}; -static uint8 _chatmessage_backup[150 * 500 * 6]; // (height * width) - -static inline uint GetChatMessageCount() -{ - uint i; - - for (i = 0; i < MAX_CHAT_MESSAGES; i++) { - if (_chatmsg_list[i].message[0] == '\0') break; - } - - return i; -} - -/* Add a text message to the 'chat window' to be shown - * @param color The colour this message is to be shown in - * @param duration The duration of the chat message in game-days - * @param message message itself in printf() style */ -void CDECL AddChatMessage(uint16 color, uint8 duration, const char *message, ...) -{ - char buf[MAX_TEXTMESSAGE_LENGTH]; - const char *bufp; - va_list va; - uint msg_count; - uint16 lines; - - va_start(va, message); - vsnprintf(buf, lengthof(buf), message, va); - va_end(va); - - - Utf8TrimString(buf, MAX_TEXTMESSAGE_LENGTH); - - /* Force linebreaks for strings that are too long */ - lines = GB(FormatStringLinebreaks(buf, _chatmsg_box.width - 8), 0, 16) + 1; - if (lines >= MAX_CHAT_MESSAGES) return; - - msg_count = GetChatMessageCount(); - /* We want to add more chat messages than there is free space for, remove 'old' */ - if (lines > MAX_CHAT_MESSAGES - msg_count) { - int i = lines - (MAX_CHAT_MESSAGES - msg_count); - memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i)); - msg_count = MAX_CHAT_MESSAGES - lines; - } - - for (bufp = buf; lines != 0; lines--) { - ChatMessage *cmsg = &_chatmsg_list[msg_count++]; - ttd_strlcpy(cmsg->message, bufp, sizeof(cmsg->message)); - - /* The default colour for a message is player colour. Replace this with - * white for any additional lines */ - cmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR; - cmsg->end_date = _date + duration; - - bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string - } - - _chatmessage_dirty = true; -} - -void InitChatMessage() -{ - uint i; - - for (i = 0; i < MAX_CHAT_MESSAGES; i++) { - _chatmsg_list[i].message[0] = '\0'; - } -} - -/** Hide the chatbox */ -void UndrawChatMessage() -{ - if (_chatmessage_visible) { - Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); - /* Sometimes we also need to hide the cursor - * This is because both textmessage and the cursor take a shot of the - * screen before drawing. - * Now the textmessage takes his shot and paints his data before the cursor - * does, so in the shot of the cursor is the screen-data of the textmessage - * included when the cursor hangs somewhere over the textmessage. To - * avoid wrong repaints, we undraw the cursor in that case, and everything - * looks nicely ;) - * (and now hope this story above makes sense to you ;)) - */ - - if (_cursor.visible) { - if (_cursor.draw_pos.x + _cursor.draw_size.x >= _chatmsg_box.x && - _cursor.draw_pos.x <= _chatmsg_box.x + _chatmsg_box.width && - _cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _chatmsg_box.y - _chatmsg_box.height && - _cursor.draw_pos.y <= _screen.height - _chatmsg_box.y) { - UndrawMouseCursor(); - } - } - - int x = _chatmsg_box.x; - int y = _screen.height - _chatmsg_box.y - _chatmsg_box.height; - int width = _chatmsg_box.width; - int height = _chatmsg_box.height; - if (y < 0) { - height = max(height + y, min(_chatmsg_box.height, _screen.height)); - y = 0; - } - if (x + width >= _screen.width) { - width = _screen.width - x; - } - if (width <= 0 || height <= 0) return; - - _chatmessage_visible = false; - /* Put our 'shot' back to the screen */ - blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height); - /* And make sure it is updated next time */ - _video_driver->MakeDirty(x, y, width, height); - - _chatmessage_dirty = true; - } -} - -/** Check if a message is expired every day */ -void ChatMessageDailyLoop() -{ - uint i; - - for (i = 0; i < MAX_CHAT_MESSAGES; i++) { - ChatMessage *cmsg = &_chatmsg_list[i]; - if (cmsg->message[0] == '\0') continue; - - /* Message has expired, remove from the list */ - if (cmsg->end_date < _date) { - /* Move the remaining messages over the current message */ - if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1)); - - /* Mark the last item as empty */ - _chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0'; - _chatmessage_dirty = true; - - /* Go one item back, because we moved the array 1 to the left */ - i--; - } - } -} - -/** Draw the chat message-box */ -void DrawChatMessage() -{ - Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); - if (!_chatmessage_dirty) return; - - /* First undraw if needed */ - UndrawChatMessage(); - - if (_iconsole_mode == ICONSOLE_FULL) return; - - /* Check if we have anything to draw at all */ - uint count = GetChatMessageCount(); - if (count == 0) return; - - int x = _chatmsg_box.x; - int y = _screen.height - _chatmsg_box.y - _chatmsg_box.height; - int width = _chatmsg_box.width; - int height = _chatmsg_box.height; - if (y < 0) { - height = max(height + y, min(_chatmsg_box.height, _screen.height)); - y = 0; - } - if (x + width >= _screen.width) { - width = _screen.width - x; - } - if (width <= 0 || height <= 0) return; - - assert(blitter->BufferSize(width, height) < (int)sizeof(_chatmessage_backup)); - - /* Make a copy of the screen as it is before painting (for undraw) */ - blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height); - - _cur_dpi = &_screen; // switch to _screen painting - - /* Paint a half-transparent box behind the chat messages */ - GfxFillRect( - _chatmsg_box.x, - _screen.height - _chatmsg_box.y - count * 13 - 2, - _chatmsg_box.x + _chatmsg_box.width - 1, - _screen.height - _chatmsg_box.y - 2, - PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOR // black, but with some alpha for background - ); - - /* Paint the chat messages starting with the lowest at the bottom */ - for (uint y = 13; count-- != 0; y += 13) { - DoDrawString(_chatmsg_list[count].message, _chatmsg_box.x + 3, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].color); - } - - /* Make sure the data is updated next flush */ - _video_driver->MakeDirty(x, y, width, height); - - _chatmessage_visible = true; - _chatmessage_dirty = false; -} +static uint16 _num_text_effects = INIT_NUM_TEXT_EFFECTS; /* Text Effects */ /** diff --git a/src/texteff.hpp b/src/texteff.hpp index 36692a49a..7b59def8c 100644 --- a/src/texteff.hpp +++ b/src/texteff.hpp @@ -26,11 +26,6 @@ void DrawTextEffects(DrawPixelInfo *dpi); void UpdateTextEffect(TextEffectID effect_id, StringID msg); void RemoveTextEffect(TextEffectID effect_id); -void InitChatMessage(); -void DrawChatMessage(); -void CDECL AddChatMessage(uint16 color, uint8 duration, const char *message, ...); -void UndrawChatMessage(); - /* misc_gui.cpp */ TextEffectID ShowFillingPercent(int x, int y, int z, uint8 percent, StringID color); void UpdateFillingPercent(TextEffectID te_id, uint8 percent, StringID color); diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm index cee4e8fe2..a196ca4b0 100644 --- a/src/video/cocoa/event.mm +++ b/src/video/cocoa/event.mm @@ -694,7 +694,7 @@ void QZ_GameLoop() st += GetTick() - st0; #endif _screen.dst_ptr = _cocoa_subdriver->GetPixelBuffer(); - DrawChatMessage(); + NetworkDrawChatMessage(); DrawMouseCursor(); _cocoa_subdriver->Draw(); } diff --git a/src/video/sdl_v.cpp b/src/video/sdl_v.cpp index d6e274fc0..4ccf90abe 100644 --- a/src/video/sdl_v.cpp +++ b/src/video/sdl_v.cpp @@ -518,7 +518,7 @@ void VideoDriver_SDL::MainLoop() } else { SDL_CALL SDL_Delay(1); _screen.dst_ptr = _sdl_screen->pixels; - DrawChatMessage(); + NetworkDrawChatMessage(); DrawMouseCursor(); DrawSurfaceToScreen(); } diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index 8dcab88e9..9f5c32045 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -890,7 +890,7 @@ void VideoDriver_Win32::MainLoop() GdiFlush(); #endif _screen.dst_ptr = _wnd.buffer_bits; - DrawChatMessage(); + NetworkDrawChatMessage(); DrawMouseCursor(); } } diff --git a/src/window.cpp b/src/window.cpp index c33b48f06..3686b0e14 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -23,6 +23,7 @@ #include "cheat_func.h" #include "window_func.h" #include "tilehighlight_func.h" +#include "network/network.h" #include "table/sprites.h" @@ -1984,7 +1985,7 @@ void UpdateWindows() FOR_ALL_WINDOWS(wz) { if ((*wz)->viewport != NULL) UpdateViewportPosition(*wz); } - DrawChatMessage(); + NetworkDrawChatMessage(); /* Redraw mouse cursor in case it was hidden */ DrawMouseCursor(); } |