Chat console, including a number of rebases and modifications.
authorKahrl <kahrl@gmx.net>
Sat, 3 Dec 2011 08:01:14 +0000 (09:01 +0100)
committerPerttu Ahola <celeron55@gmail.com>
Sat, 10 Mar 2012 18:11:10 +0000 (20:11 +0200)
Defaults modified from original: alpha=200, key=F10

17 files changed:
README.txt
minetest.conf.example
share/client/textures/fontdejavusansmono.png [new file with mode: 0644]
src/CMakeLists.txt
src/chat.cpp [new file with mode: 0644]
src/chat.h [new file with mode: 0644]
src/client.cpp
src/client.h
src/defaultsettings.cpp
src/game.cpp
src/game.h
src/guiChatConsole.cpp [new file with mode: 0644]
src/guiChatConsole.h [new file with mode: 0644]
src/guiKeyChangeMenu.cpp
src/guiKeyChangeMenu.h
src/main.cpp
src/utility.h

index c10fa753771da951e015d747b6b3399113d6c051..9002ab64314e2a2696b4b82d60c9b633f22a9e6a 100644 (file)
@@ -277,3 +277,21 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 IN THE SOFTWARE.
 
 
+Fonts
+---------------
+
+DejaVu Sans Mono:
+
+  Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+  Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+  Bitstream Vera Fonts Copyright:
+
+  Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+  a trademark of Bitstream, Inc.
+
+  Arev Fonts Copyright:
+
+  Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+
index fbc7249d215eb740c9b725260b1f7751b788f216..8deee7bd090c1eaff4b34843f4e86425a01bf274 100644 (file)
@@ -42,6 +42,8 @@
 # Go down ladder / go down in fly mode / go fast in fast mode
 #keymap_special1 = KEY_KEY_E
 #keymap_chat = KEY_KEY_T
+#keymap_cmd = /
+#keyman_console = KEY_F10
 #keymap_rangeselect = KEY_KEY_R
 #keymap_freemove = KEY_KEY_K
 #keymap_fastmove = KEY_KEY_J
 #screenshot_path = .
 # Amount of view bobbing (0 = no view bobbing, 1.0 = normal, 2.0 = double)
 #view_bobbing_amount = 1.0
+# In-game chat console background color (R,G,B)
+#console_color = (0,0,0)
+# In-game chat console background alpha (opaqueness, between 0 and 255)
+#console_alpha = 200
 
 #
 # Server stuff
