Merge remote-tracking branch 'minetest/master'

This commit is contained in:
proller 2015-11-08 00:23:15 +03:00
commit 52fe7de610
26 changed files with 1273 additions and 130 deletions

View File

@ -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

View File

@ -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

View 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
)

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)
{
}

View File

@ -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
View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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
{

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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

View 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
View 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

View File

@ -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("")) == "");

View File

@ -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;