summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/date.cpp13
-rw-r--r--src/gfx.cpp11
-rw-r--r--src/misc.cpp5
-rw-r--r--src/network/network.cpp2
-rw-r--r--src/network/network.h2
-rw-r--r--src/network/network_chat_gui.cpp487
-rw-r--r--src/network/network_func.h5
-rw-r--r--src/network/network_gui.cpp248
-rw-r--r--src/texteff.cpp226
-rw-r--r--src/texteff.hpp5
-rw-r--r--src/video/cocoa/event.mm2
-rw-r--r--src/video/sdl_v.cpp2
-rw-r--r--src/video/win32_v.cpp2
-rw-r--r--src/window.cpp3
14 files changed, 522 insertions, 491 deletions
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();
}