diff --git a/share/client/textures/fontdejavusansmono.png b/share/client/textures/fontdejavusansmono.png
new file mode 100644 (file)
index 0000000..416a74f
Binary files /dev/null and b/share/client/textures/fontdejavusansmono.png differ
index e11c0f345b297504dfd5ae875f5a35e89a0590ca..e1cfcfa09d8956f2b4469961eb9ffa605bb500a3 100644 (file)
@@ -167,6 +167,7 @@ set(minetest_SRCS
        camera.cpp
        clouds.cpp
        clientobject.cpp
+       chat.cpp
        guiMainMenu.cpp
        guiKeyChangeMenu.cpp
        guiMessageMenu.cpp
@@ -175,6 +176,7 @@ set(minetest_SRCS
        guiPauseMenu.cpp
        guiPasswordChange.cpp
        guiDeathScreen.cpp
+       guiChatConsole.cpp
        client.cpp
        tile.cpp
        game.cpp
diff --git a/src/chat.cpp b/src/chat.cpp
new file mode 100644 (file)
index 0000000..2f5f8a4
--- /dev/null
@@ -0,0 +1,768 @@
+/*
+Minetest-c55
+Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "chat.h"
+#include "debug.h"
+#include "utility.h"
+#include <cassert>
+#include <cctype>
+#include <sstream>
+
+ChatBuffer::ChatBuffer(u32 scrollback):
+       m_scrollback(scrollback),
+       m_unformatted(),
+       m_cols(0),
+       m_rows(0),
+       m_scroll(0),
+       m_formatted(),
+       m_empty_formatted_line()
+{
+       if (m_scrollback == 0)
+               m_scrollback = 1;
+       m_empty_formatted_line.first = true;
+}
+
+ChatBuffer::~ChatBuffer()
+{
+}
+
+void ChatBuffer::addLine(std::wstring name, std::wstring text)
+{
+       ChatLine line(name, text);
+       m_unformatted.push_back(line);
+
+       if (m_rows > 0)
+       {
+               // m_formatted is valid and must be kept valid
+               bool scrolled_at_bottom = (m_scroll == getBottomScrollPos());
+               u32 num_added = formatChatLine(line, m_cols, m_formatted);
+               if (scrolled_at_bottom)
+                       m_scroll += num_added;
+       }
+
+       // Limit number of lines by m_scrollback
+       if (m_unformatted.size() > m_scrollback)
+       {
+               deleteOldest(m_unformatted.size() - m_scrollback);
+       }
+}
+
+void ChatBuffer::clear()
+{
+       m_unformatted.clear();
+       m_formatted.clear();
+       m_scroll = 0;
+}
+
+u32 ChatBuffer::getLineCount() const
+{
+       return m_unformatted.size();
+}
+
+u32 ChatBuffer::getScrollback() const
+{
+       return m_scrollback;
+}
+
+const ChatLine& ChatBuffer::getLine(u32 index) const
+{
+       assert(index < getLineCount());
+       return m_unformatted[index];
+}
+
+void ChatBuffer::step(f32 dtime)
+{
+       for (u32 i = 0; i < m_unformatted.size(); ++i)
+       {
+               m_unformatted[i].age += dtime;
+       }
+}
+
+void ChatBuffer::deleteOldest(u32 count)
+{
+       u32 del_unformatted = 0;
+       u32 del_formatted = 0;
+
+       while (count > 0 && del_unformatted < m_unformatted.size())
+       {
+               ++del_unformatted;
+
+               // keep m_formatted in sync
+               if (del_formatted < m_formatted.size())
+               {
+                       assert(m_formatted[del_formatted].first);
+                       ++del_formatted;
+                       while (del_formatted < m_formatted.size() &&
+                                       !m_formatted[del_formatted].first)
+                               ++del_formatted;
+               }
+
+               --count;
+       }
+
+       m_unformatted.erase(0, del_unformatted);
+       m_formatted.erase(0, del_formatted);
+}
+
+void ChatBuffer::deleteByAge(f32 maxAge)
+{
+       u32 count = 0;
+       while (count < m_unformatted.size() && m_unformatted[count].age > maxAge)
+               ++count;
+       deleteOldest(count);
+}
+
+u32 ChatBuffer::getColumns() const
+{
+       return m_cols;
+}
+
+u32 ChatBuffer::getRows() const
+{
+       return m_rows;
+}
+
+void ChatBuffer::reformat(u32 cols, u32 rows)
+{
+       if (cols == 0 || rows == 0)
+       {
+               // Clear formatted buffer
+               m_cols = 0;
+               m_rows = 0;
+               m_scroll = 0;
+               m_formatted.clear();
+       }
+       else if (cols != m_cols || rows != m_rows)
+       {
+               // TODO: Avoid reformatting ALL lines (even inivisble ones)
+               // each time the console size changes.
+
+               // Find out the scroll position in *unformatted* lines
+               u32 restore_scroll_unformatted = 0;
+               u32 restore_scroll_formatted = 0;
+               bool at_bottom = (m_scroll == getBottomScrollPos());
+               if (!at_bottom)
+               {
+                       for (s32 i = 0; i < m_scroll; ++i)
+                       {
+                               if (m_formatted[i].first)
+                                       ++restore_scroll_unformatted;
+                       }
+               }
+
+               // If number of columns change, reformat everything
+               if (cols != m_cols)
+               {
+                       m_formatted.clear();
+                       for (u32 i = 0; i < m_unformatted.size(); ++i)
+                       {
+                               if (i == restore_scroll_unformatted)
+                                       restore_scroll_formatted = m_formatted.size();
+                               formatChatLine(m_unformatted[i], cols, m_formatted);
+                       }
+               }
+
+               // Update the console size
+               m_cols = cols;
+               m_rows = rows;
+
+               // Restore the scroll position
+               if (at_bottom)
+               {
+                       scrollBottom();
+               }
+               else
+               {
+                       scrollAbsolute(restore_scroll_formatted);
+               }
+       }
+}
+
+const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const
+{
+       s32 index = m_scroll + (s32) row;
+       if (index >= 0 && index < (s32) m_formatted.size())
+               return m_formatted[index];
+       else
+               return m_empty_formatted_line;
+}
+
+void ChatBuffer::scroll(s32 rows)
+{
+       scrollAbsolute(m_scroll + rows);
+}
+
+void ChatBuffer::scrollAbsolute(s32 scroll)
+{
+       s32 top = getTopScrollPos();
+       s32 bottom = getBottomScrollPos();
+
+       m_scroll = scroll;
+       if (m_scroll < top)
+               m_scroll = top;
+       if (m_scroll > bottom)
+               m_scroll = bottom;
+}
+
+void ChatBuffer::scrollBottom()
+{
+       m_scroll = getBottomScrollPos();
+}
+
+void ChatBuffer::scrollTop()
+{
+       m_scroll = getTopScrollPos();
+}
+
+u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
+               core::array<ChatFormattedLine>& destination) const
+{
+       u32 num_added = 0;
+       core::array<ChatFormattedFragment> next_frags;
+       ChatFormattedLine next_line;
+       ChatFormattedFragment temp_frag;
+       u32 out_column = 0;
+       u32 in_pos = 0;
+       u32 hanging_indentation = 0;
+
+       // Format the sender name and produce fragments
+       if (!line.name.empty())
+       {
+               temp_frag.text = L"<";
+               temp_frag.column = 0;
+               //temp_frag.bold = 0;
+               next_frags.push_back(temp_frag);
+               temp_frag.text = line.name;
+               temp_frag.column = 0;
+               //temp_frag.bold = 1;
+               next_frags.push_back(temp_frag);
+               temp_frag.text = L"> ";
+               temp_frag.column = 0;
+               //temp_frag.bold = 0;
+               next_frags.push_back(temp_frag);
+       }
+
+       // Choose an indentation level
+       if (line.name.empty())
+       {
+               // Server messages
+               hanging_indentation = 0;
+       }
+       else if (line.name.size() + 3 <= cols/2)
+       {
+               // Names shorter than about half the console width
+               hanging_indentation = line.name.size() + 3;
+       }
+       else
+       {
+               // Very long names
+               hanging_indentation = 2;
+       }
+
+       next_line.first = true;
+       bool text_processing = false;
+
+       // Produce fragments and layout them into lines
+       while (!next_frags.empty() || in_pos < line.text.size())
+       {
+               // Layout fragments into lines
+               while (!next_frags.empty())
+               {
+                       ChatFormattedFragment& frag = next_frags[0];
+                       if (frag.text.size() <= cols - out_column)
+                       {
+                               // Fragment fits into current line
+                               frag.column = out_column;
+                               next_line.fragments.push_back(frag);
+                               out_column += frag.text.size();
+                               next_frags.erase(0, 1);
+                       }
+                       else
+                       {
+                               // Fragment does not fit into current line
+                               // So split it up
+                               temp_frag.text = frag.text.substr(0, cols - out_column);
+                               temp_frag.column = out_column;
+                               //temp_frag.bold = frag.bold;
+                               next_line.fragments.push_back(temp_frag);
+                               frag.text = frag.text.substr(cols - out_column);
+                               out_column = cols;
+                       }
+                       if (out_column == cols || text_processing)
+                       {
+                               // End the current line
+                               destination.push_back(next_line);
+                               num_added++;
+                               next_line.fragments.clear();
+                               next_line.first = false;
+
+                               out_column = text_processing ? hanging_indentation : 0;
+                       }
+               }
+
+               // Produce fragment
+               if (in_pos < line.text.size())
+               {
+                       u32 remaining_in_input = line.text.size() - in_pos;
+                       u32 remaining_in_output = cols - out_column;
+
+                       // Determine a fragment length <= the minimum of
+                       // remaining_in_{in,out}put. Try to end the fragment
+                       // on a word boundary.
+                       u32 frag_length = 1, space_pos = 0;
+                       while (frag_length < remaining_in_input &&
+                                       frag_length < remaining_in_output)
+                       {
+                               if (isspace(line.text[in_pos + frag_length]))
+                                       space_pos = frag_length;
+                               ++frag_length;
+                       }
+                       if (space_pos != 0 && frag_length < remaining_in_input)
+                               frag_length = space_pos + 1;
+
+                       temp_frag.text = line.text.substr(in_pos, frag_length);
+                       temp_frag.column = 0;
+                       //temp_frag.bold = 0;
+                       next_frags.push_back(temp_frag);
+                       in_pos += frag_length;
+                       text_processing = true;
+               }
+       }
+
+       // End the last line
+       if (num_added == 0 || !next_line.fragments.empty())
+       {
+               destination.push_back(next_line);
+               num_added++;
+       }
+
+       return num_added;
+}
+
+s32 ChatBuffer::getTopScrollPos() const
+{
+       s32 formatted_count = (s32) m_formatted.size();
+       s32 rows = (s32) m_rows;
+       if (rows == 0)
+               return 0;
+       else if (formatted_count <= rows)
+               return formatted_count - rows;
+       else
+               return 0;
+}
+
+s32 ChatBuffer::getBottomScrollPos() const
+{
+       s32 formatted_count = (s32) m_formatted.size();
+       s32 rows = (s32) m_rows;
+       if (rows == 0)
+               return 0;
+       else
+               return formatted_count - rows;
+}
+
+
+
+ChatPrompt::ChatPrompt(std::wstring prompt, u32 history_limit):
+       m_prompt(prompt),
+       m_line(L""),
+       m_history(),
+       m_history_index(0),
+       m_history_limit(history_limit),
+       m_cols(0),
+       m_view(0),
+       m_cursor(0),
+       m_nick_completion_start(0),
+       m_nick_completion_end(0)
+{
+}
+
+ChatPrompt::~ChatPrompt()
+{
+}
+
+void ChatPrompt::input(wchar_t ch)
+{
+       m_line.insert(m_cursor, 1, ch);
+       m_cursor++;
+       clampView();
+       m_nick_completion_start = 0;
+       m_nick_completion_end = 0;
+}
+
+std::wstring ChatPrompt::submit()
+{
+       std::wstring line = m_line;
+       m_line.clear();
+       if (!line.empty())
+               m_history.push_back(line);
+       if (m_history.size() > m_history_limit)
+               m_history.erase(0);
+       m_history_index = m_history.size();
+       m_view = 0;
+       m_cursor = 0;
+       m_nick_completion_start = 0;
+       m_nick_completion_end = 0;
+       return line;
+}
+
+void ChatPrompt::clear()
+{
+       m_line.clear();
+       m_view = 0;
+       m_cursor = 0;
+       m_nick_completion_start = 0;
+       m_nick_completion_end = 0;
+}
+
+void ChatPrompt::replace(std::wstring line)
+{
+       m_line =  line;
+       m_view = m_cursor = line.size();
+       clampView();
+       m_nick_completion_start = 0;
+       m_nick_completion_end = 0;
+}
+
+void ChatPrompt::historyPrev()
+{
+       if (m_history_index != 0)
+       {
+               --m_history_index;
+               replace(m_history[m_history_index]);
+       }
+}
+
+void ChatPrompt::historyNext()
+{
+       if (m_history_index + 1 >= m_history.size())
+       {
+               m_history_index = m_history.size();
+               replace(L"");
+       }
+       else
+       {
+               ++m_history_index;
+               replace(m_history[m_history_index]);
+       }
+}
+
+void ChatPrompt::nickCompletion(const core::list<std::wstring>& names, bool backwards)
+{
+       // Two cases:
+       // (a) m_nick_completion_start == m_nick_completion_end == 0
+       //     Then no previous nick completion is active.
+       //     Get the word around the cursor and replace with any nick
+       //     that has that word as a prefix.
+       // (b) else, continue a previous nick completion.
+       //     m_nick_completion_start..m_nick_completion_end are the
+       //     interval where the originally used prefix was. Cycle
+       //     through the list of completions of that prefix.
+       u32 prefix_start = m_nick_completion_start;
+       u32 prefix_end = m_nick_completion_end;
+       bool initial = (prefix_end == 0);
+       if (initial)
+       {
+               // no previous nick completion is active
+               prefix_start = prefix_end = m_cursor;
+               while (prefix_start > 0 && !isspace(m_line[prefix_start-1]))
+                       --prefix_start;
+               while (prefix_end < m_line.size() && !isspace(m_line[prefix_end]))
+                       ++prefix_end;
+               if (prefix_start == prefix_end)
+                       return;
+       }
+       std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start);
+
+       // find all names that start with the selected prefix
+       core::array<std::wstring> completions;
+       for (core::list<std::wstring>::ConstIterator
+                       i = names.begin();
+                       i != names.end(); i++)
+       {
+               if (str_starts_with(*i, prefix, true))
+               {
+                       std::wstring completion = *i;
+                       if (prefix_start == 0)
+                               completion += L":";
+                       completions.push_back(completion);
+               }
+       }
+       if (completions.empty())
+               return;
+
+       // find a replacement string and the word that will be replaced
+       u32 word_end = prefix_end;
+       u32 replacement_index = 0;
+       if (!initial)
+       {
+               while (word_end < m_line.size() && !isspace(m_line[word_end]))
+                       ++word_end;
+               std::wstring word = m_line.substr(prefix_start, word_end - prefix_start);
+
+               // cycle through completions
+               for (u32 i = 0; i < completions.size(); ++i)
+               {
+                       if (str_equal(word, completions[i], true))
+                       {
+                               if (backwards)
+                                       replacement_index = i + completions.size() - 1;
+                               else
+                                       replacement_index = i + 1;
+                               replacement_index %= completions.size();
+                               break;
+                       }
+               }
+       }
+       std::wstring replacement = completions[replacement_index] + L" ";
+       if (word_end < m_line.size() && isspace(word_end))
+               ++word_end;
+
+       // replace existing word with replacement word,
+       // place the cursor at the end and record the completion prefix
+       m_line.replace(prefix_start, word_end - prefix_start, replacement);
+       m_cursor = prefix_start + replacement.size();
+       clampView();
+       m_nick_completion_start = prefix_start;
+       m_nick_completion_end = prefix_end;
+}
+
+void ChatPrompt::reformat(u32 cols)
+{
+       if (cols <= m_prompt.size())
+       {
+               m_cols = 0;
+               m_view = m_cursor;
+       }
+       else
+       {
+               s32 length = m_line.size();
+               bool was_at_end = (m_view + m_cols >= length + 1);
+               m_cols = cols - m_prompt.size();
+               if (was_at_end)
+                       m_view = length;
+               clampView();
+       }
+}
+
+std::wstring ChatPrompt::getVisiblePortion() const
+{
+       return m_prompt + m_line.substr(m_view, m_cols);
+}
+
+s32 ChatPrompt::getVisibleCursorPosition() const
+{
+       return m_cursor - m_view + m_prompt.size();
+}
+
+void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope)
+{
+       s32 old_cursor = m_cursor;
+       s32 new_cursor = m_cursor;
+
+       s32 length = m_line.size();
+       s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
+
+       if (scope == CURSOROP_SCOPE_CHARACTER)
+       {
+               new_cursor += increment;
+       }
+       else if (scope == CURSOROP_SCOPE_WORD)
+       {
+               if (increment > 0)
+               {
+                       // skip one word to the right
+                       while (new_cursor < length && isspace(m_line[new_cursor]))
+                               new_cursor++;
+                       while (new_cursor < length && !isspace(m_line[new_cursor]))
+                               new_cursor++;
+                       while (new_cursor < length && isspace(m_line[new_cursor]))
+                               new_cursor++;
+               }
+               else
+               {
+                       // skip one word to the left
+                       while (new_cursor >= 1 && isspace(m_line[new_cursor - 1]))
+                               new_cursor--;
+                       while (new_cursor >= 1 && !isspace(m_line[new_cursor - 1]))
+                               new_cursor--;
+               }
+       }
+       else if (scope == CURSOROP_SCOPE_LINE)
+       {
+               new_cursor += increment * length;
+       }
+
+       new_cursor = MYMAX(MYMIN(new_cursor, length), 0);
+
+       if (op == CURSOROP_MOVE)
+       {
+               m_cursor = new_cursor;
+       }
+       else if (op == CURSOROP_DELETE)
+       {
+               if (new_cursor < old_cursor)
+               {
+                       m_line.erase(new_cursor, old_cursor - new_cursor);
+                       m_cursor = new_cursor;
+               }
+               else if (new_cursor > old_cursor)
+               {
+                       m_line.erase(old_cursor, new_cursor - old_cursor);
+                       m_cursor = old_cursor;
+               }
+       }
+
+       clampView();
+
+       m_nick_completion_start = 0;
+       m_nick_completion_end = 0;
+}
+
+void ChatPrompt::clampView()
+{
+       s32 length = m_line.size();
+       if (length + 1 <= m_cols)
+       {
+               m_view = 0;
+       }
+       else
+       {
+               m_view = MYMIN(m_view, length + 1 - m_cols);
+               m_view = MYMIN(m_view, m_cursor);
+               m_view = MYMAX(m_view, m_cursor - m_cols + 1);
+               m_view = MYMAX(m_view, 0);
+       }
+}
+
+
+
+ChatBackend::ChatBackend():
+       m_console_buffer(500),
+       m_recent_buffer(6),
+       m_prompt(L"]", 500)
+{
+}
+
+ChatBackend::~ChatBackend()
+{
+}
+
+void ChatBackend::addMessage(std::wstring name, std::wstring text)
+{
+       // Note: A message may consist of multiple lines, for example the MOTD.
+       WStrfnd fnd(text);
+       while (!fnd.atend())
+       {
+               std::wstring line = fnd.next(L"\n");
+               m_console_buffer.addLine(name, line);
+               m_recent_buffer.addLine(name, line);
+       }
+}
+
+void ChatBackend::addUnparsedMessage(std::wstring message)
+{
+       // TODO: Remove the need to parse chat messages client-side, by sending
+       // separate name and text fields in TOCLIENT_CHAT_MESSAGE.
+
+       if (message.size() >= 2 && message[0] == L'<')
+       {
+               std::size_t closing = message.find_first_of(L'>', 1);
+               if (closing != std::wstring::npos &&
+                               closing + 2 <= message.size() &&
+                               message[closing+1] == L' ')
+               {
+                       std::wstring name = message.substr(1, closing - 1);
+                       std::wstring text = message.substr(closing + 2);
+                       addMessage(name, text);
+                       return;
+               }
+       }
+
+       // Unable to parse, probably a server message.
+       addMessage(L"", message);
+}
+
+ChatBuffer& ChatBackend::getConsoleBuffer()
+{
+       return m_console_buffer;
+}
+
+ChatBuffer& ChatBackend::getRecentBuffer()
+{
+       return m_recent_buffer;
+}
+
+std::wstring ChatBackend::getRecentChat()
+{
+       std::wostringstream stream;
+       for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i)
+       {
+               const ChatLine& line = m_recent_buffer.getLine(i);
+               if (i != 0)
+                       stream << L"\n";
+               if (!line.name.empty())
+                       stream << L"<" << line.name << L"> ";
+               stream << line.text;
+       }
+       return stream.str();
+}
+
+ChatPrompt& ChatBackend::getPrompt()
+{
+       return m_prompt;
+}
+
+void ChatBackend::reformat(u32 cols, u32 rows)
+{
+       m_console_buffer.reformat(cols, rows);
+
+       // no need to reformat m_recent_buffer, its formatted lines
+       // are not used
+
+       m_prompt.reformat(cols);
+}
+
+void ChatBackend::clearRecentChat()
+{
+       m_recent_buffer.clear();
+}
+
+void ChatBackend::step(float dtime)
+{
+       m_recent_buffer.step(dtime);
+       m_recent_buffer.deleteByAge(60.0);
+
+       // no need to age messages in anything but m_recent_buffer
+}
+
+void ChatBackend::scroll(s32 rows)
+{
+       m_console_buffer.scroll(rows);
+}
+
+void ChatBackend::scrollPageDown()
+{
+       m_console_buffer.scroll(m_console_buffer.getRows());
+}
+
+void ChatBackend::scrollPageUp()
+{
+       m_console_buffer.scroll(-m_console_buffer.getRows());
+}
diff --git a/src/chat.h b/src/chat.h
new file mode 100644 (file)
index 0000000..0e636ea
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+Minetest-c55
+Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef CHAT_HEADER
+#define CHAT_HEADER
+
+#include "common_irrlicht.h"
+#include <string>
+
+// Chat console related classes, only used by the client
+
+struct ChatLine
+{
+       // age in seconds
+       f32 age;
+       // name of sending player, or empty if sent by server
+       std::wstring name;
+       // message text
+       std::wstring text;
+
+       ChatLine(std::wstring a_name, std::wstring a_text):
+               age(0.0),
+               name(a_name),
+               text(a_text)
+       {
+       }
+};
+
+struct ChatFormattedFragment
+{
+       // text string
+       std::wstring text;
+       // starting column
+       u32 column;
+       // formatting
+       //u8 bold:1;
+};
+
+struct ChatFormattedLine
+{
+       // Array of text fragments
+       core::array<ChatFormattedFragment> fragments;
+       // true if first line of one formatted ChatLine
+       bool first;
+};
+
+class ChatBuffer
+{
+public:
+       ChatBuffer(u32 scrollback);
+       ~ChatBuffer();
+
+       // Append chat line
+       // Removes oldest chat line if scrollback size is reached
+       void addLine(std::wstring name, std::wstring text);
+
+       // Remove all chat lines
+       void clear();
+
+       // Get number of lines currently in buffer.
+       u32 getLineCount() const;
+       // Get scrollback size, maximum number of lines in buffer.
+       u32 getScrollback() const;
+       // Get reference to i-th chat line.
+       const ChatLine& getLine(u32 index) const;
+
+       // Increase each chat line's age by dtime.
+       void step(f32 dtime);
+       // Delete oldest N chat lines.
+       void deleteOldest(u32 count);
+       // Delete lines older than maxAge.
+       void deleteByAge(f32 maxAge);
+
+       // Get number of columns, 0 if reformat has not been called yet.
+       u32 getColumns() const;
+       // Get number of rows, 0 if reformat has not been called yet.
+       u32 getRows() const;
+       // Update console size and reformat all formatted lines.
+       void reformat(u32 cols, u32 rows);
+       // Get formatted line for a given row (0 is top of screen).
+       // Only valid after reformat has been called at least once
+       const ChatFormattedLine& getFormattedLine(u32 row) const;
+       // Scrolling in formatted buffer (relative)
+       // positive rows == scroll up, negative rows == scroll down
+       void scroll(s32 rows);
+       // Scrolling in formatted buffer (absolute)
+       void scrollAbsolute(s32 scroll);
+       // Scroll to bottom of buffer (newest)
+       void scrollBottom();
+       // Scroll to top of buffer (oldest)
+       void scrollTop();
+
+       // Format a chat line for the given number of columns.
+       // Appends the formatted lines to the destination array and
+       // returns the number of formatted lines.
+       u32 formatChatLine(const ChatLine& line, u32 cols,
+                       core::array<ChatFormattedLine>& destination) const;
+
+protected:
+       s32 getTopScrollPos() const;
+       s32 getBottomScrollPos() const;
+
+private:
+       // Scrollback size
+       u32 m_scrollback;
+       // Array of unformatted chat lines
+       core::array<ChatLine> m_unformatted;
+       
+       // Number of character columns in console
+       u32 m_cols;
+       // Number of character rows in console
+       u32 m_rows;
+       // Scroll position (console's top line index into m_formatted)
+       s32 m_scroll;
+       // Array of formatted lines
+       core::array<ChatFormattedLine> m_formatted;
+       // Empty formatted line, for error returns
+       ChatFormattedLine m_empty_formatted_line;
+};
+
+class ChatPrompt
+{
+public:
+       ChatPrompt(std::wstring prompt, u32 history_limit);
+       ~ChatPrompt();
+
+       // Input character
+       void input(wchar_t ch);
+
+       // Submit, clear and return current line
+       std::wstring submit();
+
+       // Clear the current line
+       void clear();
+
+       // Replace the current line with the given text
+       void replace(std::wstring line);
+
+       // Select previous command from history
+       void historyPrev();
+       // Select next command from history
+       void historyNext();
+
+       // Nick completion
+       void nickCompletion(const core::list<std::wstring>& names, bool backwards);
+
+       // Update console size and reformat the visible portion of the prompt
+       void reformat(u32 cols);
+       // Get visible portion of the prompt.
+       std::wstring getVisiblePortion() const;
+       // Get cursor position (relative to visible portion). -1 if invalid
+       s32 getVisibleCursorPosition() const;
+
+       // Cursor operations
+       enum CursorOp {
+               CURSOROP_MOVE,
+               CURSOROP_DELETE
+       };
+
+       // Cursor operation direction
+       enum CursorOpDir {
+               CURSOROP_DIR_LEFT,
+               CURSOROP_DIR_RIGHT
+       };
+
+       // Cursor operation scope
+       enum CursorOpScope {
+               CURSOROP_SCOPE_CHARACTER,
+               CURSOROP_SCOPE_WORD,
+               CURSOROP_SCOPE_LINE
+       };
+
+       // Cursor operation
+       // op specifies whether it's a move or delete operation
+       // dir specifies whether the operation goes left or right
+       // scope specifies how far the operation will reach (char/word/line)
+       // Examples:
+       //   cursorOperation(CURSOROP_MOVE, CURSOROP_DIR_RIGHT, CURSOROP_SCOPE_LINE)
+       //     moves the cursor to the end of the line.
+       //   cursorOperation(CURSOROP_DELETE, CURSOROP_DIR_LEFT, CURSOROP_SCOPE_WORD)
+       //     deletes the word to the left of the cursor.
+       void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);
+
+protected:
+       // set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols
+       // if line can be fully shown, set m_view to zero
+       // else, also ensure m_view <= m_line.size() + 1 - m_cols
+       void clampView();
+
+private:
+       // Prompt prefix
+       std::wstring m_prompt;
+       // Currently edited line
+       std::wstring m_line;
+       // History buffer
+       core::array<std::wstring> m_history;
+       // History index (0 <= m_history_index <= m_history.size()) 
+       u32 m_history_index;
+       // Maximum number of history entries
+       u32 m_history_limit;
+
+       // Number of columns excluding columns reserved for the prompt
+       s32 m_cols;
+       // Start of visible portion (index into m_line)
+       s32 m_view;
+       // Cursor (index into m_line)
+       s32 m_cursor;
+
+       // Last nick completion start (index into m_line)
+       s32 m_nick_completion_start;
+       // Last nick completion start (index into m_line)
+       s32 m_nick_completion_end;
+};
+
+class ChatBackend
+{
+public:
+       ChatBackend();
+       ~ChatBackend();
+
+       // Add chat message
+       void addMessage(std::wstring name, std::wstring text);
+       // Parse and add unparsed chat message
+       void addUnparsedMessage(std::wstring line);
+
+       // Get the console buffer
+       ChatBuffer& getConsoleBuffer();
+       // Get the recent messages buffer
+       ChatBuffer& getRecentBuffer();
+       // Concatenate all recent messages
+       std::wstring getRecentChat();
+       // Get the console prompt
+       ChatPrompt& getPrompt();
+
+       // Reformat all buffers
+       void reformat(u32 cols, u32 rows);
+
+       // Clear all recent messages
+       void clearRecentChat();
+
+       // Age recent messages
+       void step(float dtime);
+
+       // Scrolling
+       void scroll(s32 rows);
+       void scrollPageDown();
+       void scrollPageUp();
+
+private:
+       ChatBuffer m_console_buffer;
+       ChatBuffer m_recent_buffer;
+       ChatPrompt m_prompt;
+};
+
+#endif
+
index 72cd28b18f9a14d82e09a5ac3dbe43ae460dd08e..14f93a1a18145524fd8c463a91af9f81aba89864 100644 (file)
@@ -2036,7 +2036,21 @@ void Client::printDebugInfo(std::ostream &os)
                //<<", m_opt_not_found_history.size()="<<m_opt_not_found_history.size()
                <<std::endl;*/
 }
-       
+
+core::list<std::wstring> Client::getConnectedPlayerNames()
+{
+       core::list<Player*> players = m_env.getPlayers(true);
+       core::list<std::wstring> playerNames;
+       for(core::list<Player*>::Iterator
+                       i = players.begin();
+                       i != players.end(); i++)
+       {
+               Player *player = *i;
+               playerNames.push_back(narrow_to_wide(player->getName()));
+       }
+       return playerNames;
+}
+
 u32 Client::getDayNightRatio()
 {
        //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
@@ -2084,6 +2098,39 @@ void Client::clearTempMod(v3s16 p)
        }
 }
 
