Merge remote-tracking branch 'minetest/master'
This commit is contained in:
commit
52fe7de610
@ -7,6 +7,15 @@
|
||||
|
||||
-- Initialize some very basic things
|
||||
function core.debug(...) core.log(table.concat({...}, "\t")) end
|
||||
if core.print then
|
||||
local core_print = core.print
|
||||
-- Override native print and use
|
||||
-- terminal if that's turned on
|
||||
function print(...)
|
||||
core_print(table.concat({...}, "\t"))
|
||||
end
|
||||
core.print = nil -- don't pollute our namespace
|
||||
end
|
||||
math.randomseed(os.time())
|
||||
os.setlocale("C", "numeric")
|
||||
minetest = core
|
||||
|
@ -936,52 +936,58 @@ mgv7_np_cave2 (Mapgen v7 cave2 noise parameters) noise_params 0, 12, (100, 100,
|
||||
# Flags starting with "no" are used to explicitly disable them.
|
||||
mgfractal_spflags (Mapgen fractal flags) flags nojulia julia,nojulia
|
||||
|
||||
# Mandelbrot set: iterations of recursive function.
|
||||
# Mandelbrot set: Iterations of the recursive function.
|
||||
# Controls scale of finest detail.
|
||||
mgfractal_m_iterations (Mapgen fractal mandelbrot iterations) int 9
|
||||
|
||||
# Mandelbrot set: Approximate scale in nodes.
|
||||
# Format is 3 numbers separated by commas, inside brackets.
|
||||
# Mandelbrot set: Approximate (X,Y,Z) scales in nodes.
|
||||
# Format is 3 numbers separated by commas and inside brackets.
|
||||
mgfractal_m_scale (Mapgen fractal mandelbrot scale) string (1024.0, 256.0, 1024.0)
|
||||
# 'string' is currently used for a v3f argument.
|
||||
# Type 'string' is currently used for a v3f.
|
||||
|
||||
# Mandelbrot set: Offsets the fractal from world centre.
|
||||
# Format is 3 numbers separated by commas, inside brackets.
|
||||
# Range -2 to 2, multiply by m_scale for actual offset in nodes.
|
||||
# Mandelbrot set: (X,Y,Z) offsets from world centre.
|
||||
# Format is 3 numbers separated by commas and inside brackets.
|
||||
# Range roughly -2 to 2, multiply by m_scale for offsets in nodes.
|
||||
mgfractal_m_offset (Mapgen fractal mandelbrot offset) string (1.75, 0.0, 0.0)
|
||||
# 'string' is currently used for a v3f argument.
|
||||
# Type 'string' is currently used for a v3f.
|
||||
|
||||
# Mandelbrot set: W co-ordinate of the generated 3D slice of the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
mgfractal_m_slice_w (Mapgen fractal mandelbrot slice w) float 0.0
|
||||
|
||||
# Julia set: iterations of recursive function.
|
||||
# Julia set: Iterations of the recursive function.
|
||||
# Controls scale of finest detail.
|
||||
mgfractal_j_iterations (Mapgen fractal julia iterations) int 9
|
||||
|
||||
# Julia set: Approximate scale in nodes.
|
||||
# Format is 3 numbers separated by commas, inside brackets.
|
||||
# Julia set: Approximate (X,Y,Z) scales in nodes.
|
||||
# Format is 3 numbers separated by commas and inside brackets.
|
||||
mgfractal_j_scale (Mapgen fractal julia scale) string (2048.0, 512.0, 2048.0)
|
||||
# 'string' is currently used for a v3f argument.
|
||||
# Type 'string' is currently used for a v3f.
|
||||
|
||||
# Julia set: Offsets the fractal from world centre.
|
||||
# Format is 3 numbers separated by commas, inside brackets.
|
||||
# Range -2 to 2, multiply by j_scale for actual offset in nodes.
|
||||
# Julia set: (X,Y,Z) offsets from world centre.
|
||||
# Format is 3 numbers separated by commas and inside brackets.
|
||||
# Range roughly -2 to 2, multiply by j_scale for offsets in nodes.
|
||||
mgfractal_j_offset (Mapgen fractal julia offset) string (0.0, 1.0, 0.0)
|
||||
# 'string' is currently used for a v3f argument.
|
||||
# Type 'string' is currently used for a v3f.
|
||||
|
||||
# Julia set: W co-ordinate of the generated 3D slice of the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
mgfractal_j_slice_w (Mapgen fractal julia slice w) float 0.0
|
||||
|
||||
# Julia set: X value determining the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
mgfractal_julia_x (Mapgen fractal julia x) float 0.33
|
||||
|
||||
# Julia set: Y value determining the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
mgfractal_julia_y (Mapgen fractal julia y) float 0.33
|
||||
|
||||
# Julia set: Z value determining the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
mgfractal_julia_z (Mapgen fractal julia z) float 0.33
|
||||
|
||||
# Julia set: W value determining the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
mgfractal_julia_w (Mapgen fractal julia w) float 0.33
|
||||
|
||||
mgfractal_np_seabed (Mapgen fractal seabed noise parameters) noise_params -14, 9, (600, 600, 600), 41900, 5, 0.6, 2.0
|
||||
|
189
cmake/Modules/FindNcursesw.cmake
Normal file
189
cmake/Modules/FindNcursesw.cmake
Normal file
@ -0,0 +1,189 @@
|
||||
#.rst:
|
||||
# FindNcursesw
|
||||
# ------------
|
||||
#
|
||||
# Find the ncursesw (wide ncurses) include file and library.
|
||||
#
|
||||
# Based on FindCurses.cmake which comes with CMake.
|
||||
#
|
||||
# Checks for ncursesw first. If not found, it then executes the
|
||||
# regular old FindCurses.cmake to look for for ncurses (or curses).
|
||||
#
|
||||
#
|
||||
# Result Variables
|
||||
# ^^^^^^^^^^^^^^^^
|
||||
#
|
||||
# This module defines the following variables:
|
||||
#
|
||||
# ``CURSES_FOUND``
|
||||
# True if curses is found.
|
||||
# ``NCURSESW_FOUND``
|
||||
# True if ncursesw is found.
|
||||
# ``CURSES_INCLUDE_DIRS``
|
||||
# The include directories needed to use Curses.
|
||||
# ``CURSES_LIBRARIES``
|
||||
# The libraries needed to use Curses.
|
||||
# ``CURSES_HAVE_CURSES_H``
|
||||
# True if curses.h is available.
|
||||
# ``CURSES_HAVE_NCURSES_H``
|
||||
# True if ncurses.h is available.
|
||||
# ``CURSES_HAVE_NCURSES_NCURSES_H``
|
||||
# True if ``ncurses/ncurses.h`` is available.
|
||||
# ``CURSES_HAVE_NCURSES_CURSES_H``
|
||||
# True if ``ncurses/curses.h`` is available.
|
||||
# ``CURSES_HAVE_NCURSESW_NCURSES_H``
|
||||
# True if ``ncursesw/ncurses.h`` is available.
|
||||
# ``CURSES_HAVE_NCURSESW_CURSES_H``
|
||||
# True if ``ncursesw/curses.h`` is available.
|
||||
#
|
||||
# Set ``CURSES_NEED_NCURSES`` to ``TRUE`` before the
|
||||
# ``find_package(Ncursesw)`` call if NCurses functionality is required.
|
||||
#
|
||||
#=============================================================================
|
||||
# Copyright 2001-2014 Kitware, Inc.
|
||||
# modifications: Copyright 2015 kahrl <kahrl@gmx.net>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the names of Kitware, Inc., the Insight Software Consortium,
|
||||
# nor the names of their contributors may be used to endorse or promote
|
||||
# products derived from this software without specific prior written
|
||||
# permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
#
|
||||
# The above copyright and license notice applies to distributions of
|
||||
# CMake in source and binary form. Some source files contain additional
|
||||
# notices of original copyright by their contributors; see each source
|
||||
# for details. Third-party software packages supplied with CMake under
|
||||
# compatible licenses provide their own copyright notices documented in
|
||||
# corresponding subdirectories.
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
#
|
||||
# CMake was initially developed by Kitware with the following sponsorship:
|
||||
#
|
||||
# * National Library of Medicine at the National Institutes of Health
|
||||
# as part of the Insight Segmentation and Registration Toolkit (ITK).
|
||||
#
|
||||
# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel
|
||||
# Visualization Initiative.
|
||||
#
|
||||
# * National Alliance for Medical Image Computing (NAMIC) is funded by the
|
||||
# National Institutes of Health through the NIH Roadmap for Medical Research,
|
||||
# Grant U54 EB005149.
|
||||
#
|
||||
# * Kitware, Inc.
|
||||
#=============================================================================
|
||||
|
||||
include(CheckLibraryExists)
|
||||
|
||||
find_library(CURSES_NCURSESW_LIBRARY NAMES ncursesw
|
||||
DOC "Path to libncursesw.so or .lib or .a")
|
||||
|
||||
set(CURSES_USE_NCURSES FALSE)
|
||||
set(CURSES_USE_NCURSESW FALSE)
|
||||
|
||||
if(CURSES_NCURSESW_LIBRARY)
|
||||
set(CURSES_USE_NCURSES TRUE)
|
||||
set(CURSES_USE_NCURSESW TRUE)
|
||||
endif()
|
||||
|
||||
if(CURSES_USE_NCURSESW)
|
||||
get_filename_component(_cursesLibDir "${CURSES_NCURSESW_LIBRARY}" PATH)
|
||||
get_filename_component(_cursesParentDir "${_cursesLibDir}" PATH)
|
||||
|
||||
find_path(CURSES_INCLUDE_PATH
|
||||
NAMES ncursesw/ncurses.h ncursesw/curses.h
|
||||
HINTS "${_cursesParentDir}/include"
|
||||
)
|
||||
|
||||
# Previous versions of FindCurses provided these values.
|
||||
if(NOT DEFINED CURSES_LIBRARY)
|
||||
set(CURSES_LIBRARY "${CURSES_NCURSESW_LIBRARY}")
|
||||
endif()
|
||||
|
||||
CHECK_LIBRARY_EXISTS("${CURSES_NCURSESW_LIBRARY}"
|
||||
cbreak "" CURSES_NCURSESW_HAS_CBREAK)
|
||||
if(NOT CURSES_NCURSESW_HAS_CBREAK)
|
||||
find_library(CURSES_EXTRA_LIBRARY tinfo HINTS "${_cursesLibDir}"
|
||||
DOC "Path to libtinfo.so or .lib or .a")
|
||||
find_library(CURSES_EXTRA_LIBRARY tinfo )
|
||||
endif()
|
||||
|
||||
# Report whether each possible header name exists in the include directory.
|
||||
if(NOT DEFINED CURSES_HAVE_NCURSESW_NCURSES_H)
|
||||
if(EXISTS "${CURSES_INCLUDE_PATH}/ncursesw/ncurses.h")
|
||||
set(CURSES_HAVE_NCURSESW_NCURSES_H "${CURSES_INCLUDE_PATH}/ncursesw/ncurses.h")
|
||||
else()
|
||||
set(CURSES_HAVE_NCURSESW_NCURSES_H "CURSES_HAVE_NCURSESW_NCURSES_H-NOTFOUND")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT DEFINED CURSES_HAVE_NCURSESW_CURSES_H)
|
||||
if(EXISTS "${CURSES_INCLUDE_PATH}/ncursesw/curses.h")
|
||||
set(CURSES_HAVE_NCURSESW_CURSES_H "${CURSES_INCLUDE_PATH}/ncursesw/curses.h")
|
||||
else()
|
||||
set(CURSES_HAVE_NCURSESW_CURSES_H "CURSES_HAVE_NCURSESW_CURSES_H-NOTFOUND")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_library(CURSES_FORM_LIBRARY form HINTS "${_cursesLibDir}"
|
||||
DOC "Path to libform.so or .lib or .a")
|
||||
find_library(CURSES_FORM_LIBRARY form )
|
||||
|
||||
# Need to provide the *_LIBRARIES
|
||||
set(CURSES_LIBRARIES ${CURSES_LIBRARY})
|
||||
|
||||
if(CURSES_EXTRA_LIBRARY)
|
||||
set(CURSES_LIBRARIES ${CURSES_LIBRARIES} ${CURSES_EXTRA_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(CURSES_FORM_LIBRARY)
|
||||
set(CURSES_LIBRARIES ${CURSES_LIBRARIES} ${CURSES_FORM_LIBRARY})
|
||||
endif()
|
||||
|
||||
# Provide the *_INCLUDE_DIRS result.
|
||||
set(CURSES_INCLUDE_DIRS ${CURSES_INCLUDE_PATH})
|
||||
set(CURSES_INCLUDE_DIR ${CURSES_INCLUDE_PATH}) # compatibility
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set CURSES_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Ncursesw DEFAULT_MSG
|
||||
CURSES_LIBRARY CURSES_INCLUDE_PATH)
|
||||
set(CURSES_FOUND ${NCURSESW_FOUND})
|
||||
|
||||
else()
|
||||
find_package(Curses)
|
||||
set(NCURSESW_FOUND FALSE)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
CURSES_INCLUDE_PATH
|
||||
CURSES_CURSES_LIBRARY
|
||||
CURSES_NCURSES_LIBRARY
|
||||
CURSES_NCURSESW_LIBRARY
|
||||
CURSES_EXTRA_LIBRARY
|
||||
CURSES_FORM_LIBRARY
|
||||
)
|
@ -89,6 +89,9 @@ Run speed tests
|
||||
.B \-\-migrate <value>
|
||||
Migrate from current map backend to another. Possible values are sqlite3,
|
||||
leveldb, redis, and dummy.
|
||||
.TP
|
||||
.B \-\-terminal
|
||||
Display an interactive terminal over ncurses during execution.
|
||||
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
|
@ -1188,54 +1188,50 @@
|
||||
# type: flags possible values: julia, nojulia
|
||||
# mgfractal_spflags = nojulia
|
||||
|
||||
# Mandelbrot set: iterations of recursive function.
|
||||
# Mandelbrot set: Iterations of the recursive function.
|
||||
# Controls scale of finest detail.
|
||||
# type: int
|
||||
# mgfractal_m_iterations = 9
|
||||
|
||||
# Mandelbrot set: Approximate scale in nodes.
|
||||
# Mandelbrot set: Approximate (X,Y,Z) scales in nodes.
|
||||
# type: v3f
|
||||
# mgfractal_m_scale = (1024.0, 256.0, 1024.0)
|
||||
|
||||
# Mandelbrot set: Offsets the fractal from world centre.
|
||||
# Range -2 to 2, multiply by m_scale for actual offset in nodes.
|
||||
# Mandelbrot set: (X,Y,Z) offsets from world centre.
|
||||
# Range roughly -2 to 2, multiply by m_scale for offsets in nodes.
|
||||
# type: v3f
|
||||
# mgfractal_m_offset = (1.75, 0.0, 0.0)
|
||||
|
||||
# Mandelbrot set: W co-ordinate of the generated 3D slice of the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
# type: float
|
||||
# mgfractal_m_slice_w = 0.0
|
||||
|
||||
# Julia set: iterations of recursive function.
|
||||
# Julia set: Iterations of the recursive function.
|
||||
# Controls scale of finest detail.
|
||||
# type: int
|
||||
# mgfractal_j_iterations = 9
|
||||
|
||||
# Julia set: Approximate scale in nodes.
|
||||
# Julia set: Approximate (X,Y,Z) scales in nodes.
|
||||
# type: v3f
|
||||
# mgfractal_j_scale = (2048.0, 512.0, 2048.0)
|
||||
|
||||
# Julia set: Offsets the fractal from world centre.
|
||||
# Range -2 to 2, multiply by j_scale for actual offset in nodes.
|
||||
# Julia set: (X,Y,Z) offsets from world centre.
|
||||
# Range roughly -2 to 2, multiply by j_scale for offsets in nodes.
|
||||
# type: v3f
|
||||
# mgfractal_j_offset = (0.0, 1.0, 0.0)
|
||||
|
||||
# Julia set: W co-ordinate of the generated 3D slice of the 4D shape.
|
||||
# Range roughly -2 to 2.
|
||||
# type: float
|
||||
# mgfractal_j_slice_w = 0.0
|
||||
|
||||
# Julia set: The following 4 values determine the 4D shape.
|
||||
# Range -2 to 2.
|
||||
# Range roughly -2 to 2.
|
||||
# type: float
|
||||
# mgfractal_julia_x = 0.33
|
||||
|
||||
# type: float
|
||||
# mgfractal_julia_y = 0.33
|
||||
|
||||
# type: float
|
||||
# mgfractal_julia_z = 0.33
|
||||
|
||||
# type: float
|
||||
# mgfractal_julia_w = 0.33
|
||||
|
||||
# type: noise_params
|
||||
|
@ -386,6 +386,20 @@ find_package(Lua REQUIRED)
|
||||
|
||||
find_package(GMP REQUIRED)
|
||||
|
||||
option(ENABLE_CURSES "Enable ncurses console" TRUE)
|
||||
set(USE_CURSES FALSE)
|
||||
|
||||
if(ENABLE_CURSES)
|
||||
find_package(Ncursesw)
|
||||
if(CURSES_FOUND)
|
||||
set(USE_CURSES TRUE)
|
||||
message(STATUS "ncurses console enabled.")
|
||||
include_directories(${CURSES_INCLUDE_DIRS})
|
||||
else()
|
||||
message(STATUS "ncurses not found!")
|
||||
endif()
|
||||
endif(ENABLE_CURSES)
|
||||
|
||||
option(ENABLE_LEVELDB "Enable LevelDB backend" TRUE)
|
||||
set(USE_LEVELDB FALSE)
|
||||
|
||||
@ -618,6 +632,7 @@ set(common_SRCS
|
||||
areastore.cpp
|
||||
ban.cpp
|
||||
cavegen.cpp
|
||||
chat.cpp
|
||||
clientiface.cpp
|
||||
collision.cpp
|
||||
content_abm.cpp
|
||||
@ -683,6 +698,7 @@ set(common_SRCS
|
||||
sound.cpp
|
||||
staticobject.cpp
|
||||
subgame.cpp
|
||||
terminal_chat_console.cpp
|
||||
tool.cpp
|
||||
treegen.cpp
|
||||
version.cpp
|
||||
@ -735,7 +751,6 @@ set(client_SRCS
|
||||
${sound_SRCS}
|
||||
${client_network_SRCS}
|
||||
camera.cpp
|
||||
chat.cpp
|
||||
client.cpp
|
||||
clientmap.cpp
|
||||
clientmedia.cpp
|
||||
@ -868,6 +883,9 @@ if(BUILD_CLIENT)
|
||||
${CGUITTFONT_LIBRARY}
|
||||
)
|
||||
endif()
|
||||
if (USE_CURSES)
|
||||
target_link_libraries(${PROJECT_NAME} ${CURSES_LIBRARIES})
|
||||
endif()
|
||||
if (USE_LEVELDB AND NOT FORCE_LEVELDB)
|
||||
target_link_libraries(${PROJECT_NAME} ${LEVELDB_LIBRARY} ${SNAPPY_LIBRARY})
|
||||
endif()
|
||||
@ -903,6 +921,9 @@ if(BUILD_SERVER)
|
||||
)
|
||||
set_target_properties(${PROJECT_NAME}server PROPERTIES
|
||||
COMPILE_DEFINITIONS "SERVER")
|
||||
if (USE_CURSES)
|
||||
target_link_libraries(${PROJECT_NAME}server ${CURSES_LIBRARIES})
|
||||
endif()
|
||||
if (USE_LEVELDB)
|
||||
target_link_libraries(${PROJECT_NAME}server ${LEVELDB_LIBRARY} ${SNAPPY_LIBRARY})
|
||||
endif()
|
||||
|
@ -679,7 +679,8 @@ void ChatPrompt::clampView()
|
||||
|
||||
ChatBackend::ChatBackend():
|
||||
m_console_buffer(500),
|
||||
m_recent_buffer(g_settings->getU16("chat_buffer_size")),
|
||||
//m_recent_buffer(g_settings->getU16("chat_buffer_size")),
|
||||
m_recent_buffer(6),
|
||||
m_prompt(L"]", 500)
|
||||
{
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "FMColoredString.h"
|
||||
|
||||
// Chat console related classes, only used by the client
|
||||
// Chat console related classes
|
||||
|
||||
struct ChatLine
|
||||
{
|
||||
@ -128,7 +128,7 @@ private:
|
||||
u32 m_scrollback;
|
||||
// Array of unformatted chat lines
|
||||
std::vector<ChatLine> m_unformatted;
|
||||
|
||||
|
||||
// Number of character columns in console
|
||||
u32 m_cols;
|
||||
// Number of character rows in console
|
||||
@ -220,7 +220,7 @@ private:
|
||||
std::wstring m_line;
|
||||
// History buffer
|
||||
std::vector<std::wstring> m_history;
|
||||
// History index (0 <= m_history_index <= m_history.size())
|
||||
// History index (0 <= m_history_index <= m_history.size())
|
||||
u32 m_history_index;
|
||||
// Maximum number of history entries
|
||||
u32 m_history_limit;
|
||||
|
82
src/chat_interface.h
Normal file
82
src/chat_interface.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2015 est31 <MTest31@outlook.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser 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_INTERFACE_H
|
||||
#define CHAT_INTERFACE_H
|
||||
|
||||
#include "util/container.h"
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include "irrlichttypes.h"
|
||||
|
||||
enum ChatEventType {
|
||||
CET_CHAT,
|
||||
CET_NICK_ADD,
|
||||
CET_NICK_REMOVE,
|
||||
CET_TIME_INFO,
|
||||
};
|
||||
|
||||
class ChatEvent {
|
||||
protected:
|
||||
ChatEvent(ChatEventType a_type) { type = a_type; }
|
||||
public:
|
||||
ChatEventType type;
|
||||
};
|
||||
|
||||
struct ChatEventTimeInfo : public ChatEvent {
|
||||
ChatEventTimeInfo(
|
||||
u64 a_game_time,
|
||||
u32 a_time) :
|
||||
ChatEvent(CET_TIME_INFO),
|
||||
game_time(a_game_time),
|
||||
time(a_time)
|
||||
{}
|
||||
|
||||
u64 game_time;
|
||||
u32 time;
|
||||
};
|
||||
|
||||
struct ChatEventNick : public ChatEvent {
|
||||
ChatEventNick(ChatEventType a_type,
|
||||
const std::string &a_nick) :
|
||||
ChatEvent(a_type), // one of CET_NICK_ADD, CET_NICK_REMOVE
|
||||
nick(a_nick)
|
||||
{}
|
||||
|
||||
std::string nick;
|
||||
};
|
||||
|
||||
struct ChatEventChat : public ChatEvent {
|
||||
ChatEventChat(const std::string &a_nick,
|
||||
const std::wstring &an_evt_msg) :
|
||||
ChatEvent(CET_CHAT),
|
||||
nick(a_nick),
|
||||
evt_msg(an_evt_msg)
|
||||
{}
|
||||
|
||||
std::string nick;
|
||||
std::wstring evt_msg;
|
||||
};
|
||||
|
||||
struct ChatInterface {
|
||||
MutexedQueue<ChatEvent *> command_queue; // chat backend --> server
|
||||
MutexedQueue<ChatEvent *> outgoing_queue; // server --> chat backend
|
||||
};
|
||||
|
||||
#endif
|
@ -19,12 +19,19 @@
|
||||
#cmakedefine01 USE_CURL
|
||||
#cmakedefine01 USE_SOUND
|
||||
#cmakedefine01 USE_FREETYPE
|
||||
#cmakedefine01 USE_CURSES
|
||||
#cmakedefine01 USE_LEVELDB
|
||||
#cmakedefine01 USE_LUAJIT
|
||||
#cmakedefine01 USE_SPATIAL
|
||||
#cmakedefine01 USE_SYSTEM_GMP
|
||||
#cmakedefine01 USE_REDIS
|
||||
#cmakedefine01 HAVE_ENDIAN_H
|
||||
#cmakedefine01 CURSES_HAVE_CURSES_H
|
||||
#cmakedefine01 CURSES_HAVE_NCURSES_H
|
||||
#cmakedefine01 CURSES_HAVE_NCURSES_NCURSES_H
|
||||
#cmakedefine01 CURSES_HAVE_NCURSES_CURSES_H
|
||||
#cmakedefine01 CURSES_HAVE_NCURSESW_NCURSES_H
|
||||
#cmakedefine01 CURSES_HAVE_NCURSESW_CURSES_H
|
||||
|
||||
//freeminer:
|
||||
#cmakedefine01 USE_SQLITE3
|
||||
|
@ -40,6 +40,10 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "filesys.h"
|
||||
#endif
|
||||
|
||||
#if USE_CURSES
|
||||
#include "terminal_chat_console.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
Assert
|
||||
*/
|
||||
@ -47,6 +51,10 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
void sanity_check_fn(const char *assertion, const char *file,
|
||||
unsigned int line, const char *function)
|
||||
{
|
||||
#if USE_CURSES
|
||||
g_term_console.stopAndWaitforThread();
|
||||
#endif
|
||||
|
||||
errorstream << std::endl << "In thread " << std::hex
|
||||
<< thr_get_current_thread_id() << ":" << std::endl;
|
||||
errorstream << file << ":" << line << ": " << function
|
||||
@ -60,6 +68,10 @@ void sanity_check_fn(const char *assertion, const char *file,
|
||||
void fatal_error_fn(const char *msg, const char *file,
|
||||
unsigned int line, const char *function)
|
||||
{
|
||||
#if USE_CURSES
|
||||
g_term_console.stopAndWaitforThread();
|
||||
#endif
|
||||
|
||||
errorstream << std::endl << "In thread " << std::hex
|
||||
<< thr_get_current_thread_id() << ":" << std::endl;
|
||||
errorstream << file << ":" << line << ": " << function
|
||||
|
@ -76,7 +76,7 @@ void fm_set_default_settings(Settings *settings) {
|
||||
settings->setDefault("video_driver", "opengl");
|
||||
settings->setDefault("enable_shaders", "1");
|
||||
#endif
|
||||
settings->setDefault("chat_buffer_size", "6");
|
||||
// settings->setDefault("chat_buffer_size", "6"); // todo re-enable
|
||||
settings->setDefault("timelapse", "0");
|
||||
|
||||
// Paths
|
||||
|
16
src/log.cpp
16
src/log.cpp
@ -187,6 +187,14 @@ void Logger::addOutput(ILogOutput *out, LogLevel lev)
|
||||
m_outputs[lev].push_back(out);
|
||||
}
|
||||
|
||||
void Logger::addOutputMasked(ILogOutput *out, LogLevelMask mask)
|
||||
{
|
||||
for (size_t i = 0; i < LL_MAX; i++) {
|
||||
if (mask & LOGLEVEL_TO_MASKLEVEL(i))
|
||||
m_outputs[i].push_back(out);
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev)
|
||||
{
|
||||
assert(lev < LL_MAX);
|
||||
@ -194,15 +202,19 @@ void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev)
|
||||
m_outputs[i].push_back(out);
|
||||
}
|
||||
|
||||
void Logger::removeOutput(ILogOutput *out)
|
||||
LogLevelMask Logger::removeOutput(ILogOutput *out)
|
||||
{
|
||||
LogLevelMask ret_mask = 0;
|
||||
for (size_t i = 0; i < LL_MAX; i++) {
|
||||
std::vector<ILogOutput *>::iterator it;
|
||||
|
||||
it = std::find(m_outputs[i].begin(), m_outputs[i].end(), out);
|
||||
if (it != m_outputs[i].end())
|
||||
if (it != m_outputs[i].end()) {
|
||||
ret_mask |= LOGLEVEL_TO_MASKLEVEL(i);
|
||||
m_outputs[i].erase(it);
|
||||
}
|
||||
}
|
||||
return ret_mask;
|
||||
}
|
||||
|
||||
void Logger::setLevelSilenced(LogLevel lev, bool silenced)
|
||||
|
@ -30,6 +30,7 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "threads.h"
|
||||
#include "threading/mutex.h"
|
||||
#include "threading/mutex_auto_lock.h"
|
||||
#include "irrlichttypes.h"
|
||||
|
||||
class ILogOutput;
|
||||
|
||||
@ -43,12 +44,16 @@ enum LogLevel {
|
||||
LL_MAX,
|
||||
};
|
||||
|
||||
typedef u8 LogLevelMask;
|
||||
#define LOGLEVEL_TO_MASKLEVEL(x) (1 << x)
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
void addOutput(ILogOutput *out);
|
||||
void addOutput(ILogOutput *out, LogLevel lev);
|
||||
void addOutputMasked(ILogOutput *out, LogLevelMask mask);
|
||||
void addOutputMaxLevel(ILogOutput *out, LogLevel lev);
|
||||
void removeOutput(ILogOutput *out);
|
||||
LogLevelMask removeOutput(ILogOutput *out);
|
||||
void setLevelSilenced(LogLevel lev, bool silenced);
|
||||
|
||||
void registerThread(const std::string &name);
|
||||
|
106
src/main.cpp
106
src/main.cpp
@ -50,9 +50,14 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "httpfetch.h"
|
||||
#include "guiEngine.h"
|
||||
#include "map.h"
|
||||
#include "player.h"
|
||||
#include "fontengine.h"
|
||||
#include "gameparams.h"
|
||||
#include "database.h"
|
||||
#include "config.h"
|
||||
#if USE_CURSES
|
||||
#include "terminal_chat_console.h"
|
||||
#endif
|
||||
#ifndef SERVER
|
||||
#include "client/clientlauncher.h"
|
||||
#endif
|
||||
@ -311,9 +316,13 @@ static void set_allowed_options(OptionList *allowed_options)
|
||||
allowed_options->insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING,
|
||||
_("Set gameid (\"--gameid list\" prints available ones)"))));
|
||||
allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING,
|
||||
_("Migrate from current map backend to another (Only works when using freeminerserver or with --server)"))));
|
||||
_("Migrate from current map backend to another (Only works when using minetestserver or with --server)"))));
|
||||
|
||||
allowed_options->insert(std::make_pair("autoexit", ValueSpec(VALUETYPE_STRING,
|
||||
_("Exit after X seconds"))));
|
||||
|
||||
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
|
||||
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
||||
#ifndef SERVER
|
||||
allowed_options->insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG,
|
||||
_("Show available video modes"))));
|
||||
@ -890,25 +899,88 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
||||
if (cmd_args.exists("migrate"))
|
||||
return migrate_database(game_params, cmd_args);
|
||||
|
||||
try {
|
||||
// Create server
|
||||
Server server(game_params.world_path, game_params.game_spec, false,
|
||||
bind_addr.isIPv6());
|
||||
server.start(bind_addr);
|
||||
if (cmd_args.exists("terminal")) {
|
||||
#if USE_CURSES
|
||||
bool name_ok = true;
|
||||
std::string admin_nick = g_settings->get("name");
|
||||
|
||||
int autoexit_ = 0;
|
||||
cmd_args.getS32NoEx("autoexit", autoexit_);
|
||||
server.m_autoexit = autoexit_;
|
||||
name_ok = name_ok && !admin_nick.empty();
|
||||
name_ok = name_ok && string_allowed(admin_nick, PLAYERNAME_ALLOWED_CHARS);
|
||||
|
||||
// Run server
|
||||
if (!name_ok) {
|
||||
if (admin_nick.empty()) {
|
||||
errorstream << "No name given for admin. "
|
||||
<< "Please check your minetest.conf that it "
|
||||
<< "contains a 'name = ' to your main admin account."
|
||||
<< std::endl;
|
||||
} else {
|
||||
errorstream << "Name for admin '"
|
||||
<< admin_nick << "' is not valid. "
|
||||
<< "Please check that it only contains allowed characters. "
|
||||
<< "Valid characters are: " << PLAYERNAME_ALLOWED_CHARS_USER_EXPL
|
||||
<< std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ChatInterface iface;
|
||||
bool &kill = *porting::signal_handler_killstatus();
|
||||
dedicated_server_loop(server, kill);
|
||||
} catch (const ModError &e) {
|
||||
errorstream << "ModError: " << e.what() << std::endl;
|
||||
return false;
|
||||
} catch (const ServerError &e) {
|
||||
errorstream << "ServerError: " << e.what() << std::endl;
|
||||
return false;
|
||||
|
||||
try {
|
||||
// Create server
|
||||
Server server(game_params.world_path,
|
||||
game_params.game_spec, false, bind_addr.isIPv6(), &iface);
|
||||
|
||||
g_term_console.setup(&iface, &kill, admin_nick);
|
||||
|
||||
g_term_console.start();
|
||||
|
||||
server.start(bind_addr);
|
||||
// Run server
|
||||
dedicated_server_loop(server, kill);
|
||||
} catch (const ModError &e) {
|
||||
g_term_console.stopAndWaitforThread();
|
||||
errorstream << "ModError: " << e.what() << std::endl;
|
||||
return false;
|
||||
} catch (const ServerError &e) {
|
||||
g_term_console.stopAndWaitforThread();
|
||||
errorstream << "ServerError: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tell the console to stop, and wait for it to finish,
|
||||
// only then leave context and free iface
|
||||
g_term_console.stop();
|
||||
g_term_console.wait();
|
||||
|
||||
g_term_console.clearKillStatus();
|
||||
} else {
|
||||
#else
|
||||
errorstream << "Cmd arg --terminal passed, but "
|
||||
<< "compiled without ncurses. Ignoring." << std::endl;
|
||||
} {
|
||||
#endif
|
||||
try {
|
||||
|
||||
// Create server
|
||||
Server server(game_params.world_path, game_params.game_spec, false,
|
||||
bind_addr.isIPv6());
|
||||
server.start(bind_addr);
|
||||
|
||||
int autoexit_ = 0;
|
||||
cmd_args.getS32NoEx("autoexit", autoexit_);
|
||||
server.m_autoexit = autoexit_;
|
||||
|
||||
// Run server
|
||||
bool &kill = *porting::signal_handler_killstatus();
|
||||
dedicated_server_loop(server, kill);
|
||||
|
||||
} catch (const ModError &e) {
|
||||
errorstream << "ModError: " << e.what() << std::endl;
|
||||
return false;
|
||||
} catch (const ServerError &e) {
|
||||
errorstream << "ServerError: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1096,73 +1096,14 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
|
||||
return;
|
||||
}
|
||||
|
||||
// If something goes wrong, this player is to blame
|
||||
RollbackScopeActor rollback_scope(m_rollback,
|
||||
std::string("player:")+player->getName());
|
||||
|
||||
// Get player name of this client
|
||||
std::wstring name = narrow_to_wide(player->getName());
|
||||
std::string name = player->getName();
|
||||
std::wstring wname = narrow_to_wide(name);
|
||||
|
||||
// Run script hook
|
||||
bool ate = m_script->on_chat_message(player->getName(),
|
||||
wide_to_narrow(message));
|
||||
// If script ate the message, don't proceed
|
||||
if (ate)
|
||||
return;
|
||||
|
||||
// Line to send to players
|
||||
std::wstring line;
|
||||
// Whether to send to the player that sent the line
|
||||
bool send_to_sender_only = false;
|
||||
|
||||
// Commands are implemented in Lua, so only catch invalid
|
||||
// commands that were not "eaten" and send an error back
|
||||
if (message[0] == L'/') {
|
||||
message = message.substr(1);
|
||||
send_to_sender_only = true;
|
||||
if (message.length() == 0)
|
||||
line += L"-!- Empty command";
|
||||
else
|
||||
line += L"-!- Invalid command: " + str_split(message, L' ')[0];
|
||||
}
|
||||
else {
|
||||
if (checkPriv(player->getName(), "shout")) {
|
||||
line += L"<";
|
||||
line += name;
|
||||
line += L"> ";
|
||||
line += message;
|
||||
} else {
|
||||
line += L"-!- You don't have permission to shout.";
|
||||
send_to_sender_only = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (line != L"")
|
||||
{
|
||||
/*
|
||||
Send the message to sender
|
||||
*/
|
||||
if (send_to_sender_only) {
|
||||
SendChatMessage(pkt->getPeerId(), line);
|
||||
}
|
||||
/*
|
||||
Send the message to others
|
||||
*/
|
||||
else {
|
||||
stat.add("chat", player->getName());
|
||||
actionstream << "CHAT: " << wide_to_narrow(line)<<std::endl;
|
||||
|
||||
std::vector<u16> clients = m_clients.getClientIDs();
|
||||
|
||||
//SendChatMessage(PEER_ID_INEXISTENT, line); //dupe with mt clients
|
||||
|
||||
for (std::vector<u16>::iterator i = clients.begin();
|
||||
i != clients.end(); ++i) {
|
||||
if (*i != pkt->getPeerId())
|
||||
SendChatMessage(*i, line);
|
||||
}
|
||||
|
||||
}
|
||||
std::wstring answer_to_sender = handleChat(name, wname, message, pkt->getPeerId());
|
||||
if (!answer_to_sender.empty()) {
|
||||
// Send the answer to sender
|
||||
SendChatMessage(pkt->getPeerId(), answer_to_sender);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define PLAYERNAME_SIZE 20
|
||||
|
||||
#define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
|
||||
#define PLAYERNAME_ALLOWED_CHARS_USER_EXPL "'a' to 'z', 'A' to 'Z', '0' to '9', '-', '_'"
|
||||
|
||||
struct PlayerControl
|
||||
{
|
||||
|
@ -48,6 +48,16 @@ int ModApiServer::l_get_server_status(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// print(text)
|
||||
int ModApiServer::l_print(lua_State *L)
|
||||
{
|
||||
NO_MAP_LOCK_REQUIRED;
|
||||
std::string text;
|
||||
text = luaL_checkstring(L, 1);
|
||||
getServer(L)->printToConsoleOnly(text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// chat_send_all(text)
|
||||
int ModApiServer::l_chat_send_all(lua_State *L)
|
||||
{
|
||||
@ -508,6 +518,8 @@ void ModApiServer::Initialize(lua_State *L, int top)
|
||||
API_FCT(get_modpath);
|
||||
API_FCT(get_modnames);
|
||||
|
||||
API_FCT(print);
|
||||
|
||||
API_FCT(chat_send_all);
|
||||
API_FCT(chat_send_player);
|
||||
API_FCT(show_formspec);
|
||||
|
@ -49,6 +49,9 @@ private:
|
||||
// the returned list is sorted alphabetically for you
|
||||
static int l_get_modnames(lua_State *L);
|
||||
|
||||
// print(text)
|
||||
static int l_print(lua_State *L);
|
||||
|
||||
// chat_send_all(text)
|
||||
static int l_chat_send_all(lua_State *L);
|
||||
|
||||
|
@ -41,7 +41,7 @@ private:
|
||||
// log([level,] text)
|
||||
// Writes a line to the logger.
|
||||
// The one-argument version logs to infostream.
|
||||
// The two-argument version accept a log level: error, action, info, or verbose.
|
||||
// The two-argument version accepts a log level.
|
||||
static int l_log(lua_State *L);
|
||||
|
||||
// get us precision time
|
||||
|
152
src/server.cpp
152
src/server.cpp
@ -189,7 +189,8 @@ Server::Server(
|
||||
const std::string &path_world,
|
||||
const SubgameSpec &gamespec,
|
||||
bool simple_singleplayer_mode,
|
||||
bool ipv6
|
||||
bool ipv6,
|
||||
ChatInterface *iface
|
||||
):
|
||||
m_path_world(path_world),
|
||||
m_gamespec(gamespec),
|
||||
@ -222,6 +223,7 @@ Server::Server(
|
||||
m_clients(&m_con),
|
||||
m_shutdown_requested(false),
|
||||
m_shutdown_ask_reconnect(false),
|
||||
m_admin_chat(iface),
|
||||
m_ignore_map_edit_events(false),
|
||||
m_ignore_map_edit_events_peer_id(0),
|
||||
m_next_sound_id(0)
|
||||
@ -746,6 +748,36 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
Listen to the admin chat, if available
|
||||
*/
|
||||
if (m_admin_chat) {
|
||||
if (!m_admin_chat->command_queue.empty()) {
|
||||
MutexAutoLock lock(m_env_mutex);
|
||||
while (!m_admin_chat->command_queue.empty()) {
|
||||
ChatEvent *evt = m_admin_chat->command_queue.pop_frontNoEx();
|
||||
if (evt->type == CET_NICK_ADD) {
|
||||
// The terminal informed us of its nick choice
|
||||
m_admin_nick = ((ChatEventNick *)evt)->nick;
|
||||
if (!m_script->getAuth(m_admin_nick, NULL, NULL)) {
|
||||
errorstream << "You haven't set up an account." << std::endl
|
||||
<< "Please log in using the client as '"
|
||||
<< m_admin_nick << "' with a secure password." << std::endl
|
||||
<< "Until then, you can't execute admin tasks via the console," << std::endl
|
||||
<< "and everybody can claim the user account instead of you," << std::endl
|
||||
<< "giving them full control over this server." << std::endl;
|
||||
}
|
||||
} else {
|
||||
assert(evt->type == CET_CHAT);
|
||||
handleAdminChat((ChatEventChat *)evt);
|
||||
}
|
||||
delete evt;
|
||||
}
|
||||
}
|
||||
m_admin_chat->outgoing_queue.push_back(
|
||||
new ChatEventTimeInfo(m_env->getGameTime(), m_env->getTimeOfDay()));
|
||||
}
|
||||
|
||||
/*
|
||||
Do background stuff
|
||||
*/
|
||||
@ -1398,6 +1430,26 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id)
|
||||
if(!m_simple_singleplayer_mode) {
|
||||
// Send information about server to player in chat
|
||||
SendChatMessage(peer_id, getStatusString());
|
||||
|
||||
// Send information about joining in chat
|
||||
{
|
||||
std::string name = "unknown";
|
||||
Player *player = m_env->getPlayer(peer_id);
|
||||
if(player != NULL)
|
||||
name = player->getName();
|
||||
|
||||
/*
|
||||
std::wstring message;
|
||||
message += L"*** ";
|
||||
message += narrow_to_wide(name);
|
||||
message += L" joined the game.";
|
||||
SendChatMessage(PEER_ID_INEXISTENT,message);
|
||||
*/
|
||||
if (m_admin_chat)
|
||||
m_admin_chat->outgoing_queue.push_back(
|
||||
new ChatEventNick(CET_NICK_ADD, name));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1728,6 +1780,16 @@ void Server::handlePeerChanges()
|
||||
}
|
||||
}
|
||||
|
||||
void Server::printToConsoleOnly(const std::string &text)
|
||||
{
|
||||
if (m_admin_chat) {
|
||||
m_admin_chat->outgoing_queue.push_back(
|
||||
new ChatEventChat("", utf8_to_wide(text)));
|
||||
} else {
|
||||
std::cout << text;
|
||||
}
|
||||
}
|
||||
|
||||
#if MINETEST_PROTO
|
||||
void Server::Send(NetworkPacket* pkt)
|
||||
{
|
||||
@ -3018,9 +3080,13 @@ void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason)
|
||||
os<<player->getName()<<" ";
|
||||
}
|
||||
|
||||
actionstream<<player->getName()<<" "
|
||||
<<(reason==CDR_TIMEOUT?"times out.":"leaves game.")
|
||||
<<" List of players: "<<os.str()<<std::endl;
|
||||
std::string name = player->getName();
|
||||
actionstream << name << " "
|
||||
<< (reason == CDR_TIMEOUT ? "times out." : "leaves game.")
|
||||
<< " List of players: " << os.str() << std::endl;
|
||||
if (m_admin_chat)
|
||||
m_admin_chat->outgoing_queue.push_back(
|
||||
new ChatEventNick(CET_NICK_REMOVE, name));
|
||||
}
|
||||
}
|
||||
{
|
||||
@ -3053,6 +3119,77 @@ void Server::UpdateCrafting(Player* player)
|
||||
plist->changeItem(0, preview);
|
||||
}
|
||||
|
||||
std::wstring Server::handleChat(const std::string &name, const std::wstring &wname,
|
||||
const std::wstring &wmessage, u16 peer_id_to_avoid_sending)
|
||||
{
|
||||
// If something goes wrong, this player is to blame
|
||||
RollbackScopeActor rollback_scope(m_rollback,
|
||||
std::string("player:") + name);
|
||||
|
||||
// Line to send
|
||||
std::wstring line;
|
||||
// Whether to send line to the player that sent the message, or to all players
|
||||
bool broadcast_line = true;
|
||||
|
||||
// Run script hook
|
||||
bool ate = m_script->on_chat_message(name,
|
||||
wide_to_utf8(wmessage));
|
||||
// If script ate the message, don't proceed
|
||||
if (ate)
|
||||
return L"";
|
||||
|
||||
// Commands are implemented in Lua, so only catch invalid
|
||||
// commands that were not "eaten" and send an error back
|
||||
if (wmessage[0] == L'/') {
|
||||
std::wstring wcmd = wmessage.substr(1);
|
||||
broadcast_line = false;
|
||||
if (wcmd.length() == 0)
|
||||
line += L"-!- Empty command";
|
||||
else
|
||||
line += L"-!- Invalid command: " + str_split(wcmd, L' ')[0];
|
||||
} else {
|
||||
line += L"<";
|
||||
line += wname;
|
||||
line += L"> ";
|
||||
line += wmessage;
|
||||
}
|
||||
|
||||
/*
|
||||
Tell calling method to send the message to sender
|
||||
*/
|
||||
if (!broadcast_line) {
|
||||
return line;
|
||||
} else {
|
||||
/*
|
||||
Send the message to others
|
||||
*/
|
||||
actionstream << "CHAT: " << wide_to_narrow(line) << std::endl;
|
||||
|
||||
std::vector<u16> clients = m_clients.getClientIDs();
|
||||
|
||||
for (u16 i = 0; i < clients.size(); i++) {
|
||||
u16 cid = clients[i];
|
||||
if (cid != peer_id_to_avoid_sending)
|
||||
SendChatMessage(cid, line);
|
||||
}
|
||||
}
|
||||
return L"";
|
||||
}
|
||||
|
||||
void Server::handleAdminChat(const ChatEventChat *evt)
|
||||
{
|
||||
std::string name = evt->nick;
|
||||
std::wstring wname = utf8_to_wide(name);
|
||||
std::wstring wmessage = evt->evt_msg;
|
||||
|
||||
std::wstring answer = handleChat(name, wname, wmessage);
|
||||
|
||||
// If asked to send answer to sender
|
||||
if (!answer.empty()) {
|
||||
m_admin_chat->outgoing_queue.push_back(new ChatEventChat("", answer));
|
||||
}
|
||||
}
|
||||
|
||||
RemoteClient* Server::getClient(u16 peer_id, ClientState state_min)
|
||||
{
|
||||
RemoteClient *client = getClientNoEx(peer_id,state_min);
|
||||
@ -3186,9 +3323,14 @@ void Server::notifyPlayer(const char *name, const std::string &msg)
|
||||
if (!m_env)
|
||||
return;
|
||||
|
||||
if (m_admin_nick == name && !m_admin_nick.empty()) {
|
||||
m_admin_chat->outgoing_queue.push_back(new ChatEventChat("", utf8_to_wide(msg)));
|
||||
}
|
||||
|
||||
Player *player = m_env->getPlayer(name);
|
||||
if (!player)
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player->peer_id == PEER_ID_INEXISTENT)
|
||||
return;
|
||||
|
15
src/server.h
15
src/server.h
@ -35,6 +35,7 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "util/numeric.h"
|
||||
#include "util/thread.h"
|
||||
#include "environment.h"
|
||||
#include "chat_interface.h"
|
||||
#include "clientiface.h"
|
||||
#include "network/networkpacket.h"
|
||||
#include <string>
|
||||
@ -182,7 +183,8 @@ public:
|
||||
const std::string &path_world,
|
||||
const SubgameSpec &gamespec,
|
||||
bool simple_singleplayer_mode,
|
||||
bool ipv6
|
||||
bool ipv6,
|
||||
ChatInterface *iface = NULL
|
||||
);
|
||||
~Server();
|
||||
void start(Address bind_addr);
|
||||
@ -391,6 +393,8 @@ public:
|
||||
u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch,
|
||||
std::string* vers_string);
|
||||
|
||||
void printToConsoleOnly(const std::string &text);
|
||||
|
||||
void SendPlayerHPOrDie(PlayerSAO *player);
|
||||
void SendPlayerBreath(u16 peer_id);
|
||||
void SendInventory(PlayerSAO* playerSAO);
|
||||
@ -507,6 +511,12 @@ private:
|
||||
void DeleteClient(u16 peer_id, ClientDeletionReason reason);
|
||||
void UpdateCrafting(Player *player);
|
||||
|
||||
// This returns the answer to the sender of wmessage, or "" if there is none
|
||||
std::wstring handleChat(const std::string &name, const std::wstring &wname,
|
||||
const std::wstring &wmessage,
|
||||
u16 peer_id_to_avoid_sending = PEER_ID_INEXISTENT);
|
||||
void handleAdminChat(const ChatEventChat *evt);
|
||||
|
||||
v3f findSpawnPos();
|
||||
|
||||
// When called, connection mutex should be locked
|
||||
@ -650,6 +660,9 @@ private:
|
||||
std::string m_shutdown_msg;
|
||||
bool m_shutdown_ask_reconnect;
|
||||
|
||||
ChatInterface *m_admin_chat;
|
||||
std::string m_admin_nick;
|
||||
|
||||
/*
|
||||
Map edit event queue. Automatically receives all map edits.
|
||||
The constructor of this class registers us to receive them through
|
||||
|
453
src/terminal_chat_console.cpp
Normal file
453
src/terminal_chat_console.cpp
Normal file
@ -0,0 +1,453 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2015 est31 <MTest31@outlook.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser 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 "config.h"
|
||||
#if USE_CURSES
|
||||
#include "version.h"
|
||||
#include "terminal_chat_console.h"
|
||||
#include "porting.h"
|
||||
#include "settings.h"
|
||||
#include "util/numeric.h"
|
||||
#include "util/string.h"
|
||||
|
||||
TerminalChatConsole g_term_console;
|
||||
|
||||
// include this last to avoid any conflicts
|
||||
// (likes to set macros to common names, conflicting various stuff)
|
||||
#if CURSES_HAVE_NCURSESW_NCURSES_H
|
||||
#include <ncursesw/ncurses.h>
|
||||
#elif CURSES_HAVE_NCURSESW_CURSES_H
|
||||
#include <ncursesw/curses.h>
|
||||
#elif CURSES_HAVE_CURSES_H
|
||||
#include <curses.h>
|
||||
#elif CURSES_HAVE_NCURSES_H
|
||||
#include <ncurses.h>
|
||||
#elif CURSES_HAVE_NCURSES_NCURSES_H
|
||||
#include <ncurses/ncurses.h>
|
||||
#elif CURSES_HAVE_NCURSES_CURSES_H
|
||||
#include <ncurses/curses.h>
|
||||
#endif
|
||||
|
||||
// Some functions to make drawing etc position independent
|
||||
static bool reformat_backend(ChatBackend *backend, int rows, int cols)
|
||||
{
|
||||
if (rows < 2)
|
||||
return false;
|
||||
backend->reformat(cols, rows - 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void move_for_backend(int row, int col)
|
||||
{
|
||||
move(row + 1, col);
|
||||
}
|
||||
|
||||
void TerminalChatConsole::initOfCurses()
|
||||
{
|
||||
initscr();
|
||||
cbreak(); //raw();
|
||||
noecho();
|
||||
keypad(stdscr, TRUE);
|
||||
nodelay(stdscr, TRUE);
|
||||
timeout(100);
|
||||
|
||||
// To make esc not delay up to one second. According to the internet,
|
||||
// this is the value vim uses, too.
|
||||
set_escdelay(25);
|
||||
|
||||
getmaxyx(stdscr, m_rows, m_cols);
|
||||
m_can_draw_text = reformat_backend(&m_chat_backend, m_rows, m_cols);
|
||||
}
|
||||
|
||||
void TerminalChatConsole::deInitOfCurses()
|
||||
{
|
||||
endwin();
|
||||
}
|
||||
|
||||
void *TerminalChatConsole::run()
|
||||
{
|
||||
BEGIN_DEBUG_EXCEPTION_HANDLER
|
||||
|
||||
std::cout << "========================" << std::endl;
|
||||
std::cout << "Begin log output over terminal"
|
||||
<< " (no stdout/stderr backlog during that)" << std::endl;
|
||||
// Make the loggers to stdout/stderr shut up.
|
||||
// Go over our own loggers instead.
|
||||
LogLevelMask err_mask = g_logger.removeOutput(&stderr_output);
|
||||
LogLevelMask out_mask = g_logger.removeOutput(&stdout_output);
|
||||
|
||||
g_logger.addOutput(&m_log_output);
|
||||
|
||||
// Inform the server of our nick
|
||||
m_chat_interface->command_queue.push_back(
|
||||
new ChatEventNick(CET_NICK_ADD, m_nick));
|
||||
|
||||
{
|
||||
// Ensures that curses is deinitialized even on an exception being thrown
|
||||
CursesInitHelper helper(this);
|
||||
|
||||
while (!stopRequested()) {
|
||||
|
||||
int ch = getch();
|
||||
if (stopRequested())
|
||||
break;
|
||||
|
||||
step(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_kill_requested)
|
||||
*m_kill_requested = true;
|
||||
|
||||
g_logger.removeOutput(&m_log_output);
|
||||
g_logger.addOutputMasked(&stderr_output, err_mask);
|
||||
g_logger.addOutputMasked(&stdout_output, out_mask);
|
||||
|
||||
std::cout << "End log output over terminal"
|
||||
<< " (no stdout/stderr backlog during that)" << std::endl;
|
||||
std::cout << "========================" << std::endl;
|
||||
|
||||
END_DEBUG_EXCEPTION_HANDLER
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TerminalChatConsole::typeChatMessage(const std::wstring &msg)
|
||||
{
|
||||
// Discard empty line
|
||||
if (msg.empty())
|
||||
return;
|
||||
|
||||
// Send to server
|
||||
m_chat_interface->command_queue.push_back(
|
||||
new ChatEventChat(m_nick, msg));
|
||||
|
||||
// Print if its a command (gets eaten by server otherwise)
|
||||
if (msg[0] == L'/') {
|
||||
m_chat_backend.addMessage(L"", (std::wstring)L"Issued command: " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalChatConsole::handleInput(int ch, bool &complete_redraw_needed)
|
||||
{
|
||||
// Helpful if you want to collect key codes that aren't documented
|
||||
/*if (ch != ERR) {
|
||||
m_chat_backend.addMessage(L"",
|
||||
(std::wstring)L"Pressed key " + utf8_to_wide(
|
||||
std::string(keyname(ch)) + " (code " + itos(ch) + ")"));
|
||||
complete_redraw_needed = true;
|
||||
}//*/
|
||||
|
||||
// All the key codes below are compatible to xterm
|
||||
// Only add new ones if you have tried them there,
|
||||
// to ensure compatibility with not just xterm but the wide
|
||||
// range of terminals that are compatible to xterm.
|
||||
|
||||
switch (ch) {
|
||||
case ERR: // no input
|
||||
break;
|
||||
case 27: // ESC
|
||||
// Toggle ESC mode
|
||||
m_esc_mode = !m_esc_mode;
|
||||
break;
|
||||
case KEY_PPAGE:
|
||||
m_chat_backend.scrollPageUp();
|
||||
complete_redraw_needed = true;
|
||||
break;
|
||||
case KEY_NPAGE:
|
||||
m_chat_backend.scrollPageDown();
|
||||
complete_redraw_needed = true;
|
||||
break;
|
||||
case KEY_ENTER:
|
||||
case '\r':
|
||||
case '\n': {
|
||||
std::wstring text = m_chat_backend.getPrompt().submit();
|
||||
typeChatMessage(text);
|
||||
break;
|
||||
}
|
||||
case KEY_UP:
|
||||
m_chat_backend.getPrompt().historyPrev();
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
m_chat_backend.getPrompt().historyNext();
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
// Left pressed
|
||||
// move character to the left
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case 545:
|
||||
// Ctrl-Left pressed
|
||||
// move word to the left
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD);
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
// Right pressed
|
||||
// move character to the right
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case 560:
|
||||
// Ctrl-Right pressed
|
||||
// move word to the right
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD);
|
||||
break;
|
||||
case 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);
|
||||
break;
|
||||
case 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);
|
||||
break;
|
||||
case KEY_BACKSPACE:
|
||||
case '\b':
|
||||
case 127:
|
||||
// Backspace pressed
|
||||
// delete character to the left
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case KEY_DC:
|
||||
// Delete pressed
|
||||
// delete character to the right
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case 519:
|
||||
// Ctrl-Delete pressed
|
||||
// delete word to the right
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD);
|
||||
break;
|
||||
case 21:
|
||||
// Ctrl-U pressed
|
||||
// kill line to left end
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
break;
|
||||
case 11:
|
||||
// Ctrl-K pressed
|
||||
// kill line to right end
|
||||
m_chat_backend.getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
break;
|
||||
case KEY_TAB:
|
||||
// Tab pressed
|
||||
// Nick completion
|
||||
m_chat_backend.getPrompt().nickCompletion(m_nicks, false);
|
||||
break;
|
||||
default:
|
||||
// Add character to the prompt,
|
||||
// assuming UTF-8.
|
||||
if (IS_UTF8_MULTB_START(ch)) {
|
||||
m_pending_utf8_bytes.append(1, (char)ch);
|
||||
m_utf8_bytes_to_wait += UTF8_MULTB_START_LEN(ch) - 1;
|
||||
} else if (m_utf8_bytes_to_wait != 0) {
|
||||
m_pending_utf8_bytes.append(1, (char)ch);
|
||||
m_utf8_bytes_to_wait--;
|
||||
if (m_utf8_bytes_to_wait == 0) {
|
||||
std::wstring w = utf8_to_wide(m_pending_utf8_bytes);
|
||||
m_pending_utf8_bytes = "";
|
||||
// hopefully only one char in the wstring...
|
||||
for (size_t i = 0; i < w.size(); i++) {
|
||||
m_chat_backend.getPrompt().input(w.c_str()[i]);
|
||||
}
|
||||
}
|
||||
} else if (IS_ASCII_PRINTABLE_CHAR(ch)) {
|
||||
m_chat_backend.getPrompt().input(ch);
|
||||
} else {
|
||||
// Silently ignore characters we don't handle
|
||||
|
||||
//warningstream << "Pressed invalid character '"
|
||||
// << keyname(ch) << "' (code " << itos(ch) << ")" << std::endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalChatConsole::step(int ch)
|
||||
{
|
||||
bool complete_redraw_needed = false;
|
||||
|
||||
// empty queues
|
||||
while (!m_chat_interface->outgoing_queue.empty()) {
|
||||
ChatEvent *evt = m_chat_interface->outgoing_queue.pop_frontNoEx();
|
||||
switch (evt->type) {
|
||||
case CET_NICK_REMOVE:
|
||||
m_nicks.remove(((ChatEventNick *)evt)->nick);
|
||||
break;
|
||||
case CET_NICK_ADD:
|
||||
m_nicks.push_back(((ChatEventNick *)evt)->nick);
|
||||
break;
|
||||
case CET_CHAT:
|
||||
complete_redraw_needed = true;
|
||||
// This is only used for direct replies from commands
|
||||
// or for lua's print() functionality
|
||||
m_chat_backend.addMessage(L"", ((ChatEventChat *)evt)->evt_msg);
|
||||
break;
|
||||
case CET_TIME_INFO:
|
||||
ChatEventTimeInfo *tevt = (ChatEventTimeInfo *)evt;
|
||||
m_game_time = tevt->game_time;
|
||||
m_time_of_day = tevt->time;
|
||||
};
|
||||
delete evt;
|
||||
}
|
||||
while (!m_log_output.queue.empty()) {
|
||||
complete_redraw_needed = true;
|
||||
std::pair<LogLevel, std::string> p = m_log_output.queue.pop_frontNoEx();
|
||||
if (p.first > m_log_level)
|
||||
continue;
|
||||
|
||||
m_chat_backend.addMessage(
|
||||
utf8_to_wide(Logger::getLevelLabel(p.first)),
|
||||
utf8_to_wide(p.second));
|
||||
}
|
||||
|
||||
// handle input
|
||||
if (!m_esc_mode) {
|
||||
handleInput(ch, complete_redraw_needed);
|
||||
} else {
|
||||
switch (ch) {
|
||||
case ERR: // no input
|
||||
break;
|
||||
case 27: // ESC
|
||||
// Toggle ESC mode
|
||||
m_esc_mode = !m_esc_mode;
|
||||
break;
|
||||
case 'L':
|
||||
m_log_level--;
|
||||
m_log_level = MYMAX(m_log_level, LL_NONE + 1); // LL_NONE isn't accessible
|
||||
break;
|
||||
case 'l':
|
||||
m_log_level++;
|
||||
m_log_level = MYMIN(m_log_level, LL_MAX - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// was there a resize?
|
||||
int xn, yn;
|
||||
getmaxyx(stdscr, yn, xn);
|
||||
if (xn != m_cols || yn != m_rows) {
|
||||
m_cols = xn;
|
||||
m_rows = yn;
|
||||
m_can_draw_text = reformat_backend(&m_chat_backend, m_rows, m_cols);
|
||||
complete_redraw_needed = true;
|
||||
}
|
||||
|
||||
// draw title
|
||||
move(0, 0);
|
||||
clrtoeol();
|
||||
addstr(PROJECT_NAME_C);
|
||||
addstr(" ");
|
||||
addstr(g_version_hash);
|
||||
|
||||
u32 minutes = m_time_of_day % 1000;
|
||||
u32 hours = m_time_of_day / 1000;
|
||||
minutes = (float)minutes / 1000 * 60;
|
||||
|
||||
if (m_game_time)
|
||||
printw(" | Game %d Time of day %02d:%02d ",
|
||||
m_game_time, hours, minutes);
|
||||
|
||||
// draw text
|
||||
if (complete_redraw_needed && m_can_draw_text)
|
||||
draw_text();
|
||||
|
||||
// draw prompt
|
||||
if (!m_esc_mode) {
|
||||
// normal prompt
|
||||
ChatPrompt& prompt = m_chat_backend.getPrompt();
|
||||
std::string prompt_text = wide_to_utf8(prompt.getVisiblePortion());
|
||||
move(m_rows - 1, 0);
|
||||
clrtoeol();
|
||||
addstr(prompt_text.c_str());
|
||||
// Draw cursor
|
||||
s32 cursor_pos = prompt.getVisibleCursorPosition();
|
||||
if (cursor_pos >= 0) {
|
||||
move(m_rows - 1, cursor_pos);
|
||||
}
|
||||
} else {
|
||||
// esc prompt
|
||||
move(m_rows - 1, 0);
|
||||
clrtoeol();
|
||||
printw("[ESC] Toggle ESC mode |"
|
||||
" [CTRL+C] Shut down |"
|
||||
" (L) in-, (l) decrease loglevel %s",
|
||||
Logger::getLevelLabel((LogLevel) m_log_level).c_str());
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void TerminalChatConsole::draw_text()
|
||||
{
|
||||
ChatBuffer& buf = m_chat_backend.getConsoleBuffer();
|
||||
for (u32 row = 0; row < buf.getRows(); row++) {
|
||||
move_for_backend(row, 0);
|
||||
clrtoeol();
|
||||
const ChatFormattedLine& line = buf.getFormattedLine(row);
|
||||
if (line.fragments.empty())
|
||||
continue;
|
||||
for (u32 i = 0; i < line.fragments.size(); ++i) {
|
||||
const ChatFormattedFragment& fragment = line.fragments[i];
|
||||
//addstr(wide_to_utf8(fragment.text).c_str());
|
||||
addstr(wide_to_utf8(fragment.text.getString()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalChatConsole::stopAndWaitforThread()
|
||||
{
|
||||
clearKillStatus();
|
||||
stop();
|
||||
wait();
|
||||
}
|
||||
|
||||
#endif
|
131
src/terminal_chat_console.h
Normal file
131
src/terminal_chat_console.h
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2015 est31 <MTest31@outlook.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser 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 TERMINAL_CHAT_CONSOLE_H
|
||||
#define TERMINAL_CHAT_CONSOLE_H
|
||||
|
||||
#include "chat.h"
|
||||
#include "threading/thread.h"
|
||||
#include "chat_interface.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
class TermLogOutput : public ILogOutput {
|
||||
public:
|
||||
|
||||
void logRaw(LogLevel lev, const std::string &line)
|
||||
{
|
||||
queue.push_back(std::make_pair(lev, line));
|
||||
}
|
||||
|
||||
virtual void log(LogLevel lev, const std::string &combined,
|
||||
const std::string &time, const std::string &thread_name,
|
||||
const std::string &payload_text)
|
||||
{
|
||||
std::ostringstream os(std::ios_base::binary);
|
||||
os << time << ": [" << thread_name << "] " << payload_text;
|
||||
|
||||
queue.push_back(std::make_pair(lev, os.str()));
|
||||
}
|
||||
|
||||
MutexedQueue<std::pair<LogLevel, std::string> > queue;
|
||||
};
|
||||
|
||||
class TerminalChatConsole : public Thread {
|
||||
public:
|
||||
|
||||
TerminalChatConsole() :
|
||||
Thread("TerminalThread"),
|
||||
m_log_level(LL_ACTION),
|
||||
m_utf8_bytes_to_wait(0),
|
||||
m_kill_requested(NULL),
|
||||
m_esc_mode(false),
|
||||
m_game_time(0),
|
||||
m_time_of_day(0)
|
||||
{}
|
||||
|
||||
void setup(
|
||||
ChatInterface *iface,
|
||||
bool *kill_requested,
|
||||
const std::string &nick)
|
||||
{
|
||||
m_nick = nick;
|
||||
m_kill_requested = kill_requested;
|
||||
m_chat_interface = iface;
|
||||
}
|
||||
|
||||
virtual void *run();
|
||||
|
||||
// Highly required!
|
||||
void clearKillStatus() { m_kill_requested = NULL; }
|
||||
|
||||
void stopAndWaitforThread();
|
||||
|
||||
private:
|
||||
// these have stupid names so that nobody missclassifies them
|
||||
// as curses functions. Oh, curses has stupid names too?
|
||||
// Well, at least it was worth a try...
|
||||
void initOfCurses();
|
||||
void deInitOfCurses();
|
||||
|
||||
void draw_text();
|
||||
|
||||
void typeChatMessage(const std::wstring &m);
|
||||
|
||||
void handleInput(int ch, bool &complete_redraw_needed);
|
||||
|
||||
void step(int ch);
|
||||
|
||||
// Used to ensure the deinitialisation is always called.
|
||||
struct CursesInitHelper {
|
||||
TerminalChatConsole *cons;
|
||||
CursesInitHelper(TerminalChatConsole * a_console)
|
||||
: cons(a_console)
|
||||
{ cons->initOfCurses(); }
|
||||
~CursesInitHelper() { cons->deInitOfCurses(); }
|
||||
};
|
||||
|
||||
int m_log_level;
|
||||
std::string m_nick;
|
||||
|
||||
u8 m_utf8_bytes_to_wait;
|
||||
std::string m_pending_utf8_bytes;
|
||||
|
||||
std::list<std::string> m_nicks;
|
||||
|
||||
int m_cols;
|
||||
int m_rows;
|
||||
bool m_can_draw_text;
|
||||
|
||||
bool *m_kill_requested;
|
||||
ChatBackend m_chat_backend;
|
||||
ChatInterface *m_chat_interface;
|
||||
|
||||
TermLogOutput m_log_output;
|
||||
|
||||
bool m_esc_mode;
|
||||
|
||||
u64 m_game_time;
|
||||
u32 m_time_of_day;
|
||||
};
|
||||
|
||||
extern TerminalChatConsole g_term_console;
|
||||
|
||||
#endif
|
@ -43,6 +43,7 @@ public:
|
||||
void testStrToIntConversion();
|
||||
void testStringReplace();
|
||||
void testStringAllowed();
|
||||
void testAsciiPrintableHelper();
|
||||
void testUTF8();
|
||||
void testWrapRows();
|
||||
void testIsNumber();
|
||||
@ -68,6 +69,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
|
||||
TEST(testStrToIntConversion);
|
||||
TEST(testStringReplace);
|
||||
TEST(testStringAllowed);
|
||||
TEST(testAsciiPrintableHelper);
|
||||
TEST(testUTF8);
|
||||
TEST(testWrapRows);
|
||||
TEST(testIsNumber);
|
||||
@ -232,6 +234,18 @@ void TestUtilities::testStringAllowed()
|
||||
UASSERT(string_allowed_blacklist("hello123", "123") == false);
|
||||
}
|
||||
|
||||
void TestUtilities::testAsciiPrintableHelper()
|
||||
{
|
||||
UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
|
||||
UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);
|
||||
|
||||
// Ensures that there is no cutting off going on...
|
||||
// If there were, 331 would be cut to 75 in this example
|
||||
// and 73 is a valid ASCII char.
|
||||
int ch = 331;
|
||||
UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
|
||||
}
|
||||
|
||||
void TestUtilities::testUTF8()
|
||||
{
|
||||
UASSERT(wide_to_utf8(utf8_to_wide("")) == "");
|
||||
|
@ -36,8 +36,26 @@ along with Freeminer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
|
||||
// Checks whether a value is an ASCII printable character
|
||||
#define IS_ASCII_PRINTABLE_CHAR(x) \
|
||||
(((unsigned int)(x) >= 0x20) && \
|
||||
( (unsigned int)(x) <= 0x7e))
|
||||
|
||||
// Checks whether a byte is an inner byte for an utf-8 multibyte sequence
|
||||
#define IS_UTF8_MULTB_INNER(x) (((unsigned char)x >= 0x80) && ((unsigned char)x < 0xc0))
|
||||
#define IS_UTF8_MULTB_INNER(x) \
|
||||
(((unsigned char)(x) >= 0x80) && \
|
||||
( (unsigned char)(x) <= 0xbf))
|
||||
|
||||
// Checks whether a byte is a start byte for an utf-8 multibyte sequence
|
||||
#define IS_UTF8_MULTB_START(x) \
|
||||
(((unsigned char)(x) >= 0xc2) && \
|
||||
( (unsigned char)(x) <= 0xf4))
|
||||
|
||||
// Given a start byte x for an utf-8 multibyte sequence
|
||||
// it gives the length of the whole sequence in bytes.
|
||||
#define UTF8_MULTB_START_LEN(x) \
|
||||
(((unsigned char)(x) < 0xe0) ? 2 : \
|
||||
(((unsigned char)(x) < 0xf0) ? 3 : 4))
|
||||
|
||||
typedef std::map<std::string, std::string> StringMap;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user