+bool Client::getChatMessage(std::wstring &message)
+{
+       if(m_chat_queue.size() == 0)
+               return false;
+       message = m_chat_queue.pop_front();
+       return true;
+}
+
+void Client::typeChatMessage(const std::wstring &message)
+{
+       // Discard empty line
+       if(message == L"")
+               return;
+
+       // Send to others
+       sendChatMessage(message);
+
+       // Show locally
+       if (message[0] == L'/')
+       {
+               m_chat_queue.push_back(
+                               (std::wstring)L"issued command: "+message);
+       }
+       else
+       {
+               LocalPlayer *player = m_env.getLocalPlayer();
+               assert(player != NULL);
+               std::wstring name = narrow_to_wide(player->getName());
+               m_chat_queue.push_back(
+                               (std::wstring)L"<"+name+L"> "+message);
+       }
+}
+
 void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server)
 {
        /*infostream<<"Client::addUpdateMeshTask(): "
index efdf315f7d78c73bb2f2765c5b0fa9cc9a534493..4b16b717caf7e8ce26030c4fb30b05b9daec71da 100644 (file)
@@ -258,6 +258,8 @@ public:
        // Prints a line or two of info
        void printDebugInfo(std::ostream &os);
 
+       core::list<std::wstring> getConnectedPlayerNames();
+
        u32 getDayNightRatio();
 
        u16 getHP();
@@ -274,29 +276,8 @@ public:
                }
        }
 
-       bool getChatMessage(std::wstring &message)
-       {
-               if(m_chat_queue.size() == 0)
-                       return false;
-               message = m_chat_queue.pop_front();
-               return true;
-       }
-
-       void addChatMessage(const std::wstring &message)
-       {
-               if (message[0] == L'/') {
-                       m_chat_queue.push_back(
-                               (std::wstring)L"issued command: "+message);
-                       return;
-               }
-
-               //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
-               LocalPlayer *player = m_env.getLocalPlayer();
-               assert(player != NULL);
-               std::wstring name = narrow_to_wide(player->getName());
-               m_chat_queue.push_back(
-                               (std::wstring)L"<"+name+L"> "+message);
-       }
+       bool getChatMessage(std::wstring &message);
+       void typeChatMessage(const std::wstring& message);
 
        u64 getMapSeed(){ return m_map_seed; }
 
index 6c611d6721770ed78a16613c0cb5d9530d469e52..23199eef482e5bdc63e52994b85e053c923c7bcb 100644 (file)
@@ -40,6 +40,7 @@ void set_default_settings(Settings *settings)
        settings->setDefault("keymap_special1", "KEY_KEY_E");
        settings->setDefault("keymap_chat", "KEY_KEY_T");
        settings->setDefault("keymap_cmd", "/");
+       settings->setDefault("keymap_console", "KEY_F10");
        settings->setDefault("keymap_rangeselect", "KEY_KEY_R");
        settings->setDefault("keymap_freemove", "KEY_KEY_K");
        settings->setDefault("keymap_fastmove", "KEY_KEY_J");
@@ -91,7 +92,8 @@ void set_default_settings(Settings *settings)
        settings->setDefault("view_bobbing_amount", "1.0");
        settings->setDefault("enable_3d_clouds", "false");
        settings->setDefault("opaque_water", "false");
-
+       settings->setDefault("console_color", "(0,0,0)");
+       settings->setDefault("console_alpha", "200");
        // Server stuff
        // "map-dir" doesn't exist by default.
        settings->setDefault("motd", "");
index 96f834341c4929bf9d65ebb510291e3d7f846107..616d05865bef1c557d5d5a21c4e9ed9efaabadcb 100644 (file)
@@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiTextInputMenu.h"
 #include "guiDeathScreen.h"
 #include "tool.h"
+#include "guiChatConsole.h"
 #include "config.h"
 #include "clouds.h"
 #include "camera.h"
@@ -62,22 +63,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define FIELD_OF_VIEW_TEST 0
 
 
-// Chat data
-struct ChatLine
-{
-       ChatLine():
-               age(0.0)
-       {
-       }
-       ChatLine(const std::wstring &a_text):
-               age(0.0),
-               text(a_text)
-       {
-       }
-       float age;
-       std::wstring text;
-};
-
 /*
        Text input system
 */
@@ -90,14 +75,7 @@ struct TextDestChat : public TextDest
        }
        void gotText(std::wstring text)
        {
-               // Discard empty line
-               if(text == L"")
-                       return;
-
-               // Send to others
-               m_client->sendChatMessage(text);
-               // Show locally
-               m_client->addChatMessage(text);
+               m_client->typeChatMessage(text);
        }
 
        Client *m_client;
@@ -676,7 +654,8 @@ void the_game(
        std::string address,
        u16 port,
        std::wstring &error_message,
-    std::string configpath
+       std::string configpath,
+       ChatBackend &chat_backend
 )
 {
        video::IVideoDriver* driver = device->getVideoDriver();
@@ -978,8 +957,10 @@ void the_game(
                        core::rect<s32>(0,0,0,0),
                        //false, false); // Disable word wrap as of now
                        false, true);
-       //guitext_chat->setBackgroundColor(video::SColor(96,0,0,0));
-       core::list<ChatLine> chat_lines;
+       // Remove stale "recent" chat messages from previous connections
+       chat_backend.clearRecentChat();
+       // Chat backend and console
+       GUIChatConsole *gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, &chat_backend, &client);
        
        // Profiler text (size is updated when text is updated)
        gui::IGUIStaticText *guitext_profiler = guienv->addStaticText(
@@ -1299,7 +1280,9 @@ void the_game(
                */
                
                // Reset input if window not active or some menu is active
-               if(device->isWindowActive() == false || noMenuActive() == false)
+               if(device->isWindowActive() == false
+                               || noMenuActive() == false
+                               || guienv->hasFocus(gui_chat_console))
                {
                        input->clear();
                }
@@ -1375,6 +1358,15 @@ void the_game(
                                        &g_menumgr, dest,
                                        L"/"))->drop();
                }
+               else if(input->wasKeyDown(getKeySetting("keymap_console")))
+               {
+                       if (!gui_chat_console->isOpenInhibited())
+                       {
+                               // Open up to over half of the screen
+                               gui_chat_console->openConsole(0.6);
+                               guienv->setFocus(gui_chat_console);
+                       }
+               }
                else if(input->wasKeyDown(getKeySetting("keymap_freemove")))
                {
                        if(g_settings->getBool("free_move"))
@@ -1655,23 +1647,6 @@ void the_game(
                /*
                        Player speed control
                */
-               
-               if(!noMenuActive() || !device->isWindowActive())
-               {
-                       PlayerControl control(
-                               false,
-                               false,
-                               false,
-                               false,
-                               false,
-                               false,
-                               false,
-                               camera_pitch,
-                               camera_yaw
-                       );
-                       client.setPlayerControl(control);
-               }
-               else
                {
                        /*bool a_up,
                        bool a_down,
@@ -1758,6 +1733,8 @@ void the_game(
                                                                &g_menumgr, respawner);
                                        menu->drop();
                                        
+                                       chat_backend.addMessage(L"", L"You died.");
+
                                        /* Handle visualization */
 
                                        damage_flash_timer = 0;
@@ -2357,83 +2334,38 @@ void the_game(
                        // Get new messages from error log buffer
                        while(!chat_log_error_buf.empty())
                        {
-                               chat_lines.push_back(ChatLine(narrow_to_wide(
-                                               chat_log_error_buf.get())));
+                               chat_backend.addMessage(L"", narrow_to_wide(
+                                               chat_log_error_buf.get()));
                        }
                        // Get new messages from client
                        std::wstring message;
                        while(client.getChatMessage(message))
                        {
-                               chat_lines.push_back(ChatLine(message));
-                               /*if(chat_lines.size() > 6)
-                               {
-                                       core::list<ChatLine>::Iterator
-                                                       i = chat_lines.begin();
-                                       chat_lines.erase(i);
-                               }*/
+                               chat_backend.addUnparsedMessage(message);
                        }
-                       // Append them to form the whole static text and throw
-                       // it to the gui element
-                       std::wstring whole;
-                       // This will correspond to the line number counted from
-                       // top to bottom, from size-1 to 0
-                       s16 line_number = chat_lines.size();
-                       // Count of messages to be removed from the top
-                       u16 to_be_removed_count = 0;
-                       for(core::list<ChatLine>::Iterator
-                                       i = chat_lines.begin();
-                                       i != chat_lines.end(); i++)
-                       {
-                               // After this, line number is valid for this loop
-                               line_number--;
-                               // Increment age
-                               (*i).age += dtime;
-                               /*
-                                       This results in a maximum age of 60*6 to the
-                                       lowermost line and a maximum of 6 lines
-                               */
-                               float allowed_age = (6-line_number) * 60.0;
-
-                               if((*i).age > allowed_age)
-                               {
-                                       to_be_removed_count++;
-                                       continue;
-                               }
-                               whole += (*i).text + L'\n';
-                       }
-                       for(u16 i=0; i<to_be_removed_count; i++)
-                       {
-                               core::list<ChatLine>::Iterator
-                                               it = chat_lines.begin();
-                               chat_lines.erase(it);
-                       }
-                       guitext_chat->setText(whole.c_str());
-
-                       // Update gui element size and position
+                       // Remove old messages
+                       chat_backend.step(dtime);
 
-                       /*core::rect<s32> rect(
-                                       10,
-                                       screensize.Y - guitext_chat_pad_bottom
-                                                       - text_height*chat_lines.size(),
-                                       screensize.X - 10,
-                                       screensize.Y - guitext_chat_pad_bottom
-                       );*/
+                       // Display all messages in a static text element
+                       u32 recent_chat_count = chat_backend.getRecentBuffer().getLineCount();
+                       std::wstring recent_chat = chat_backend.getRecentChat();
+                       guitext_chat->setText(recent_chat.c_str());
 
+                       // Update gui element size and position
                        s32 chat_y = 5+(text_height+5);
                        if(show_debug)
                                chat_y += (text_height+5);
                        core::rect<s32> rect(
-                                       10,
-                                       chat_y,
-                                       screensize.X - 10,
-                                       chat_y + guitext_chat->getTextHeight()
+                               10,
+                               chat_y,
+                               screensize.X - 10,
+                               chat_y + guitext_chat->getTextHeight()
                        );
-
                        guitext_chat->setRelativePosition(rect);
 
-                       // Don't show chat if empty or profiler or debug is enabled
-                       guitext_chat->setVisible(chat_lines.size() != 0
-                                       && show_chat && show_profiler == 0);
+                       // Don't show chat if disabled or empty or profiler is enabled
+                       guitext_chat->setVisible(show_chat && recent_chat_count != 0
+                                       && !show_profiler);
                }
 
                /*
@@ -2634,6 +2566,8 @@ void the_game(
        */
        if(clouds)
                clouds->drop();
+       if(gui_chat_console)
+               gui_chat_console->drop();
        
        /*
                Draw a "shutting down" screen, which will be shown while the map
@@ -2648,6 +2582,9 @@ void the_game(
                gui_shuttingdowntext->remove();*/
        }
 
+       chat_backend.addMessage(L"", L"# Disconnected.");
+       chat_backend.addMessage(L"", L"");
+
        } // Client scope (must be destructed before destructing *def and tsrc
 
        delete tsrc;
index a9db6c3e1c6397bfab39d8c96c7ee72a07a82bed..01e955ecdb37bb164432f0702c672cc28b81c21c 100644 (file)
@@ -122,6 +122,8 @@ public:
        virtual void clear() {};
 };
 
+class ChatBackend;  /* to avoid having to include chat.h */
+
 void the_game(
        bool &kill,
        bool random_input,
@@ -134,7 +136,8 @@ void the_game(
        std::string address,
        u16 port,
        std::wstring &error_message,
-       std::string configpath
+       std::string configpath,
+       ChatBackend &chat_backend
 );
 
 #endif
diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp
new file mode 100644 (file)
index 0000000..d11a50e
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+Minetest-c55
+Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiChatConsole.h"
+#include "chat.h"
+#include "client.h"
+#include "debug.h"
+#include "gettime.h"
+#include "keycode.h"
+#include "settings.h"
+#include "main.h"  // for g_settings
+#include "porting.h"
+#include "tile.h"
+#include "IGUIFont.h"
+#include <string>
+
+#include "gettext.h"
+
+inline u32 clamp_u8(s32 value)
+{
+       return (u32) MYMIN(MYMAX(value, 0), 255);
+}
+
+
+GUIChatConsole::GUIChatConsole(
+               gui::IGUIEnvironment* env,
+               gui::IGUIElement* parent,
+               s32 id,
+               ChatBackend* backend,
+               Client* client
+):
+       IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
+                       core::rect<s32>(0,0,100,100)),
+       m_chat_backend(backend),
+       m_client(client),
+       m_screensize(v2u32(0,0)),
+       m_animate_time_old(0),
+       m_open(false),
+       m_height(0),
+       m_desired_height(0),
+       m_desired_height_fraction(0.0),
+       m_height_speed(5.0),
+       m_open_inhibited(0),
+       m_cursor_blink(0.0),
+       m_cursor_blink_speed(0.0),
+       m_cursor_height(0.0),
+       m_background(NULL),
+       m_background_color(255, 0, 0, 0),
+       m_font(NULL),
+       m_fontsize(0, 0)
+{
+       m_animate_time_old = getTimeMs();
+
+       // load background settings
+       bool console_color_set = !g_settings->get("console_color").empty();
+       s32 console_alpha = g_settings->getS32("console_alpha");
+
+       // load the background texture depending on settings
+       m_background_color.setAlpha(clamp_u8(console_alpha));
+       if (console_color_set)
+       {
+               v3f console_color = g_settings->getV3F("console_color");
+               m_background_color.setRed(clamp_u8(myround(console_color.X)));
+               m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
+               m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
+       }
+       else
+       {
+               m_background = env->getVideoDriver()->getTexture(getTexturePath("background_chat.jpg").c_str());
+               m_background_color.setRed(255);
+               m_background_color.setGreen(255);
+               m_background_color.setBlue(255);
+       }
+
+       // load the font
+       // FIXME should a custom texture_path be searched too?
+       std::string font_name = "fontdejavusansmono.png";
+       m_font = env->getFont(getTexturePath(font_name).c_str());
+       if (m_font == NULL)
+       {
+               dstream << "Unable to load font: " << font_name << std::endl;
+       }
+       else
+       {
+               core::dimension2d<u32> dim = m_font->getDimension(L"M");
+               m_fontsize = v2u32(dim.Width, dim.Height);
+               dstream << "Font size: " << m_fontsize.X << " " << m_fontsize.Y << std::endl;
+       }
+       m_fontsize.X = MYMAX(m_fontsize.X, 1);
+       m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
+
+       // set default cursor options
+       setCursor(true, true, 2.0, 0.1);
+}
+
+GUIChatConsole::~GUIChatConsole()
+{
+}
+
+void GUIChatConsole::openConsole(f32 height)
+{
+       m_open = true;
+       m_desired_height_fraction = height;
+       m_desired_height = height * m_screensize.Y;
+       reformatConsole();
+}
+
+bool GUIChatConsole::isOpenInhibited() const
+{
+       return m_open_inhibited > 0;
+}
+
+void GUIChatConsole::closeConsole()
+{
+       m_open = false;
+}
+
+void GUIChatConsole::closeConsoleAtOnce()
+{
+       m_open = false;
+       m_height = 0;
+       recalculateConsolePosition();
+}
+
+f32 GUIChatConsole::getDesiredHeight() const
+{
+       return m_desired_height_fraction;
+}
+
+void GUIChatConsole::setCursor(
+       bool visible, bool blinking, f32 blink_speed, f32 relative_height)
+{
+       if (visible)
+       {
+               if (blinking)
+               {
+                       // leave m_cursor_blink unchanged
+                       m_cursor_blink_speed = blink_speed;
+               }
+               else
+               {
+                       m_cursor_blink = 0x8000;  // on
+                       m_cursor_blink_speed = 0.0;
+               }
+       }
+       else
+       {
+               m_cursor_blink = 0;  // off
+               m_cursor_blink_speed = 0.0;
+       }
+       m_cursor_height = relative_height;
+}
+
+void GUIChatConsole::draw()
+{
+       if(!IsVisible)
+               return;
+
+       video::IVideoDriver* driver = Environment->getVideoDriver();
+
+       // Check screen size
+       v2u32 screensize = driver->getScreenSize();
+       if (screensize != m_screensize)
+       {
+               // screen size has changed
+               // scale current console height to new window size
+               if (m_screensize.Y != 0)
+                       m_height = m_height * screensize.Y / m_screensize.Y;
+               m_desired_height = m_desired_height_fraction * m_screensize.Y;
+               m_screensize = screensize;
+               reformatConsole();
+       }
+
+       // Animation
+       u32 now = getTimeMs();
+       animate(now - m_animate_time_old);
+       m_animate_time_old = now;
+
+       // Draw console elements if visible
+       if (m_height > 0)
+       {
+               drawBackground();
+               drawText();
+               drawPrompt();
+       }
+
+       gui::IGUIElement::draw();
+}
+
+void GUIChatConsole::reformatConsole()
+{
+       s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
+       s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
+       if (cols <= 0 || rows <= 0)
+               cols = rows = 0;
+       m_chat_backend->reformat(cols, rows);
+}
+
+void GUIChatConsole::recalculateConsolePosition()
+{
+       core::rect<s32> rect(0, 0, m_screensize.X, m_height);
+       DesiredRect = rect;
+       recalculateAbsolutePosition(false);
+}
+
+void GUIChatConsole::animate(u32 msec)
+{
+       // animate the console height
+       s32 goal = m_open ? m_desired_height : 0;
+       if (m_height != goal)
+       {
+               s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
+               if (max_change == 0)
+                       max_change = 1;
+
+               if (m_height < goal)
+               {
+                       // increase height
+                       if (m_height + max_change < goal)
+                               m_height += max_change;
+                       else
+                               m_height = goal;
+               }
+               else
+               {
+                       // decrease height
+                       if (m_height > goal + max_change)
+                               m_height -= max_change;
+                       else
+                               m_height = goal;
+               }
+
+               recalculateConsolePosition();
+       }
+
+       // blink the cursor
+       if (m_cursor_blink_speed != 0.0)
+       {
+               u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
+               if (blink_increase == 0)
+                       blink_increase = 1;
+               m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
+       }
+
+       // decrease open inhibit counter
+       if (m_open_inhibited > msec)
+               m_open_inhibited -= msec;
+       else
+               m_open_inhibited = 0;
+}
+
+void GUIChatConsole::drawBackground()
+{
+       video::IVideoDriver* driver = Environment->getVideoDriver();
+       if (m_background != NULL)
+       {
+               core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
+               driver->draw2DImage(
+                       m_background,
+                       v2s32(0, 0),
+                       sourcerect,
+                       &AbsoluteClippingRect,
+                       m_background_color,
+                       false);
+       }
+       else
+       {
+               driver->draw2DRectangle(
+                       m_background_color,
+                       core::rect<s32>(0, 0, m_screensize.X, m_height),
+                       &AbsoluteClippingRect);
+       }
+}
+
+void GUIChatConsole::drawText()
+{
+       if (m_font == NULL)
+               return;
+
+       ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
+       for (u32 row = 0; row < buf.getRows(); ++row)
+       {
+               const ChatFormattedLine& line = buf.getFormattedLine(row);
+               if (line.fragments.empty())
+                       continue;
+
+               s32 line_height = m_fontsize.Y;
+               s32 y = row * line_height + m_height - m_desired_height;
+               if (y + line_height < 0)
+                       continue;
+
+               for (u32 i = 0; i < line.fragments.size(); ++i)
+               {
+                       const ChatFormattedFragment& fragment = line.fragments[i];
+                       s32 x = (fragment.column + 1) * m_fontsize.X;
+                       core::rect<s32> destrect(
+                               x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
+                       m_font->draw(
+                               fragment.text.c_str(),
+                               destrect,
+                               video::SColor(255, 255, 255, 255),
+                               false,
+                               false,
+                               &AbsoluteClippingRect);
+               }
+       }
+}
+
+void GUIChatConsole::drawPrompt()
+{
+       if (m_font == NULL)
+               return;
+
+       u32 row = m_chat_backend->getConsoleBuffer().getRows();
+       s32 line_height = m_fontsize.Y;
+       s32 y = row * line_height + m_height - m_desired_height;
+
+       ChatPrompt& prompt = m_chat_backend->getPrompt();
+       std::wstring prompt_text = prompt.getVisiblePortion();
+
+       // FIXME Draw string at once, not character by character
+       // That will only work with the cursor once we have a monospace font
+       for (u32 i = 0; i < prompt_text.size(); ++i)
+       {
+               wchar_t ws[2] = {prompt_text[i], 0};
+               s32 x = (1 + i) * m_fontsize.X;
+               core::rect<s32> destrect(
+                       x, y, x + m_fontsize.X, y + m_fontsize.Y);
+               m_font->draw(
+                       ws,
+                       destrect,
+                       video::SColor(255, 255, 255, 255),
+                       false,
+                       false,
+                       &AbsoluteClippingRect);
+       }
+
+       // Draw the cursor during on periods
+       if ((m_cursor_blink & 0x8000) != 0)
+       {
+               s32 cursor_pos = prompt.getVisibleCursorPosition();
+               if (cursor_pos >= 0)
+               {
+                       video::IVideoDriver* driver = Environment->getVideoDriver();
+                       s32 x = (1 + cursor_pos) * m_fontsize.X;
+                       core::rect<s32> destrect(
+                               x,
+                               y + (1.0-m_cursor_height) * m_fontsize.Y,
+                               x + m_fontsize.X,
+                               y + m_fontsize.Y);
+                       video::SColor cursor_color(255,255,255,255);
+                       driver->draw2DRectangle(
+                               cursor_color,
+                               destrect,
+                               &AbsoluteClippingRect);
+               }
+       }
+
+}
+
+bool GUIChatConsole::OnEvent(const SEvent& event)
+{
+       if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
+       {
+               // Key input
+               if(KeyPress(event.KeyInput) == getKeySetting("keymap_console"))
+               {
+                       closeConsole();
+                       Environment->removeFocus(this);
+
+                       // inhibit open so the_game doesn't reopen immediately
+                       m_open_inhibited = 50;
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_ESCAPE)
+               {
+                       closeConsoleAtOnce();
+                       Environment->removeFocus(this);
+                       // the_game will open the pause menu
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_PRIOR)
+               {
+                       m_chat_backend->scrollPageUp();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_NEXT)
+               {
+                       m_chat_backend->scrollPageDown();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_RETURN)
+               {
+                       std::wstring text = m_chat_backend->getPrompt().submit();
+                       m_client->typeChatMessage(text);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_UP)
+               {
+                       // Up pressed
+                       // Move back in history
+                       m_chat_backend->getPrompt().historyPrev();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_DOWN)
+               {
+                       // Down pressed
+                       // Move forward in history
+                       m_chat_backend->getPrompt().historyNext();
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_LEFT)
+               {
+                       // Left or Ctrl-Left pressed
+                       // move character / word to the left
+                       ChatPrompt::CursorOpScope scope =
+                               event.KeyInput.Control ?
+                               ChatPrompt::CURSOROP_SCOPE_WORD :
+                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_MOVE,
+                               ChatPrompt::CURSOROP_DIR_LEFT,
+                               scope);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_RIGHT)
+               {
+                       // Right or Ctrl-Right pressed
+                       // move character / word to the right
+                       ChatPrompt::CursorOpScope scope =
+                               event.KeyInput.Control ?
+                               ChatPrompt::CURSOROP_SCOPE_WORD :
+                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_MOVE,
+                               ChatPrompt::CURSOROP_DIR_RIGHT,
+                               scope);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_HOME)
+               {
+                       // Home pressed
+                       // move to beginning of line
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_MOVE,
+                               ChatPrompt::CURSOROP_DIR_LEFT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_END)
+               {
+                       // End pressed
+                       // move to end of line
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_MOVE,
+                               ChatPrompt::CURSOROP_DIR_RIGHT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_BACK)
+               {
+                       // Backspace or Ctrl-Backspace pressed
+                       // delete character / word to the left
+                       ChatPrompt::CursorOpScope scope =
+                               event.KeyInput.Control ?
+                               ChatPrompt::CURSOROP_SCOPE_WORD :
+                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_LEFT,
+                               scope);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_DELETE)
+               {
+                       // Delete or Ctrl-Delete pressed
+                       // delete character / word to the right
+                       ChatPrompt::CursorOpScope scope =
+                               event.KeyInput.Control ?
+                               ChatPrompt::CURSOROP_SCOPE_WORD :
+                               ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_RIGHT,
+                               scope);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
+               {
+                       // Ctrl-U pressed
+                       // kill line to left end
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_LEFT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
+               {
+                       // Ctrl-K pressed
+                       // kill line to right end
+                       m_chat_backend->getPrompt().cursorOperation(
+                               ChatPrompt::CURSOROP_DELETE,
+                               ChatPrompt::CURSOROP_DIR_RIGHT,
+                               ChatPrompt::CURSOROP_SCOPE_LINE);
+                       return true;
+               }
+               else if(event.KeyInput.Key == KEY_TAB)
+               {
+                       // Tab or Shift-Tab pressed
+                       // Nick completion
+                       core::list<std::wstring> names = m_client->getConnectedPlayerNames();
+                       bool backwards = event.KeyInput.Shift;
+                       m_chat_backend->getPrompt().nickCompletion(names, backwards);
+                       return true;
+               }
+               else if(event.KeyInput.Char != 0 && !event.KeyInput.Control)
+               {
+                       m_chat_backend->getPrompt().input(event.KeyInput.Char);
+                       return true;
+               }
+       }
+       else if(event.EventType == EET_MOUSE_INPUT_EVENT)
+       {
+               if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
+               {
+                       s32 rows = myround(-3.0 * event.MouseInput.Wheel);
+                       m_chat_backend->scroll(rows);
+               }
+       }
+
+       return Parent ? Parent->OnEvent(event) : false;
+}
+
diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h
new file mode 100644 (file)
index 0000000..2b78b9e
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+Minetest-c55
+Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef GUICHATCONSOLE_HEADER
+#define GUICHATCONSOLE_HEADER
+
+#include "common_irrlicht.h"
+#include "chat.h"
+
+class Client;
+
+class GUIChatConsole : public gui::IGUIElement
+{
+public:
+       GUIChatConsole(gui::IGUIEnvironment* env,
+                       gui::IGUIElement* parent,
+                       s32 id,
+                       ChatBackend* backend,
+                       Client* client);
+       virtual ~GUIChatConsole();
+
+       // Open the console (height = desired fraction of screen size)
+       // This doesn't open immediately but initiates an animation.
+       // You should call isOpenInhibited() before this.
+       void openConsole(f32 height);
+       // Check if the console should not be opened at the moment
+       // This is to avoid reopening the console immediately after closing
+       bool isOpenInhibited() const;
+       // Close the console, equivalent to openConsole(0).
+       // This doesn't close immediately but initiates an animation.
+       void closeConsole();
+       // Close the console immediately, without animation.
+       void closeConsoleAtOnce();
+
+       // Return the desired height (fraction of screen size)
+       // Zero if the console is closed or getting closed
+       f32 getDesiredHeight() const;
+
+       // Change how the cursor looks
+       void setCursor(
+               bool visible,
+               bool blinking = false,
+               f32 blink_speed = 1.0,
+               f32 relative_height = 1.0);
+
+       // Irrlicht draw method
+       virtual void draw();
+
+       bool canTakeFocus(gui::IGUIElement* element) { return false; }
+
+       virtual bool OnEvent(const SEvent& event);
+
+private:
+       void reformatConsole();
+       void recalculateConsolePosition();
+
+       // These methods are called by draw
+       void animate(u32 msec);
+       void drawBackground();
+       void drawText();
+       void drawPrompt();
+
+private:
+       // pointer to the chat backend
+       ChatBackend* m_chat_backend;
+
+       // pointer to the client
+       Client* m_client;
+
+       // current screen size
+       v2u32 m_screensize;
+
+       // used to compute how much time passed since last animate()
+       u32 m_animate_time_old;
+
+       // should the console be opened or closed?
+       bool m_open;
+       // current console height [pixels]
+       s32 m_height;
+       // desired height [pixels]
+       f32 m_desired_height;
+       // desired height [screen height fraction]
+       f32 m_desired_height_fraction;
+       // console open/close animation speed [screen height fraction / second]
+       f32 m_height_speed;
+       // if nonzero, opening the console is inhibited [milliseconds]
+       u32 m_open_inhibited;
+
+       // cursor blink frame (16-bit value)
+       // cursor is off during [0,32767] and on during [32768,65535]
+       u32 m_cursor_blink;
+       // cursor blink speed [on/off toggles / second]
+       f32 m_cursor_blink_speed;
+       // cursor height [line height]
+       f32 m_cursor_height;
+
+       // background texture
+       video::ITexture* m_background;
+       // background color (including alpha)
+       video::SColor m_background_color;
+
+       // font
+       gui::IGUIFont* m_font;
+       v2u32 m_fontsize;
+};
+
+
+#endif
+
index 01f583a0175dc8d79976079d5c62679b41aa8fd2..4e04fccf0e1406c0268dcf2fc8bd58908a9e95d3 100644 (file)
@@ -261,7 +261,20 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
                this->cmd = Environment->addButton(rect, this, GUI_ID_KEY_CMD_BUTTON,
                                wgettext(key_cmd.name()));
        }
+       offset += v2s32(0, 25);
+       {
+               core::rect < s32 > rect(0, 0, 100, 20);
+               rect += topleft + v2s32(offset.X, offset.Y);
+               Environment->addStaticText(wgettext("Console"), rect, false, true, this, -1);
+               //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
+       }
 
+       {
+               core::rect < s32 > rect(0, 0, 100, 30);
+               rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
+               this->console = Environment->addButton(rect, this, GUI_ID_KEY_CONSOLE_BUTTON,
+                               wgettext(key_console.name()));
+       }
 
        //next col
        offset = v2s32(250, 40);
@@ -371,6 +384,7 @@ bool GUIKeyChangeMenu::acceptInput()
        g_settings->set("keymap_inventory", key_inventory.sym());
        g_settings->set("keymap_chat", key_chat.sym());
        g_settings->set("keymap_cmd", key_cmd.sym());
+       g_settings->set("keymap_console", key_console.sym());
        g_settings->set("keymap_rangeselect", key_range.sym());
        g_settings->set("keymap_freemove", key_fly.sym());
        g_settings->set("keymap_fastmove", key_fast.sym());
@@ -391,6 +405,7 @@ void GUIKeyChangeMenu::init_keys()
        key_inventory = getKeySetting("keymap_inventory");
        key_chat = getKeySetting("keymap_chat");
        key_cmd = getKeySetting("keymap_cmd");
+       key_console = getKeySetting("keymap_console");
        key_range = getKeySetting("keymap_rangeselect");
        key_fly = getKeySetting("keymap_freemove");
        key_fast = getKeySetting("keymap_fastmove");
@@ -437,6 +452,9 @@ bool GUIKeyChangeMenu::resetMenu()
                case GUI_ID_KEY_CMD_BUTTON:
                        this->cmd->setText(wgettext(key_cmd.name()));
                        break;
+               case GUI_ID_KEY_CONSOLE_BUTTON:
+                       this->console->setText(wgettext(key_console.name()));
+                       break;
                case GUI_ID_KEY_RANGE_BUTTON:
                        this->range->setText(wgettext(key_range.name()));
                        break;
@@ -516,6 +534,11 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
                        this->cmd->setText(wgettext(kp.name()));
                        this->key_cmd = kp;
                }
+               else if (activeKey == GUI_ID_KEY_CONSOLE_BUTTON)
+               {
+                       this->console->setText(wgettext(kp.name()));
+                       this->key_console = kp;
+               }
                else if (activeKey == GUI_ID_KEY_RANGE_BUTTON)
                {
                        this->range->setText(wgettext(kp.name()));
@@ -630,6 +653,11 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
                                activeKey = event.GUIEvent.Caller->getID();
                                this->cmd->setText(wgettext("press Key"));
                                break;
+                       case GUI_ID_KEY_CONSOLE_BUTTON:
+                               resetMenu();
+                               activeKey = event.GUIEvent.Caller->getID();
+                               this->console->setText(wgettext("press Key"));
+                               break;
                        case GUI_ID_KEY_SNEAK_BUTTON:
                                resetMenu();
                                activeKey = event.GUIEvent.Caller->getID();
index a3d8b474351b1bfd8ee74884a766d2603ce0e48f..9772bde3c8d2d88780e486a407a5db24d2d71567 100644 (file)
@@ -44,6 +44,7 @@ enum
        GUI_ID_KEY_JUMP_BUTTON,
        GUI_ID_KEY_CHAT_BUTTON,
        GUI_ID_KEY_CMD_BUTTON,
+       GUI_ID_KEY_CONSOLE_BUTTON,
        GUI_ID_KEY_SNEAK_BUTTON,
        GUI_ID_KEY_DROP_BUTTON,
        GUI_ID_KEY_INVENTORY_BUTTON,
@@ -91,6 +92,7 @@ private:
        gui::IGUIButton *dump;
        gui::IGUIButton *chat;
        gui::IGUIButton *cmd;
+       gui::IGUIButton *console;
 
        s32 activeKey;
        KeyPress key_forward;
@@ -107,6 +109,7 @@ private:
        KeyPress key_range;
        KeyPress key_chat;
        KeyPress key_cmd;
+       KeyPress key_console;
        KeyPress key_dump;
 };
 
index 1b7331ee235eeb2a45f8ee8e448e8f5ceb9f6548..10e01be2ad006a6d0fd2f92263839d4adc8a91d7 100644 (file)
@@ -403,6 +403,7 @@ Doing currently:
 #include "game.h"
 #include "keycode.h"
 #include "tile.h"
+#include "chat.h"
 #include "defaultsettings.h"
 #include "gettext.h"
 #include "settings.h"
@@ -940,20 +941,20 @@ void drawMenuBackground(video::IVideoDriver* driver)
                        driver->getTexture(getTexturePath("menubg.png").c_str());
        if(bgtexture)
        {
-               s32 texturesize = 128;
-               s32 tiled_y = screensize.Height / texturesize + 1;
-               s32 tiled_x = screensize.Width / texturesize + 1;
+               s32 scaledsize = 128;
                
-               for(s32 y=0; y<tiled_y; y++)
-               for(s32 x=0; x<tiled_x; x++)
-               {
-                       core::rect<s32> rect(0,0,texturesize,texturesize);
-                       rect += v2s32(x*texturesize, y*texturesize);
-                       driver->draw2DImage(bgtexture, rect,
-                               core::rect<s32>(core::position2d<s32>(0,0),
-                               core::dimension2di(bgtexture->getSize())),
-                               NULL, NULL, true);
-               }
+               // The important difference between destsize and screensize is
+               // that destsize is rounded to whole scaled pixels.
+               // These formulas use component-wise multiplication and division of v2u32.
+               v2u32 texturesize = bgtexture->getSize();
+               v2u32 sourcesize = texturesize * screensize / scaledsize + v2u32(1,1);
+               v2u32 destsize = scaledsize * sourcesize / texturesize;
+               
+               // Default texture wrapping mode in Irrlicht is ETC_REPEAT.
+               driver->draw2DImage(bgtexture,
+                       core::rect<s32>(0, 0, destsize.X, destsize.Y),
+                       core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+                       NULL, NULL, true);
        }
        
        video::ITexture *logotexture =
@@ -1479,6 +1480,8 @@ int main(int argc, char *argv[])
                GUI stuff
        */
 
+       ChatBackend chat_backend;
+
        /*
                If an error occurs, this is set to something and the
                menu-game loop is restarted. It is then displayed before
@@ -1663,7 +1666,8 @@ int main(int argc, char *argv[])
                                address,
                                port,
                                error_message,
-                               configpath
+                               configpath,
+                               chat_backend
                        );
 
                } //try
index f4c7c30171073a20192fd57b5ea6f8823dbbf4b0..aa64c28bb601ceb3be158917d939c461c60a89ed 100644 (file)
@@ -694,6 +694,46 @@ private:
        u32 *m_result;
 };
 
+// Tests if two strings are equal, optionally case insensitive
+inline bool str_equal(const std::wstring& s1, const std::wstring& s2,
+               bool case_insensitive = false)
+{
+       if(case_insensitive)
+       {
+               if(s1.size() != s2.size())
+                       return false;
+               for(size_t i = 0; i < s1.size(); ++i)
+                       if(tolower(s1[i]) != tolower(s2[i]))
+                               return false;
+               return true;
+       }
+       else
+       {
+               return s1 == s2;
+       }
+}
+
+// Tests if the second string is a prefix of the first, optionally case insensitive
+inline bool str_starts_with(const std::wstring& str, const std::wstring& prefix,
+               bool case_insensitive = false)
+{
+       if(str.size() < prefix.size())
+               return false;
+       if(case_insensitive)
+       {
+               for(size_t i = 0; i < prefix.size(); ++i)
+                       if(tolower(str[i]) != tolower(prefix[i]))
+                               return false;
+       }
+       else
+       {
+               for(size_t i = 0; i < prefix.size(); ++i)
+                       if(str[i] != prefix[i])
+                               return false;
+       }
+       return true;
+}
+
 // Calculates the borders of a "d-radius" cube
 inline void getFacePositions(core::list<v3s16> &list, u16 d)
 {
@@ -1565,6 +1605,15 @@ inline std::string wrap_rows(const std::string &from, u32 rowlen)
 #define MYMIN(a,b) ((a)<(b)?(a):(b))
 #define MYMAX(a,b) ((a)>(b)?(a):(b))
 
+/*
+       Returns nearest 32-bit integer for given floating point number.
+       <cmath> and <math.h> in VC++ don't provide round().
+*/
+inline s32 myround(f32 f)
+{
+       return floor(f + 0.5);
+}
+
 /*
        Returns integer position of node in given floating point position
 */