!/mods/minetest/mods_here.txt
/worlds
/world/
-/clientmods/*
-!/clientmods/preview/
/client/mod_storage/
+/clientmods/*
+!/clientmods/mods_here.txt
## Configuration/log files
minetest.conf
compile_commands.json
*.apk
*.zip
+ # Visual Studio
+ *.vcxproj*
+ *.sln
+ .vs/
# Optional user provided library folder
lib/irrlichtmt
+
+ # Generated mod storage database
+ client/mod_storage.sqlite
# This can be read from ${PROJECT_NAME} after project() is called
project(minetest)
-set(PROJECT_NAME_CAPITALIZED "Minetest")
+set(PROJECT_NAME_CAPITALIZED "Dragonfire")
- set(CMAKE_CXX_STANDARD 11)
- set(GCC_MINIMUM_VERSION "4.8")
- set(CLANG_MINIMUM_VERSION "3.4")
+ set(CMAKE_CXX_STANDARD 14)
+ set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
+ set(GCC_MINIMUM_VERSION "5.1")
+ set(CLANG_MINIMUM_VERSION "3.5")
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
set(VERSION_MAJOR 5)
- set(VERSION_MINOR 5)
+ set(VERSION_MINOR 6)
set(VERSION_PATCH 0)
-set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
+set(VERSION_EXTRA "dragonfire" CACHE STRING "Stuff to append to version string")
# Change to false for releases
-set(DEVELOPMENT_BUILD TRUE)
+set(DEVELOPMENT_BUILD FALSE)
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
if(VERSION_EXTRA)
- set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA})
+ set(VERSION_STRING "${VERSION_STRING}-${VERSION_EXTRA}")
elseif(DEVELOPMENT_BUILD)
set(VERSION_STRING "${VERSION_STRING}-dev")
endif()
set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
set(BUILD_SERVER FALSE CACHE BOOL "Build server")
set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
-
+ set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
+ set(IRRLICHTMT_BUILD_DIR "" CACHE PATH "Path to IrrlichtMt build directory.")
+ if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
+ find_package(IrrlichtMt QUIET
+ PATHS "${IRRLICHTMT_BUILD_DIR}"
+ NO_DEFAULT_PATH
+ )
+
+ if(NOT TARGET IrrlichtMt::IrrlichtMt)
+ # find_package() searches certain subdirectories. ${PATH}/cmake is not
+ # the only one, but it is the one where IrrlichtMt is supposed to export
+ # IrrlichtMtConfig.cmake
+ message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
+ endif()
# This is done here so that relative search paths are more reasonable
- if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
+ elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
if(BUILD_CLIENT)
# tell IrrlichtMt to create a static library
# Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}")
- else()
- message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
endif()
endif()
+ if(TARGET IrrlichtMt::IrrlichtMt)
+ message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
+ endif()
+
# Installation
set(ICONDIR "unix/icons")
set(LOCALEDIR "locale")
else()
- set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}")
- set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
- set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
- set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
+ include(GNUInstallDirs)
+ set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}")
+ set(BINDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
+ set(DOCDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DOCDIR}")
+ set(MANDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}")
set(EXAMPLE_CONF_DIR ${DOCDIR})
- set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications")
- set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/metainfo")
- set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons")
- set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale")
+ set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications")
+ set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
+ set(ICONDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons")
+ set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}")
endif()
endif()
install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6")
install(FILES "misc/net.minetest.minetest.desktop" DESTINATION "${XDG_APPS_DIR}")
install(FILES "misc/net.minetest.minetest.appdata.xml" DESTINATION "${APPDATADIR}")
- install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
- install(FILES "misc/minetest-xorg-icon-128.png"
+ install(FILES "misc/dragonfire.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
+ install(FILES "misc/dragonfire-xorg-icon-128.png"
DESTINATION "${ICONDIR}/hicolor/128x128/apps"
- RENAME "minetest.png")
+ RENAME "dragonfire.png")
endif()
if(APPLE)
- install(FILES "misc/minetest-icon.icns" DESTINATION "${SHAREDIR}")
+ install(FILES "misc/dragonfire-icon.icns" DESTINATION "${SHAREDIR}")
install(FILES "misc/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
endif()
find_package(GMP REQUIRED)
find_package(Json REQUIRED)
find_package(Lua REQUIRED)
-
- # JsonCpp doesn't compile well on GCC 4.8
- if(NOT USE_SYSTEM_JSONCPP)
- set(GCC_MINIMUM_VERSION "4.9")
+ if(NOT USE_LUAJIT)
+ set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop)
+ set(LUA_BIT_LIBRARY bitop)
+ add_subdirectory(lib/bitop)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
endif()
endif()
+ if(BUILD_BENCHMARKS)
+ add_subdirectory(lib/catch2)
+ endif()
+
# Subdirectories
# Be sure to add all relevant definitions above this
-
add_subdirectory(src)
set(CPACK_CREATE_DESKTOP_LINKS ${PROJECT_NAME})
set(CPACK_PACKAGING_INSTALL_PREFIX "/${PROJECT_NAME_CAPITALIZED}")
- set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/minetest-icon.ico")
+ set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/dragonfire-icon.ico")
# Supported languages can be found at
# http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html
#set(CPACK_WIX_CULTURES "ar-SA,bg-BG,ca-ES,hr-HR,cs-CZ,da-DK,nl-NL,en-US,et-EE,fi-FI,fr-FR,de-DE")
Minetest is a free open-source voxel game engine with easy modding and game creation.
- Copyright (C) 2010-2020 Perttu Ahola <celeron55@gmail.com>
+ Copyright (C) 2010-2022 Perttu Ahola <celeron55@gmail.com>
and contributors (see source file comments and the version control log)
In case you downloaded the source code
--------------------------------------
-If you downloaded the Minetest Engine source code in which this file is
+If you downloaded the Dragonfire Client source code in which this file is
contained, you probably want to download the [Minetest Game](https://github.com/minetest/minetest_game/)
project too. See its README.txt for more information.
| Dependency | Version | Commentary |
|------------|---------|------------|
- | GCC | 4.9+ | Can be replaced with Clang 3.4+ |
+ | GCC | 5.1+ | or Clang 3.5+ |
| CMake | 3.5+ | |
| IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht |
- | SQLite3 | 3.0+ | |
+ | Freetype | 2.0+ | |
+ | SQLite3 | 3+ | |
| Zstd | 1.0+ | |
| LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present |
| GMP | 5.0.0+ | Bundled mini-GMP is used if not present |
For Debian/Ubuntu users:
- sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev
+ sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
For Fedora users:
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
-
+
For Arch users:
sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
- git clone --depth 1 https://github.com/minetest/minetest.git
+ git clone --depth 1 https://github.com/EliasFleckenstein03/dragonfireclient
cd minetest
Download minetest_game (otherwise only the "Development Test" game is available) using Git:
- Debug build is slower, but gives much more useful output in a debugger.
- If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed.
- In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`.
- - IrrlichtMt can also be installed somewhere that is not a standard install path.
- - In that case use `-DCMAKE_PREFIX_PATH=/path/to/install_prefix`
- - The path must be set so that `$(CMAKE_PREFIX_PATH)/lib/cmake/IrrlichtMt` exists
- or that `$(CMAKE_PREFIX_PATH)` is the path of an IrrlichtMt build folder.
+
+ - Minetest will use the IrrlichtMt package that is found first, given by the following order:
+ 1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
+ 2. `${PROJECT_SOURCE_DIR}/lib/irrlichtmt` (if existent)
+ 3. Installation of IrrlichtMt in the system-specific library paths
+ 4. For server builds with disabled `BUILD_CLIENT` variable, the headers from `IRRLICHT_INCLUDE_DIR` will be used.
+ - NOTE: Changing the IrrlichtMt build directory (includes system installs) requires regenerating the CMake cache (`rm CMakeCache.txt`)
### CMake options
BUILD_CLIENT=TRUE - Build Minetest client
BUILD_SERVER=FALSE - Build Minetest server
BUILD_UNITTESTS=TRUE - Build unittest sources
+ BUILD_BENCHMARKS=FALSE - Build benchmark sources
CMAKE_BUILD_TYPE=Release - Type of build (Release vs. Debug)
Release - Release build
Debug - Debug build
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
- ENABLE_FREETYPE=ON - Build with FreeType2; Allows using TTF fonts
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
- ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by IrrlichtMt)
+ ENABLE_GLES=OFF - Enable extra support code for OpenGL ES (requires support by IrrlichtMt)
ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend
ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default)
ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp)
ENABLE_SYSTEM_JSONCPP=ON - Use JsonCPP from system
- OPENGL_GL_PREFERENCE=LEGACY - Linux client build only; See CMake Policy CMP0072 for reference
RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory)
USE_GPROF=FALSE - Enable profiling using GProf
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
+ ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt)
Library specific options:
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
- FREETYPE_INCLUDE_DIR_freetype2 - Only if building with FreeType 2; directory that contains an freetype directory with files such as ftimage.h in it
- FREETYPE_INCLUDE_DIR_ft2build - Only if building with FreeType 2; directory that contains ft2build.h
- FREETYPE_LIBRARY - Only if building with FreeType 2; path to libfreetype.a/libfreetype.so/freetype.lib
- FREETYPE_DLL - Only if building with FreeType 2 on Windows; path to libfreetype.dll
+ EXTRA_DLL - Only on Windows; optional paths to additional DLLs that should be packaged
+ FREETYPE_INCLUDE_DIR_freetype2 - Directory that contains files such as ftimage.h
+ FREETYPE_INCLUDE_DIR_ft2build - Directory that contains ft2build.h
+ FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib
+ FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll
GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs
GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h
GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a
OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll
OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located
OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib
- OPENGLES2_INCLUDE_DIR - Only if building with GLES; directory that contains gl2.h
- OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so
SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h
SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib
VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs
VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
- XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so
ZLIB_DLL - Only on Windows; path to zlib1.dll
ZLIB_INCLUDE_DIR - Directory that contains zlib.h
ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib
After you successfully built vcpkg you can easily install the required libraries:
```powershell
- vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit gmp jsoncpp --triplet x64-windows
+ vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry --triplet x64-windows
```
- **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt` as described in the Linux section.
- `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store.
- `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound.
- - `freetype` is optional, it allows true-type font rendering.
- `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter.
- `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled
Open the generated project file with Visual Studio. Right-click **Package** and choose **Generate**.
It may take some minutes to generate the installer.
+ ### Compiling on MacOS
+
+ #### Requirements
+ - [Homebrew](https://brew.sh/)
+ - [Git](https://git-scm.com/downloads)
+
+ Install dependencies with homebrew:
+
+ ```
+ brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd
+ ```
+
+ #### Download
+
+ Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
+
+ ```bash
+ git clone --depth 1 https://github.com/minetest/minetest.git
+ cd minetest
+ ```
+
+ Download minetest_game (otherwise only the "Development Test" game is available) using Git:
+
+ ```
+ git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
+ ```
+
+ Download Minetest's fork of Irrlicht:
+
+ ```
+ git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
+ ```
+
+ #### Build
+
+ ```bash
+ mkdir build
+ cd build
+
+ cmake .. \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
+ -DCMAKE_FIND_FRAMEWORK=LAST \
+ -DCMAKE_INSTALL_PREFIX=../build/macos/ \
+ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
+
+ make -j$(sysctl -n hw.logicalcpu)
+ make install
+ ```
+
+ #### Run
+
+ ```
+ open ./build/macos/minetest.app
+ ```
Docker
------
--- /dev/null
-dofile(gamepath .. "voxelarea.lua")
+ core.log("info", "Initializing asynchronous environment (game)")
+
+ local function pack2(...)
+ return {n=select('#', ...), ...}
+ end
+
+ -- Entrypoint to run async jobs, called by C++
+ function core.job_processor(func, params)
+ local retval = pack2(func(unpack(params, 1, params.n)))
+
+ return retval
+ end
+
+ -- Import a bunch of individual files from builtin/game/
+ local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM
++local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM
+
+ dofile(gamepath .. "constants.lua")
+ dofile(gamepath .. "item_s.lua")
+ dofile(gamepath .. "misc_s.lua")
+ dofile(gamepath .. "features.lua")
++dofile(commonpath .. "voxelarea.lua")
+
+ -- Transfer of globals
+ do
+ local all = assert(core.transferred_globals)
+ core.transferred_globals = nil
+
+ -- reassemble other tables
+ all.registered_nodes = {}
+ all.registered_craftitems = {}
+ all.registered_tools = {}
+ for k, v in pairs(all.registered_items) do
+ if v.type == "node" then
+ all.registered_nodes[k] = v
+ elseif v.type == "craftitem" then
+ all.registered_craftitems[k] = v
+ elseif v.type == "tool" then
+ all.registered_tools[k] = v
+ end
+ end
+
+ for k, v in pairs(all) do
+ core[k] = v
+ end
+ end
-
core.callback_origins = {}
local getinfo = debug.getinfo
return ret
end
+function core.override_item(name, redefinition)
+ if redefinition.name ~= nil then
+ error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2)
+ end
+ if redefinition.type ~= nil then
+ error("Attempt to redefine type of "..name.." to "..dump(redefinition.type), 2)
+ end
+ local itemdef = core.get_item_def(name)
+ if not itemdef then
+ error("Attempt to override non-existent item "..name, 2)
+ end
+ local nodedef = core.get_node_def(name)
+ table.combine(itemdef, nodedef)
+
+ for k, v in pairs(redefinition) do
+ rawset(itemdef, k, v)
+ end
+ core.register_item_raw(itemdef)
+end
+
--
-- Callback registration
--
core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()
core.registered_on_modchannel_signal, core.register_on_modchannel_signal = make_registration()
core.registered_on_inventory_open, core.register_on_inventory_open = make_registration()
+core.registered_on_recieve_physics_override, core.register_on_recieve_physics_override = make_registration()
+core.registered_on_play_sound, core.register_on_play_sound = make_registration()
+core.registered_on_spawn_particle, core.register_on_spawn_particle = make_registration()
+core.registered_on_object_properties_change, core.register_on_object_properties_change = make_registration()
+core.registered_on_object_hp_change, core.register_on_object_hp_change = make_registration()
+core.registered_on_object_add, core.register_on_object_add = make_registration()
+
+core.registered_nodes = {}
+core.registered_items = {}
+core.object_refs = {}
core.registered_chatcommands = {}
+ -- Interpret the parameters of a command, separating options and arguments.
+ -- Input: command, param
+ -- command: name of command
+ -- param: parameters of command
+ -- Returns: opts, args
+ -- opts is a string of option letters, or false on error
+ -- args is an array with the non-option arguments in order, or an error message
+ -- Example: for this command line:
+ -- /command a b -cd e f -g
+ -- the function would receive:
+ -- a b -cd e f -g
+ -- and it would return:
+ -- "cdg", {"a", "b", "e", "f"}
+ -- Negative numbers are taken as arguments. Long options (--option) are
+ -- currently rejected as reserved.
+ local function getopts(command, param)
+ local opts = ""
+ local args = {}
+ for match in param:gmatch("%S+") do
+ if match:byte(1) == 45 then -- 45 = '-'
+ local second = match:byte(2)
+ if second == 45 then
+ return false, S("Invalid parameters (see /help @1).", command)
+ elseif second and (second < 48 or second > 57) then -- 48 = '0', 57 = '9'
+ opts = opts .. match:sub(2)
+ else
+ -- numeric, add it to args
+ args[#args + 1] = match
+ end
+ else
+ args[#args + 1] = match
+ end
+ end
+ return opts, args
+ end
+
function core.register_chatcommand(cmd, def)
def = def or {}
def.params = def.params or ""
core.registered_chatcommands[name] = chatcommand
end
+if INIT == "client" then
+ function core.register_list_command(command, desc, setting)
+ local def = {}
+ def.description = desc
+ def.params = "del <item> | add <item> | list"
+ function def.func(param)
+ local list = (minetest.settings:get(setting) or ""):split(",")
+ if param == "list" then
+ return true, table.concat(list, ", ")
+ else
+ local sparam = param:split(" ")
+ local cmd = sparam[1]
+ local item = sparam[2]
+ if cmd == "del" then
+ if not item then
+ return false, "Missing item."
+ end
+ local i = table.indexof(list, item)
+ if i == -1 then
+ return false, item .. " is not on the list."
+ else
+ table.remove(list, i)
+ core.settings:set(setting, table.concat(list, ","))
+ return true, "Removed " .. item .. " from the list."
+ end
+ elseif cmd == "add" then
+ if not item then
+ return false, "Missing item."
+ end
+ local i = table.indexof(list, item)
+ if i ~= -1 then
+ return false, item .. " is already on the list."
+ else
+ table.insert(list, item)
+ core.settings:set(setting, table.concat(list, ","))
+ return true, "Added " .. item .. " to the list."
+ end
+ end
+ end
+ return false, "Invalid usage. (See .help " .. command .. ")"
+ end
+ core.register_chatcommand(command, def)
+ end
+end
+
+ local function format_help_line(cmd, def)
+ local cmd_marker = INIT == "client" and "." or "/"
+ local msg = core.colorize("#00ffff", cmd_marker .. cmd)
+ if def.params and def.params ~= "" then
+ msg = msg .. " " .. def.params
+ end
+ if def.description and def.description ~= "" then
+ msg = msg .. ": " .. def.description
+ end
+ return msg
+ end
+
local function do_help_cmd(name, param)
- local function format_help_line(cmd, def)
- local cmd_marker = "/"
- if INIT == "client" then
- cmd_marker = "."
- end
- local msg = core.colorize("#00ffff", cmd_marker .. cmd)
- if def.params and def.params ~= "" then
- msg = msg .. " " .. def.params
- end
- if def.description and def.description ~= "" then
- msg = msg .. ": " .. def.description
- end
- return msg
+ local opts, args = getopts("help", param)
+ if not opts then
+ return false, args
+ end
+ if #args > 1 then
+ return false, S("Too many arguments, try using just /help <command>")
end
- if param == "" then
+ local use_gui = INIT ~= "client" and core.get_player_by_name(name)
+ use_gui = use_gui and not opts:find("t")
+
+ if #args == 0 and not use_gui then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then
.. "everything.")
end
return true, msg
- elseif param == "all" then
+ elseif #args == 0 or (args[1] == "all" and use_gui) then
+ core.show_general_help_formspec(name)
+ return true
+ elseif args[1] == "all" then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then
msg = core.gettext("Available commands:")
end
return true, msg.."\n"..table.concat(cmds, "\n")
- elseif INIT == "game" and param == "privs" then
+ elseif INIT == "game" and args[1] == "privs" then
+ if use_gui then
+ core.show_privs_help_formspec(name)
+ return true
+ end
local privs = {}
for priv, def in pairs(core.registered_privileges) do
privs[#privs + 1] = priv .. ": " .. def.description
table.sort(privs)
return true, S("Available privileges:").."\n"..table.concat(privs, "\n")
else
- local cmd = param
+ local cmd = args[1]
local def = core.registered_chatcommands[cmd]
if not def then
local msg
})
else
core.register_chatcommand("help", {
- params = S("[all | privs | <cmd>]"),
- description = S("Get help for commands or list privileges"),
+ params = S("[all | privs | <cmd>] [-t]"),
+ description = S("Get help for commands or list privileges (-t: output in chat)"),
func = do_help_cmd,
})
end
end
+function table.combine(t, other)
+ other = other or {}
+ for k, v in pairs(other) do
+ if type(v) == "table" and type(t[k]) == "table" then
+ table.combine(t[k], v)
+ else
+ t[k] = v
+ end
+ end
+end
+
--------------------------------------------------------------------------------
-- mainmenu only functions
--------------------------------------------------------------------------------
end
end
- if INIT == "client" or INIT == "mainmenu" then
+ if core.gettext then -- for client and mainmenu
function fgettext_ne(text, ...)
text = core.gettext(text)
local arg = {n=select('#', ...), ...}
return table.concat(lines, "\n") .. core.get_color_escape_sequence("#ffffff")
end
+local function rgb_to_hex(rgb)
+ local hexadecimal = "#"
+
+ for key, value in pairs(rgb) do
+ local hex = ""
+
+ while(value > 0)do
+ local index = math.fmod(value, 16) + 1
+ value = math.floor(value / 16)
+ hex = string.sub("0123456789ABCDEF", index, index) .. hex
+ end
+
+ if(string.len(hex) == 0)then
+ hex = "00"
+ elseif(string.len(hex) == 1)then
+ hex = "0" .. hex
+ end
+
+ hexadecimal = hexadecimal .. hex
+ end
+
+ return hexadecimal
+end
+
+local function color_from_hue(hue)
+ local h = hue / 60
+ local c = 255
+ local x = (1 - math.abs(h % 2 - 1)) * 255
+
+ local i = math.floor(h)
+ if i == 0 then
+ return rgb_to_hex({c, x, 0})
+ elseif i == 1 then
+ return rgb_to_hex({x, c, 0})
+ elseif i == 2 then
+ return rgb_to_hex({0, c, x})
+ elseif i == 3 then
+ return rgb_to_hex({0, x, c})
+ elseif i == 4 then
+ return rgb_to_hex({x, 0, c})
+ else
+ return rgb_to_hex({c, 0, x})
+ end
+end
+
+function core.rainbow(input)
+ local step = 360 / input:len()
+ local hue = 0
+ local output = ""
+ for i = 1, input:len() do
+ local char = input:sub(i, i)
+ if char:match("%s") then
+ output = output .. char
+ else
+ output = output .. core.get_color_escape_sequence(color_from_hue(hue)) .. char
+ end
+ hue = hue + step
+ end
+ return output
+end
function core.strip_foreground_colors(str)
return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", ""))
return function(str, ...) return core.translate(textdomain or "", str, ...) end
end
+function core.get_pointed_thing_position(pointed_thing, above)
+ if pointed_thing.type == "node" then
+ if above then
+ -- The position where a node would be placed
+ return pointed_thing.above
+ end
+ -- The position where a node would be dug
+ return pointed_thing.under
+ elseif pointed_thing.type == "object" then
+ return pointed_thing.ref and pointed_thing.ref:get_pos()
+ end
+end
+
--------------------------------------------------------------------------------
-- Returns the exact coordinate of a pointed surface
--------------------------------------------------------------------------------
function core.is_nan(number)
return number ~= number
end
+
+function core.inventorycube(img1, img2, img3)
+ img2 = img2 or img1
+ img3 = img3 or img1
+ return "[inventorycube"
+ .. "{" .. img1:gsub("%^", "&")
+ .. "{" .. img2:gsub("%^", "&")
+ .. "{" .. img3:gsub("%^", "&")
+end
local builtin_shared = {}
dofile(gamepath .. "constants.lua")
+ dofile(gamepath .. "item_s.lua")
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
dofile(gamepath .. "register.lua")
end
dofile(commonpath .. "after.lua")
+dofile(commonpath .. "voxelarea.lua")
dofile(gamepath .. "item_entity.lua")
dofile(gamepath .. "deprecated.lua")
+ dofile(gamepath .. "misc_s.lua")
dofile(gamepath .. "misc.lua")
dofile(gamepath .. "privileges.lua")
dofile(gamepath .. "auth.lua")
dofile(gamepath .. "detached_inventory.lua")
assert(loadfile(gamepath .. "falling.lua"))(builtin_shared)
dofile(gamepath .. "features.lua")
-dofile(gamepath .. "voxelarea.lua")
dofile(gamepath .. "forceloading.lua")
dofile(gamepath .. "statbars.lua")
dofile(gamepath .. "knockback.lua")
+ dofile(gamepath .. "async.lua")
profiler = nil
if INIT == "game" then
dofile(gamepath .. "init.lua")
- assert(not core.get_http_api)
elseif INIT == "mainmenu" then
local mm_script = core.settings:get("main_menu_script")
local custom_loaded = false
if not custom_loaded then
dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua")
end
- elseif INIT == "async" then
- dofile(asyncpath .. "init.lua")
+ elseif INIT == "async" then
+ dofile(asyncpath .. "mainmenu.lua")
+ elseif INIT == "async_game" then
+ dofile(asyncpath .. "game.lua")
elseif INIT == "client" then
dofile(clientpath .. "init.lua")
else
-- Unordered preserves the original order of the ContentDB API,
-- before the package list is ordered based on installed state.
- local store = { packages = {}, packages_full = {}, packages_full_unordered = {} }
+ local store = { packages = {}, packages_full = {}, packages_full_unordered = {}, aliases = {} }
local http = core.get_http_api()
local filter_type = 1
local filter_types_titles = {
fgettext("All packages"),
- fgettext("Games"),
- fgettext("Mods"),
+-- fgettext("Games"),
+ fgettext("Clientmods"),
fgettext("Texture packs"),
}
local filter_types_type = {
nil,
- "game",
+-- "game",
"mod",
"txp",
}
local REASON_DEPENDENCY = "dependency"
+ -- encodes for use as URL parameter or path component
+ local function urlencode(str)
+ return str:gsub("[^%a%d()._~-]", function(char)
+ return string.format("%%%02X", string.byte(char))
+ end)
+ end
+ assert(urlencode("sample text?") == "sample%20text%3F")
+
+
local function get_download_url(package, reason)
local base_url = core.settings:get("contentdb_url")
- local ret = base_url .. ("/packages/%s/%s/releases/%d/download/"):format(package.author, package.name, package.release)
+ local ret = base_url .. ("/packages/%s/releases/%d/download/"):format(
+ package.url_part, package.release)
if reason then
ret = ret .. "?reason=" .. reason
end
end
- local function download_package(param)
- if core.download_file(param.url, param.filename) then
+ local function download_and_extract(param)
+ local package = param.package
+
+ local filename = core.get_temp_path(true)
+ if filename == "" or not core.download_file(param.url, filename) then
+ core.log("error", "Downloading " .. dump(param.url) .. " failed")
return {
- filename = param.filename,
- successful = true,
+ msg = fgettext("Failed to download $1", package.name)
}
+ end
+
+ local tempfolder = core.get_temp_path()
+ if tempfolder ~= "" then
+ tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000)
+ if not core.extract_zip(filename, tempfolder) then
+ tempfolder = nil
+ end
else
- core.log("error", "downloading " .. dump(param.url) .. " failed")
+ tempfolder = nil
+ end
+ os.remove(filename)
+ if not tempfolder then
return {
- successful = false,
+ msg = fgettext("Install: Unsupported file type or broken archive"),
}
end
+
+ return {
+ path = tempfolder
+ }
end
local function start_install(package, reason)
local params = {
package = package,
url = get_download_url(package, reason),
- filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip",
}
number_downloading = number_downloading + 1
local function callback(result)
- if result.successful then
- local path, msg = pkgmgr.install(package.type,
- result.filename, package.name,
- package.path)
+ if result.msg then
+ gamedata.errormessage = result.msg
+ else
+ local path, msg = pkgmgr.install_dir(package.type, result.path, package.name, package.path)
+ core.delete_dir(result.path)
if not path then
gamedata.errormessage = msg
else
conf:write()
end
end
- os.remove(result.filename)
- else
- gamedata.errormessage = fgettext("Failed to download $1", package.name)
end
package.downloading = false
package.queued = false
package.downloading = true
- if not core.handle_async(download_package, params, callback) then
+ if not core.handle_async(download_and_extract, params, callback) then
core.log("error", "ERROR: async event failed")
gamedata.errormessage = fgettext("Failed to download $1", package.name)
return
local url_fmt = "/api/packages/%s/dependencies/?only_hard=1&protocol_version=%s&engine_version=%s"
local version = core.get_version()
local base_url = core.settings:get("contentdb_url")
- local url = base_url .. url_fmt:format(package.id, core.get_max_supp_proto(), version.string)
+ local url = base_url .. url_fmt:format(package.url_part, core.get_max_supp_proto(), urlencode(version.string))
local response = http.fetch_sync({ url = url })
if not response.succeeded then
local base_url = core.settings:get("contentdb_url")
local url = base_url ..
"/api/packages/?type=mod&type=game&type=txp&protocol_version=" ..
- core.get_max_supp_proto() .. "&engine_version=" .. version.string
+ core.get_max_supp_proto() .. "&engine_version=" .. urlencode(version.string)
for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do
item = item:trim()
if item ~= "" then
- url = url .. "&hide=" .. item
+ url = url .. "&hide=" .. urlencode(item)
end
end
- local timeout = tonumber(core.settings:get("curl_file_download_timeout"))
- local response = http.fetch_sync({ url = url, timeout = timeout })
+ local response = http.fetch_sync({ url = url })
if not response.succeeded then
return
end
for _, package in pairs(store.packages_full) do
local name_len = #package.name
+ -- This must match what store.update_paths() does!
+ package.id = package.author:lower() .. "/"
if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
- package.id = package.author:lower() .. "/" .. package.name:sub(1, name_len - 5)
+ package.id = package.id .. package.name:sub(1, name_len - 5)
else
- package.id = package.author:lower() .. "/" .. package.name
+ package.id = package.id .. package.name
end
+ package.url_part = urlencode(package.author) .. "/" .. urlencode(package.name)
+
if package.aliases then
for _, alias in ipairs(package.aliases) do
-- We currently don't support name changing
function store.update_paths()
local mod_hash = {}
pkgmgr.refresh_globals()
- for _, mod in pairs(pkgmgr.global_mods:get_list()) do
+ for _, mod in pairs(pkgmgr.clientmods:get_list()) do
if mod.author and mod.release > 0 then
local id = mod.author:lower() .. "/" .. mod.name
mod_hash[store.aliases[id] or id] = mod
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif package.queued then
formspec[#formspec + 1] = left_base
- formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
- formspec[#formspec + 1] = "cdb_queued.png;queued]"
+ formspec[#formspec + 1] = "cdb_queued.png;queued;]"
elseif not package.path then
local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
end
if fields["view_" .. i] then
- local url = ("%s/packages/%s/%s?protocol_version=%d"):format(
- core.settings:get("contentdb_url"),
- package.author, package.name, core.get_max_supp_proto())
+ local url = ("%s/packages/%s?protocol_version=%d"):format(
+ core.settings:get("contentdb_url"), package.url_part,
+ core.get_max_supp_proto())
core.open_url(url)
return true
end
-- Parse mods
local mods_category_initialized = false
local mods = {}
- get_mods(core.get_modpath(), mods)
+ get_mods(core.get_modpath(), "mods", mods)
for _, mod in ipairs(mods) do
local path = mod.path .. DIR_DELIM .. FILENAME
local file = io.open(path, "r")
table.insert(settings, {
name = mod.name,
+ readable_name = mod.title,
level = 1,
type = "category",
})
file:close()
end
end
- get_mods(core.get_clientmodpath(), clientmods)
+
+ -- Parse clientmods
+ local clientmods_category_initialized = false
+ local clientmods = {}
++ get_mods(core.get_clientmodpath(), "clientmods", clientmods)
+ for _, clientmod in ipairs(clientmods) do
+ local path = clientmod.path .. DIR_DELIM .. FILENAME
+ local file = io.open(path, "r")
+ if file then
+ if not clientmods_category_initialized then
+ fgettext_ne("Clientmods") -- not used, but needed for xgettext
+ table.insert(settings, {
+ name = "Clientmods",
+ level = 0,
+ type = "category",
+ })
+ clientmods_category_initialized = true
+ end
+
+ table.insert(settings, {
+ name = clientmod.name,
+ level = 1,
+ type = "category",
+ })
+
+ parse_single_file(file, path, read_all, settings, 2, false)
+
+ file:close()
+ end
+ end
end
return settings
local function get_current_np_group(setting)
local value = core.settings:get_np_group(setting.name)
- local t = {}
if value == nil then
- t = setting.values
- else
- table.insert(t, value.offset)
- table.insert(t, value.scale)
- table.insert(t, value.spread.x)
- table.insert(t, value.spread.y)
- table.insert(t, value.spread.z)
- table.insert(t, value.seed)
- table.insert(t, value.octaves)
- table.insert(t, value.persistence)
- table.insert(t, value.lacunarity)
- table.insert(t, value.flags)
+ return setting.values
end
- return t
+ local p = "%g"
+ return {
+ p:format(value.offset),
+ p:format(value.scale),
+ p:format(value.spread.x),
+ p:format(value.spread.y),
+ p:format(value.spread.z),
+ p:format(value.seed),
+ p:format(value.octaves),
+ p:format(value.persistence),
+ p:format(value.lacunarity),
+ value.flags
+ }
end
local function get_current_np_group_as_string(setting)
local value = core.settings:get_np_group(setting.name)
- local t
if value == nil then
- t = setting.default
- else
- t = value.offset .. ", " ..
- value.scale .. ", (" ..
- value.spread.x .. ", " ..
- value.spread.y .. ", " ..
- value.spread.z .. "), " ..
- value.seed .. ", " ..
- value.octaves .. ", " ..
- value.persistence .. ", " ..
- value.lacunarity
- if value.flags ~= "" then
- t = t .. ", " .. value.flags
- end
+ return setting.default
end
- return t
+ return ("%g, %g, (%g, %g, %g), %g, %g, %g, %g"):format(
+ value.offset,
+ value.scale,
+ value.spread.x,
+ value.spread.y,
+ value.spread.z,
+ value.seed,
+ value.octaves,
+ value.persistence,
+ value.lacunarity
+ ) .. (value.flags ~= "" and (", " .. value.flags) or "")
end
local checkboxes = {} -- handle checkboxes events
elseif setting.type == "v3f" then
local val = get_current_value(setting)
local v3f = {}
- for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
+ for line in val:gmatch("[+-]?[%d.+-eE]+") do -- All numeric characters
table.insert(v3f, line)
end
local current_level = 0
for _, entry in ipairs(settings) do
local name
- if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
+ if not core.settings:get_bool("show_technical_names") and entry.readable_name then
name = fgettext_ne(entry.readable_name)
else
name = entry.name
"button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
"button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
"checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
- .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
+ .. dump(core.settings:get_bool("show_technical_names")) .. "]"
return formspec
end
end
if fields["cb_tech_settings"] then
- core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
+ core.settings:set("show_technical_names", fields["cb_tech_settings"])
core.settings:write()
core.update_formspec(this:get_formspec())
return true
dofile(menupath .. DIR_DELIM .. "common.lua")
dofile(menupath .. DIR_DELIM .. "pkgmgr.lua")
dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua")
- dofile(menupath .. DIR_DELIM .. "textures.lua")
+ dofile(menupath .. DIR_DELIM .. "game_theme.lua")
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
core.settings:set("menu_last_game", default_game)
end
- mm_texture.init()
+ mm_game_theme.init()
-- Create main tabview
local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0})
if tv_main.current_tab == "local" then
local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
if game == nil then
- mm_texture.reset()
+ mm_game_theme.reset()
end
end
tv_main:show()
ui.update()
-
- core.sound_play("main_menu", true)
end
init_globals()
+
for _, item in ipairs(list) do
if item ~= "base" then
- local name = item
-
local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
- if path == current_texture_path then
- name = fgettext("$1 (Enabled)", name)
- end
-
local conf = Settings(path .. "texture_pack.conf")
+ local enabled = path == current_texture_path
+
+ local title = conf:get("title") or item
+ -- list_* is only used if non-nil, else the regular versions are used.
retval[#retval + 1] = {
name = item,
+ title = title,
+ list_name = enabled and fgettext("$1 (Enabled)", item) or nil,
+ list_title = enabled and fgettext("$1 (Enabled)", title) or nil,
author = conf:get("author"),
release = tonumber(conf:get("release")) or 0,
- list_name = name,
type = "txp",
path = path,
- enabled = path == current_texture_path,
+ enabled = enabled,
}
end
end
end
- function get_mods(path,retval,modpack)
+ function get_mods(path, virtual_path, retval, modpack)
local mods = core.get_dir_list(path, true)
for _, name in ipairs(mods) do
if name:sub(1, 1) ~= "." then
- local prefix = path .. DIR_DELIM .. name
+ local mod_path = path .. DIR_DELIM .. name
+ local mod_virtual_path = virtual_path .. "/" .. name
local toadd = {
dir_name = name,
parent_dir = path,
-- Get config file
local mod_conf
- local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf")
+ local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf")
if modpack_conf then
toadd.is_modpack = true
modpack_conf:close()
- mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table()
+ mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table()
if mod_conf.name then
name = mod_conf.name
toadd.is_name_explicit = true
end
else
- mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table()
+ mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table()
if mod_conf.name then
name = mod_conf.name
toadd.is_name_explicit = true
-- Read from config
toadd.name = name
+ toadd.title = mod_conf.title
toadd.author = mod_conf.author
toadd.release = tonumber(mod_conf.release) or 0
- toadd.path = prefix
+ toadd.path = mod_path
+ toadd.virtual_path = mod_virtual_path
toadd.type = "mod"
-- Check modpack.txt
-- Note: modpack.conf is already checked above
- local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
+ local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
if modpackfile then
modpackfile:close()
toadd.is_modpack = true
elseif toadd.is_modpack then
toadd.type = "modpack"
toadd.is_modpack = true
- get_mods(prefix, retval, name)
+ get_mods(mod_path, mod_virtual_path, retval, name)
end
end
end
end
--------------------------------------------------------------------------------
- function pkgmgr.extract(modfile)
- if modfile.type == "zip" then
- local tempfolder = os.tempfolder()
-
- if tempfolder ~= nil and
- tempfolder ~= "" then
- core.create_dir(tempfolder)
- if core.extract_zip(modfile.name,tempfolder) then
- return tempfolder
- end
- end
- end
- return nil
- end
-
function pkgmgr.get_folder_type(path)
local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
if testfile ~= nil then
return nil
end
--------------------------------------------------------------------------------
- function pkgmgr.render_packagelist(render_list)
+ function pkgmgr.render_packagelist(render_list, use_technical_names)
if not render_list then
if not pkgmgr.global_mods then
pkgmgr.refresh_globals()
else
retval[#retval + 1] = "0"
end
- retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
+
+ if use_technical_names then
+ retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
+ else
+ retval[#retval + 1] = core.formspec_escape(v.list_title or v.list_name or v.title or v.name)
+ end
end
return table.concat(retval, ",")
return true
end
+ local function disable_all_by_name(list, name, except)
+ for i=1, #list do
+ if list[i].name == name and list[i] ~= except then
+ list[i].enabled = false
+ end
+ end
+ end
+
---------- toggles or en/disables a mod or modpack and its dependencies --------
local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
if not mod.is_modpack then
toset = not mod.enabled
end
if mod.enabled ~= toset then
- mod.enabled = toset
toggled_mods[#toggled_mods+1] = mod.name
end
if toset then
-- Mark this mod for recursive dependency traversal
enabled_mods[mod.name] = true
+
+ -- Disable other mods with the same name
+ disable_all_by_name(list, mod.name, mod)
end
+ mod.enabled = toset
else
-- Toggle or en/disable every mod in the modpack,
-- interleaved unsupported
local enabled_mods = {}
toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
- if not toset then
+ if next(enabled_mods) == nil then
-- Mod(s) were disabled, so no dependencies need to be enabled
table.sort(toggled_mods)
core.log("info", "Following mods were disabled: " ..
-- Enable mods' depends after activation
- -- Make a list of mod ids indexed by their names
+ -- Make a list of mod ids indexed by their names. Among mods with the
+ -- same name, enabled mods take precedence, after which game mods take
+ -- precedence, being last in the mod list.
local mod_ids = {}
for id, mod2 in pairs(list) do
if mod2.type == "mod" and not mod2.is_modpack then
- mod_ids[mod2.name] = id
+ local prev_id = mod_ids[mod2.name]
+ if not prev_id or not list[prev_id].enabled then
+ mod_ids[mod2.name] = id
+ end
end
end
end
end
end
+
-- If sp is 0, every dependency is already activated
while sp > 0 do
local name = to_enable[sp]
core.log("warning", "Mod dependency \"" .. name ..
"\" not found!")
else
- if mod_to_enable.enabled == false then
+ if not mod_to_enable.enabled then
mod_to_enable.enabled = true
toggled_mods[#toggled_mods+1] = mod_to_enable.name
end
-- Push the dependencies of the dependency onto the stack
local depends = pkgmgr.get_dependencies(mod_to_enable.path)
for i = 1, #depends do
- if not enabled_mods[name] then
+ if not enabled_mods[depends[i]] then
sp = sp+1
to_enable[sp] = depends[i]
end
local from = basefolder and basefolder.path or path
if targetpath then
core.delete_dir(targetpath)
- core.create_dir(targetpath)
else
targetpath = core.get_texturepath() .. DIR_DELIM .. basename
end
- if not core.copy_dir(from, targetpath) then
+ if not core.copy_dir(from, targetpath, false) then
return nil,
fgettext("Failed to install $1 to $2", basename, targetpath)
end
-- Get destination name for modpack
if targetpath then
core.delete_dir(targetpath)
- core.create_dir(targetpath)
else
local clean_path = nil
if basename ~= nil then
clean_path = get_last_folder(cleanup_path(basefolder.path))
end
if clean_path then
- targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
+ targetpath = core.get_clientmodpath() .. DIR_DELIM .. clean_path
else
return nil,
fgettext("Install Mod: Unable to find suitable folder name for modpack $1",
if targetpath then
core.delete_dir(targetpath)
- core.create_dir(targetpath)
else
local targetfolder = basename
if targetfolder == nil then
end
if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
- targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
+ targetpath = core.get_clientmodpath() .. DIR_DELIM .. targetfolder
else
return nil, fgettext("Install Mod: Unable to find real mod name for: $1", path)
end
if targetpath then
core.delete_dir(targetpath)
- core.create_dir(targetpath)
else
targetpath = core.get_gamepath() .. DIR_DELIM .. basename
end
end
-- Copy it
- if not core.copy_dir(basefolder.path, targetpath) then
+ if not core.copy_dir(basefolder.path, targetpath, false) then
return nil,
fgettext("Failed to install $1 to $2", basename, targetpath)
end
return targetpath, nil
end
- --------------------------------------------------------------------------------
- function pkgmgr.install(type, modfilename, basename, dest)
- local archive_info = pkgmgr.identify_filetype(modfilename)
- local path = pkgmgr.extract(archive_info)
-
- if path == nil then
- return nil,
- fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
- fgettext("Install: Unsupported file type \"$1\" or broken archive",
- archive_info.type)
- end
-
- local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
- core.delete_dir(path)
- return targetpath, msg
- end
-
--------------------------------------------------------------------------------
- if modpath ~= nil and
- modpath ~= "" then
- get_mods(modpath,clientmods)
+function pkgmgr.prepareclientmodlist(data)
+ local retval = {}
+
+ local clientmods = {}
+
+ --read clientmods
+ local modpath = core.get_clientmodpath()
+
++ if modpath ~= nil and modpath ~= "" then
++ get_mods(modpath, "clientmods", clientmods)
+ end
+
+ for i=1,#clientmods,1 do
+ clientmods[i].type = "mod"
+ clientmods[i].loc = "global"
+ clientmods[i].is_clientside = true
+ retval[#retval + 1] = clientmods[i]
+ end
+
+ --read mods configuration
+ local filename = modpath ..
+ DIR_DELIM .. "mods.conf"
+
+ local conffile = Settings(filename)
+
+ for key,value in pairs(conffile:to_table()) do
+ if key:sub(1, 9) == "load_mod_" then
+ key = key:sub(10)
+ local element = nil
+ for i=1,#retval,1 do
+ if retval[i].name == key and
+ not retval[i].is_modpack then
+ element = retval[i]
+ break
+ end
+ end
+ if element ~= nil then
+ element.enabled = value ~= "false" and value ~= "nil" and value
+ else
+ core.log("info", "Clientmod: " .. key .. " " .. dump(value) .. " but not found")
+ end
+ end
+ end
+
+ return retval
+end
+
function pkgmgr.preparemodlist(data)
local retval = {}
local game_mods = {}
--read global mods
- local modpath = core.get_modpath()
-
- if modpath ~= nil and
- modpath ~= "" then
- get_mods(modpath,global_mods)
+ local modpaths = core.get_modpaths()
+ for key, modpath in pairs(modpaths) do
+ get_mods(modpath, key, global_mods)
end
for i=1,#global_mods,1 do
global_mods[i].type = "mod"
global_mods[i].loc = "global"
+ global_mods[i].enabled = false
retval[#retval + 1] = global_mods[i]
end
DIR_DELIM .. "world.mt"
local worldfile = Settings(filename)
-
- for key,value in pairs(worldfile:to_table()) do
+ for key, value in pairs(worldfile:to_table()) do
if key:sub(1, 9) == "load_mod_" then
key = key:sub(10)
- local element = nil
- for i=1,#retval,1 do
+ local mod_found = false
+
+ local fallback_found = false
+ local fallback_mod = nil
+
+ for i=1, #retval do
if retval[i].name == key and
- not retval[i].is_modpack then
- element = retval[i]
- break
+ not retval[i].is_modpack then
+ if core.is_yes(value) or retval[i].virtual_path == value then
+ retval[i].enabled = true
+ mod_found = true
+ break
+ elseif fallback_found then
+ -- Only allow fallback if only one mod matches
+ fallback_mod = nil
+ else
+ fallback_found = true
+ fallback_mod = retval[i]
+ end
end
end
- if element ~= nil then
- element.enabled = value ~= "false" and value ~= "nil" and value
- else
- core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
+
+ if not mod_found then
+ if fallback_mod and value:find("/") then
+ fallback_mod.enabled = true
+ else
+ core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
+ end
end
end
end
pkgmgr.comparemod, is_equal, nil, {})
pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
pkgmgr.global_mods:set_sortmode("alphabetic")
+ pkgmgr.clientmods = filterlist.create(pkgmgr.prepareclientmodlist,
+ pkgmgr.comparemod, is_equal, nil, {})
+ pkgmgr.clientmods:add_sort_mechanism("alphabetic", sort_mod_list)
+ pkgmgr.clientmods:set_sortmode("alphabetic")
end
- --------------------------------------------------------------------------------
- function pkgmgr.identify_filetype(name)
-
- if name:sub(-3):lower() == "zip" then
- return {
- name = name,
- type = "zip"
- }
- end
-
- if name:sub(-6):lower() == "tar.gz" or
- name:sub(-3):lower() == "tgz"then
- return {
- name = name,
- type = "tgz"
- }
- end
-
- if name:sub(-6):lower() == "tar.bz2" then
- return {
- name = name,
- type = "tbz"
- }
- end
-
- if name:sub(-2):lower() == "7z" then
- return {
- name = name,
- type = "7z"
- }
- end
-
- return {
- name = name,
- type = "ukn"
- }
- end
-
-
--------------------------------------------------------------------------------
function pkgmgr.find_by_gameid(gameid)
for i=1,#pkgmgr.games,1 do
if gamespec ~= nil and
gamespec.gamemods_path ~= nil and
gamespec.gamemods_path ~= "" then
- get_mods(gamespec.gamemods_path, retval)
+ get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval)
end
end
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
+local dragonfire_team = {
+ "Elias Fleckenstein [Main Developer]",
+ "cora [Core Developer]",
+ "emilia [Core Developer]",
+ "oneplustwo [Developer]",
+ "joshia_wi [Developer]",
+ "Code-Sploit [Developer]",
+ "DerZombiiie [User Support]",
+ "Rtx [User Support]",
+}
local core_developers = {
"Perttu Ahola (celeron55) <celeron55@gmail.com>",
"Lars Hofhansl <larsh@apache.org>",
"Pierre-Yves Rollo <dev@pyrollo.com>",
"v-rob <robinsonvincent89@gmail.com>",
+ "hecks",
+ "Hugues Ross <hugues.ross@gmail.com>",
+ "Dmitry Kostenko (x2048) <codeforsmile@gmail.com>",
}
-- For updating active/previous contributors, see the script in ./util/gather_git_credits.py
local active_contributors = {
- "Wuzzy [devtest game, visual corrections]",
- "Zughy [Visual improvements, various fixes]",
- "Maksim (MoNTE48) [Android]",
+ "Wuzzy [I18n for builtin, liquid features, fixes]",
+ "Zughy [Various features and fixes]",
"numzero [Graphics and rendering]",
- "appgurueu [Various internal fixes]",
- "Desour [Formspec and vector API changes]",
- "HybridDog [Rendering fixes and documentation]",
- "Hugues Ross [Graphics-related improvements]",
- "ANAND (ClobberXD) [Mouse buttons rebinding]",
- "luk3yx [Fixes]",
- "hecks [Audiovisuals, Lua API]",
- "LoneWolfHT [Object crosshair, documentation fixes]",
- "Lejo [Server-related improvements]",
- "EvidenceB [Compass HUD element]",
- "Paul Ouellette (pauloue) [Lua API, documentation]",
- "TheTermos [Collision detection, physics]",
+ "Desour [Internal fixes, Clipboard on X11]",
+ "Lars Müller [Various internal fixes]",
+ "JosiahWI [CMake, cleanups and fixes]",
+ "HybridDog [builtin, documentation]",
+ "Jude Melton-Houghton [Database implementation]",
+ "savilli [Fixes]",
+ "Liso [Shadow Mapping]",
+ "MoNTE48 [Build fix]",
+ "Jean-Patrick Guerrero (kilbith) [Fixes]",
+ "ROllerozxa [Code cleanups]",
+ "Lejo [bitop library integration]",
+ "LoneWolfHT [Build fixes]",
+ "NeroBurner [Joystick]",
+ "Elias Fleckenstein [Internal fixes]",
"David CARLIER [Unix & Haiku build fixes]",
- "dcbrwn [Object shading]",
- "Elias Fleckenstein [API features/fixes]",
- "Jean-Patrick Guerrero (kilbith) [model element, visual fixes]",
- "k.h.lai [Memory leak fixes, documentation]",
+ "pecksin [Clickable web links]",
+ "srfqi [Android & rendering fixes]",
+ "EvidenceB [Formspec]",
}
local previous_core_developers = {
"Zeno",
"ShadowNinja <shadowninja@minetest.net>",
"Auke Kok (sofar) <sofar@foo-projects.org>",
+ "Aaron Suen <warr1024@gmail.com>",
}
local previous_contributors = {
"MirceaKitsune <mirceakitsune@gmail.com>",
"Constantin Wenger (SpeedProg)",
"Ciaran Gultnieks (CiaranG)",
- "stujones11 [Android UX improvements]",
- "Rogier <rogier777@gmail.com> [Fixes]",
- "Gregory Currie (gregorycu) [optimisation]",
- "srifqi [Fixes]",
+ "Paul Ouellette (pauloue)",
+ "stujones11",
+ "Rogier <rogier777@gmail.com>",
+ "Gregory Currie (gregorycu)",
"JacobF",
"Jeija <jeija@mesecons.net> [HTTP, particles]",
}
"tablecolumns[color;text]" ..
"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
"table[3.5,-0.25;8.5,6.05;list_credits;" ..
+ "#FFFF00," .. fgettext("Dragonfire Team") .. ",," ..
+ buildCreditList(dragonfire_team) .. ",,," ..
"#FFFF00," .. fgettext("Core Developers") .. ",," ..
buildCreditList(core_developers) .. ",,," ..
"#FFFF00," .. fgettext("Active Contributors") .. ",," ..
local packages_raw
local packages
+local function modname_valid(name)
+ return not name:find("[^a-z0-9_]")
+end
+
--------------------------------------------------------------------------------
local function get_formspec(tabview, name, tabdata)
if pkgmgr.global_mods == nil then
table.insert_all(packages_raw, pkgmgr.games)
table.insert_all(packages_raw, pkgmgr.get_texture_packs())
table.insert_all(packages_raw, pkgmgr.global_mods:get_list())
+ table.insert_all(packages_raw, pkgmgr.clientmods:get_list())
local function get_data()
return packages_raw
packages = filterlist.create(get_data, pkgmgr.compare_package,
is_equal, nil, {})
+
+ local filename = core.get_clientmodpath() .. DIR_DELIM .. "mods.conf"
+
+ local conffile = Settings(filename)
+ local mods = conffile:to_table()
+
+ for i = 1, #packages_raw do
+ local mod = packages_raw[i]
+ if mod.is_clientside and not mod.is_modpack then
+ if modname_valid(mod.name) then
+ conffile:set("load_mod_" .. mod.name,
+ mod.enabled and "true" or "false")
+ elseif mod.enabled then
+ gamedata.errormessage = fgettext_ne("Failed to enable clientmo" ..
+ "d \"$1\" as it contains disallowed characters. " ..
+ "Only characters [a-z0-9_] are allowed.",
+ mod.name)
+ end
+ mods["load_mod_" .. mod.name] = nil
+ end
+ end
+
+ -- Remove mods that are not present anymore
+ for key in pairs(mods) do
+ if key:sub(1, 9) == "load_mod_" then
+ conffile:remove(key)
+ end
+ end
+
+ if not conffile:write() then
+ core.log("error", "Failed to write clientmod config file")
+ end
end
if tabdata.selected_pkg == nil then
tabdata.selected_pkg = 1
end
+ local use_technical_names = core.settings:get_bool("show_technical_names")
+
local retval =
"label[0.05,-0.25;".. fgettext("Installed Packages:") .. "]" ..
"tablecolumns[color;tree;text]" ..
"table[0,0.25;5.1,4.3;pkglist;" ..
- pkgmgr.render_packagelist(packages) ..
+ pkgmgr.render_packagelist(packages, use_technical_names) ..
";" .. tabdata.selected_pkg .. "]" ..
"button[0,4.85;5.25,0.5;btn_contentdb;".. fgettext("Browse online content") .. "]"
desc = info.description
end
+ local title_and_name
+ if selected_pkg.type == "game" then
+ title_and_name = selected_pkg.name
+ else
+ title_and_name = (selected_pkg.title or selected_pkg.name) .. "\n" ..
+ core.colorize("#BFBFBF", selected_pkg.name)
+ end
+
retval = retval ..
"image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" ..
- "label[8.25,0.6;" .. core.formspec_escape(selected_pkg.name) .. "]" ..
+ "label[8.25,0.6;" .. core.formspec_escape(title_and_name) .. "]" ..
"box[5.5,2.2;6.15,2.35;#000]"
if selected_pkg.type == "mod" then
if selected_pkg.is_modpack then
- retval = retval ..
- "button[8.65,4.65;3.25,1;btn_mod_mgr_rename_modpack;" ..
- fgettext("Rename") .. "]"
+ if selected_pkg.is_clientside then
+ if pkgmgr.is_modpack_entirely_enabled({list = packages}, selected_pkg.name) then
+ retval = retval ..
+ "button[8.65,4.65;3.25,1;btn_mod_mgr_mp_disable;" ..
+ fgettext("Disable modpack") .. "]"
+ else
+ retval = retval ..
+ "button[8.65,4.65;3.25,1;btn_mod_mgr_mp_enable;" ..
+ fgettext("Enable modpack") .. "]"
+ end
+ else
+ retval = retval ..
+ "button[8.65,4.65;3.25,1;btn_mod_mgr_rename_modpack;" ..
+ fgettext("Rename") .. "]"
+ end
else
--show dependencies
desc = desc .. "\n\n"
"\n" .. toadd_soft
end
end
+ if selected_pkg.is_clientside then
+ if selected_pkg.enabled then
+ retval = retval ..
+ "button[8.65,4.65;3.25,1;btn_mod_mgr_disable_mod;" ..
+ fgettext("Disable") .. "]"
+ else
+ retval = retval ..
+ "button[8.65,4.65;3.25,1;btn_mod_mgr_enable_mod;" ..
+ fgettext("Enable") .. "]"
+ end
+ end
end
else
end
--------------------------------------------------------------------------------
-local function handle_doubleclick(pkg)
+local function handle_doubleclick(pkg, pkg_name)
if pkg.type == "txp" then
if core.settings:get("texture_path") == pkg.path then
core.settings:set("texture_path", "")
core.settings:set("texture_path", pkg.path)
end
packages = nil
+
+ mm_game_theme.init()
+ mm_game_theme.reset()
+ elseif pkg.is_clientside then
+ pkgmgr.enable_mod({data = {list = packages, selected_mod = pkg_name}})
+ packages = nil
end
end
local event = core.explode_table_event(fields["pkglist"])
tabdata.selected_pkg = event.row
if event.type == "DCL" then
- handle_doubleclick(packages:get_list()[tabdata.selected_pkg])
+ handle_doubleclick(packages:get_list()[tabdata.selected_pkg], tabdata.selected_pkg)
end
return true
end
+ if fields.btn_mod_mgr_mp_enable ~= nil or
+ fields.btn_mod_mgr_mp_disable ~= nil then
+ pkgmgr.enable_mod({data = {list = packages, selected_mod = tabdata.selected_pkg}},
+ fields.btn_mod_mgr_mp_enable ~= nil)
+ packages = nil
+ return true
+ end
+
+ if fields.btn_mod_mgr_enable_mod ~= nil or
+ fields.btn_mod_mgr_disable_mod ~= nil then
+ pkgmgr.enable_mod({data = {list = packages, selected_mod = tabdata.selected_pkg}},
+ fields.btn_mod_mgr_enable_mod ~= nil)
+ packages = nil
+ return true
+ end
+
if fields["btn_contentdb"] ~= nil then
local dlg = create_store_dlg()
dlg:set_parent(tabview)
return true
end
- if fields.btn_mod_mgr_use_txp then
- local txp = packages:get_list()[tabdata.selected_pkg]
- core.settings:set("texture_path", txp.path)
- packages = nil
- return true
- end
-
+ if fields.btn_mod_mgr_use_txp or fields.btn_mod_mgr_disable_txp then
+ local txp_path = ""
+ if fields.btn_mod_mgr_use_txp then
+ txp_path = packages:get_list()[tabdata.selected_pkg].path
+ end
- if fields.btn_mod_mgr_disable_txp then
- core.settings:set("texture_path", "")
+ core.settings:set("texture_path", txp_path)
packages = nil
+
+ mm_game_theme.init()
+ mm_game_theme.reset()
return true
end
joystick_id (Joystick ID) int 0
# The type of joystick
- joystick_type (Joystick type) enum auto auto,generic,xbox
+ joystick_type (Joystick type) enum auto auto,generic,xbox,dragonrise_gamecube
# The time in seconds it takes between repeated events
# when holding down a joystick button combination.
repeat_joystick_button_time (Joystick button repetition interval) float 0.17 0.001
- # The deadzone of the joystick
- joystick_deadzone (Joystick deadzone) int 2048
+ # The dead zone of the joystick
+ joystick_deadzone (Joystick dead zone) int 2048
# The sensitivity of the joystick axes for moving the
- # ingame view frustum around.
+ # in-game view frustum around.
joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170
# Key for moving the player forward.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_inventory (Inventory key) key KEY_KEY_I
+# Key for opening the special inventory.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_special_inventory (Special inventory key) key KEY_KEY_O
+
# Key for moving fast in fast mode.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_aux1 (Aux1 key) key KEY_KEY_E
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_zoom (View zoom key) key KEY_KEY_Z
+# Key for toggling Killaura.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_killaura (Killaura key) key KEY_KEY_X
+
+# Key for toggling Freecam.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_freecam (Freecam key) key KEY_KEY_G
+
# Key for selecting the first hotbar slot.
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
keymap_slot1 (Hotbar slot 1 key) key KEY_KEY_1
[**Basic]
- # Whether nametag backgrounds should be shown by default.
+ # Whether name tag backgrounds should be shown by default.
# Mods may still set a background.
- show_nametag_backgrounds (Show nametag backgrounds by default) bool true
+ show_nametag_backgrounds (Show name tag backgrounds by default) bool true
# Enable vertex buffer objects.
# This should greatly improve graphics performance.
# Disable for speed or for different looks.
smooth_lighting (Smooth lighting) bool true
+ # Enables tradeoffs that reduce CPU load or increase rendering performance
+ # at the expense of minor visual glitches that do not impact game playability.
+ performance_tradeoffs (Tradeoffs for performance) bool false
+
# Clouds are a client side effect.
enable_clouds (Clouds) bool true
[**Filtering]
- # Use mip mapping to scale textures. May slightly increase performance,
+ # Use mipmapping to scale textures. May slightly increase performance,
# especially when using a high resolution texture pack.
# Gamma correct downscaling is not supported.
mip_map (Mipmapping) bool false
# Requires shaders to be enabled.
enable_dynamic_shadows (Dynamic shadows) bool false
- # Set the shadow strength.
+ # Set the shadow strength gamma.
+ # Adjusts the intensity of in-game dynamic shadows.
# Lower value means lighter shadows, higher value means darker shadows.
- shadow_strength (Shadow strength) float 0.2 0.05 1.0
+ shadow_strength_gamma (Shadow strength gamma) float 1.0 0.1 10.0
# Maximum distance to render shadows.
shadow_map_max_distance (Shadow map max distance in nodes to render shadows) float 120.0 10.0 1000.0
# On true uses Poisson disk to make "soft shadows". Otherwise uses PCF filtering.
shadow_poisson_filter (Poisson filtering) bool true
- # Define shadow filtering quality
+ # Define shadow filtering quality.
# This simulates the soft shadows effect by applying a PCF or Poisson disk
# but also uses more resources.
shadow_filters (Shadow filter quality) enum 1 0,1,2
- # Enable colored shadows.
+ # Enable colored shadows.
# On true translucent nodes cast colored shadows. This is expensive.
shadow_map_color (Colored shadows) bool false
# Set the soft shadow radius size.
# Lower values mean sharper shadows, bigger values mean softer shadows.
- # Minimum value: 1.0; maxiumum value: 10.0
+ # Minimum value: 1.0; maximum value: 10.0
shadow_soft_radius (Soft shadow radius) float 1.0 1.0 10.0
- # Set the tilt of Sun/Moon orbit in degrees
+ # Set the tilt of Sun/Moon orbit in degrees.
# Value of 0 means no tilt / vertical orbit.
# Minimum value: 0.0; maximum value: 60.0
shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0
# Useful if there's something to be displayed right or left of hotbar.
hud_hotbar_max_width (Maximum hotbar width) float 1.0
- # Modifies the size of the hudbar elements.
+ # Modifies the size of the HUD elements.
hud_scaling (HUD scale factor) float 1.0
# Enables caching of facedir rotated meshes.
# A restart is required after changing this.
show_entity_selectionbox (Show entity selection boxes) bool false
+ # Distance in nodes at which transparency depth sorting is enabled
+ # Use this to limit the performance impact of transparency depth sorting
+ transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128
+
[*Menus]
# Use a cloud animation for the main menu background.
# Append item name to tooltip.
tooltip_append_itemname (Append item name) bool false
- # Whether FreeType fonts are used, requires FreeType support to be compiled in.
- # If disabled, bitmap and XML vectors fonts are used instead.
- freetype (FreeType fonts) bool true
-
font_bold (Font bold by default) bool false
font_italic (Font italic by default) bool false
# Opaqueness (alpha) of the shadow behind the default font, between 0 and 255.
font_shadow_alpha (Font shadow alpha) int 127 0 255
- # Font size of the default font in point (pt).
+ # Font size of the default font where 1 unit = 1 pixel at 96 DPI
font_size (Font size) int 16 1
- # Path to the default font.
- # If “freetype” setting is enabled: Must be a TrueType font.
- # If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
+ # For pixel-style fonts that do not scale well, this ensures that font sizes used
+ # with this font will always be divisible by this value, in pixels. For instance,
+ # a pixel font 16 pixels tall should have this set to 16, so it will only ever be
+ # sized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32.
+ font_size_divisible_by (Font size divisible by) int 1 1
+
+ # Path to the default font. Must be a TrueType font.
# The fallback font will be used if the font cannot be loaded.
font_path (Regular font path) filepath fonts/Arimo-Regular.ttf
font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf
font_path_bold_italic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf
- # Font size of the monospace font in point (pt).
- mono_font_size (Monospace font size) int 15 1
+ # Font size of the monospace font where 1 unit = 1 pixel at 96 DPI
+ mono_font_size (Monospace font size) int 16 1
- # Path to the monospace font.
- # If “freetype” setting is enabled: Must be a TrueType font.
- # If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
+ # For pixel-style fonts that do not scale well, this ensures that font sizes used
+ # with this font will always be divisible by this value, in pixels. For instance,
+ # a pixel font 16 pixels tall should have this set to 16, so it will only ever be
+ # sized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32.
+ mono_font_size_divisible_by (Monospace font size divisible by) int 1 1
+
+ # Path to the monospace font. Must be a TrueType font.
# This font is used for e.g. the console and profiler screen.
mono_font_path (Monospace font path) filepath fonts/Cousine-Regular.ttf
mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf
mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf
- # Path of the fallback font.
- # If “freetype” setting is enabled: Must be a TrueType font.
- # If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
+ # Path of the fallback font. Must be a TrueType font.
# This font will be used for certain languages or if the default font is unavailable.
fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf
screenshot_path (Screenshot folder) path screenshots
# Format of screenshots.
- screenshot_format (Screenshot format) enum png png,jpg,bmp,pcx,ppm,tga
+ screenshot_format (Screenshot format) enum png png,jpg
# Screenshot quality. Only used for JPEG format.
# 1 means worst quality; 100 means best quality.
# Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens.
screen_dpi (DPI) int 72 1
+ # Adjust the detected display density, used for scaling UI elements.
+ display_density_factor (Display Density Scaling Factor) float 1
+
# Windows systems only: Start Minetest with the command line window in the background.
# Contains the same information as the file debug.txt (default name).
enable_console (Enable console window) bool false
[Client]
- # Clickable weblinks (middle-click or ctrl-left-click) enabled in chat console output.
- clickable_chat_weblinks (Chat weblinks) bool false
+ # Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output.
+ clickable_chat_weblinks (Chat weblinks) bool true
# Optional override for chat weblink color.
chat_weblink_color (Weblink color) string
# Set to -1 for unlimited amount.
client_mapblock_limit (Mapblock limit) int 7500
+ # Whether to show technical names.
+ # Affects mods and texture packs in the Content and Select Mods menus, as well as
+ # setting names in All Settings.
+ # Controlled by the checkbox in the "All settings" menu.
+ show_technical_names (Show technical names) bool false
+
# Whether to show the client debug info (has the same effect as hitting F5).
show_debug (Show debug info) bool false
# Compression level to use when sending mapblocks to the client.
# -1 - use default compression level
- # 0 - least compresson, fastest
+ # 0 - least compression, fastest
# 9 - best compression, slowest
map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9
# If this is set, players will always (re)spawn at the given position.
static_spawnpoint (Static spawnpoint) string
- # If enabled, new players cannot join with an empty password.
+ # If enabled, players cannot join without a password or change theirs to an empty password.
disallow_empty_password (Disallow empty passwords) bool false
# If enabled, disable cheat prevention in multiplayer.
deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,error
# Number of extra blocks that can be loaded by /clearobjects at once.
- # This is a trade-off between sqlite transaction overhead and
+ # This is a trade-off between SQLite transaction overhead and
# memory consumption (4096=100MB, as a rule of thumb).
max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096
server_unload_unused_data_timeout (Unload unused server data) int 29
# Maximum number of statically stored objects in a block.
- max_objects_per_block (Maximum objects per block) int 64
+ max_objects_per_block (Maximum objects per block) int 256
# See https://www.sqlite.org/pragma.html#pragma_synchronous
sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2
# Compression level to use when saving mapblocks to disk.
# -1 - use default compression level
- # 0 - least compresson, fastest
+ # 0 - least compression, fastest
# 9 - best compression, slowest
map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9
# Instrument the action function of Loading Block Modifiers on registration.
instrument.lbm (Loading Block Modifiers) bool true
- # Instrument chatcommands on registration.
- instrument.chatcommand (Chatcommands) bool true
+ # Instrument chat commands on registration.
+ instrument.chatcommand (Chat commands) bool true
# Instrument global callback functions on registration.
# (anything you pass to a minetest.register_*() function)
# - action
# - info
# - verbose
- debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose
+ # - trace
+ debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose,trace
# If the file size of debug.txt exceeds the number of megabytes specified in
# this setting when it is opened, the file is moved to debug.txt.1,
debug_log_size_max (Debug log file size threshold) int 50
# Minimal level of logging to be written to chat.
- chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose
+ chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose,trace
# Enable IPv6 support (for both client and server).
# Required for IPv6 connections to work at all.
# Limit of map generation, in nodes, in all 6 directions from (0, 0, 0).
# Only mapchunks completely within the mapgen limit are generated.
# Value is stored per-world.
- mapgen_limit (Map generation limit) int 31000 0 31000
+ mapgen_limit (Map generation limit) int 31007 0 31007
# Global map generation attributes.
# In Mapgen v6 the 'decorations' flag controls all decorations except trees
- # and junglegrass, in all other mapgens this flag controls all decorations.
+ # and jungle grass, in all other mapgens this flag controls all decorations.
mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores
[*Biome API temperature and humidity noise parameters]
# Maximum number of concurrent downloads. Downloads exceeding this limit will be queued.
# This should be lower than curl_parallel_limit.
contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3
- cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_Simple,FM_SimpleMono,FM_MaxMode,FM_Unspecified
+
+[Cheat Menu]
+
+# Font to use for cheat menu
++cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_MaxMode,FM_Unspecified
+
+# (RGB value)
+cheat_menu_bg_color (Cell background color) v3f 255, 145, 88
+
+cheat_menu_bg_color_alpha (Cell background color alpha) int 192
+
+# (RGB value)
+cheat_menu_active_bg_color (Active cell background color) v3f 255, 87, 53
+
+cheat_menu_active_bg_color_alpha (Active cell background color alpha) int 192
+
+# (RGB value)
+cheat_menu_font_color (Font color) v3f 0, 0, 0
+
+cheat_menu_font_color_alpha (Font color alpha) int 255
+
+# (RGB value)
+cheat_menu_selected_font_color (Selected font color) v3f 255, 252, 88
+
+cheat_menu_selected_font_color_alpha (Selected font color alpha) int 255
+
+cheat_menu_head_height (Head height) int 50
+
+cheat_menu_entry_height (Entry height) int 40
+
+cheat_menu_entry_width (Entry width) int 200
+
+[Cheats]
+
+fullbright (Fullbright) bool false
+
+xray (XRay) bool false
+
+xray_nodes (XRay Nodes) string default:stone,mcl_core:stone
+
+priv_bypass (PrivBypass) bool true
+
+fastdig (FastDig) bool false
+
+fastplace (FastPlace) bool false
+
+autodig (AutoDig) bool false
+
+autoplace (AutoPlace) bool false
+
+prevent_natural_damage (NoFallDamage) bool true
+
+freecam (Freecam) bool false
+
+no_hurt_cam (NoHurtCam) bool false
+
+hud_flags_bypass (HUDBypass) bool true
+
+antiknockback (AntiKnockback) bool false
+
+entity_speed (EntitySpeed) bool false
+
+jesus (Jesus) bool false
+
+instant_break (InstantBreak) bool false
+
+no_night (BrightNight) bool false
+
+coords (Coords) bool false
+
+point_liquids (PointLiquids) bool false
+
+spamclick (FastHit) bool false
+
+no_force_rotate (NoForceRotate) bool false
+
+no_slow (NoSlow) bool false
+
+cheat_hud (CheatHUD) bool true
+
+node_esp_nodes (NodeESP Nodes) string
+
+jetpack (JetPack) bool false
+
+autohit (AutoHit) bool false
+
+antislip (AntiSlip) bool false
+
+enable_entity_tracers (EntityTracers) bool false
+
+enable_entity_esp (EntityESP) bool false
+
+enable_player_tracers (PlayerTracers) bool false
+
+enable_player_esp (PlayerESP) bool false
+
+enable_node_esp (NodeESP) bool false
+
+enable_node_tracers (NodeTracers) bool false
+
+entity_esp_color (EntityESP Color) v3f 255, 255, 255
+
+player_esp_color (PlayerESP Color) v3f 0, 255, 0
+
+tool_range (Additional Tool Range) int 2
+
+reach (Reach) bool false
+
+airjump (AirJump) bool false
+
+spider (Spider) bool false
- Minetest Lua Client Modding API Reference 5.5.0
+ Minetest Lua Client Modding API Reference 5.6.0
================================================
* More information at <http://www.minetest.net/>
* Developer Wiki: <http://dev.minetest.net/>
clientmods
├── modname
│ ├── mod.conf
+ | ├── settingtypes.txt
│ ├── init.lua
└── another
+### `settingtypes.txt`
+
+The format is documented in `builtin/settingtypes.txt`.
+It is parsed by the main menu settings dialogue to list mod-specific
+settings in the "Clientmods" category.
+
### modname
The location of this directory.
### pointed_thing
* `{type="nothing"}`
* `{type="node", under=pos, above=pos}`
-* `{type="object", id=ObjectID}`
+* `{type="object", ref=ClientObjectRef}`
Flag Specifier Format
---------------------
* `vector.floor(v)`: returns a vector, each dimension rounded down
* `vector.round(v)`: returns a vector, each dimension rounded to nearest int
* `vector.apply(v, func)`: returns a vector
+ * `vector.combine(v, w, func)`: returns a vector
* `vector.equals(v1, v2)`: returns a boolean
For the following functions `x` can be either a vector or a number:
### Global callback registration functions
Call these functions only at load time!
+* `minetest.get_send_speed(speed)`
+ * This function is called every time the player's speed is sent to server
+ * The `speed` argument is the actual speed of the player
+ * If you define it, you can return a modified `speed`. This speed will be
+ sent to server instead.
+* `minetest.open_enderchest()`
+ * This function is called if the client uses the Keybind for it (by default "O")
+ * You can override it
* `minetest.register_globalstep(function(dtime))`
* Called every client environment step, usually interval of 0.1s
* `minetest.register_on_mods_loaded(function())`
* Adds definition to minetest.registered_chatcommands
* `minetest.unregister_chatcommand(name)`
* Unregisters a chatcommands registered with register_chatcommand.
+* `minetest.register_list_command(command, desc, setting)`
+ * Registers a chatcommand `command` to manage a list that takes the args `del | add | list <param>`
+ * The list is stored comma-seperated in `setting`
+ * `desc` is the description
+ * `add` adds something to the list
+ * `del` del removes something from the list
+ * `list` lists all items on the list
* `minetest.register_on_chatcommand(function(command, params))`
* Called always when a chatcommand is triggered, before `minetest.registered_chatcommands`
is checked to see if that the command exists, but after the input is parsed.
* Called when the local player open inventory
* Newest functions are called first
* If any function returns true, inventory doesn't open
+* `minetest.register_on_recieve_physics_override(function(override))`
+ * Called when recieving physics_override from server
+ * Newest functions are called first
+ * If any function returns true, the physics override does not change
+* `minetest.register_on_play_sound(function(SimpleSoundSpec))`
+ * Called when recieving a play sound command from server
+ * Newest functions are called first
+ * If any function returns true, the sound does not play
+* `minetest.register_on_spawn_partice(function(particle definition))`
+ * Called when recieving a spawn particle command from server
+ * Newest functions are called first
+ * If any function returns true, the particle does not spawn
+* `minetest.register_on_object_add(function(obj))`
+ * Called every time an object is added
+* `minetest.register_on_object_properties_change(function(obj))`
+ * Called every time the properties of an object are changed server-side
+ * May modify the object's properties without the fear of infinite recursion
+* `minetest.register_on_object_hp_change(function(obj))`
+ * Called every time the hp of an object are changes server-side
+
+### Setting-related
+* `minetest.settings`: Settings object containing all of the settings from the
+ main config file (`minetest.conf`).
+* `minetest.setting_get_pos(name)`: Loads a setting from the main settings and
+ parses it as a position (in the format `(1,2,3)`). Returns a position or nil.
+
### Sounds
* `minetest.sound_play(spec, parameters)`: returns a handle
* `spec` is a `SimpleSoundSpec`
* Returns the time of day: `0` for midnight, `0.5` for midday
### Map
+* `minetest.interact(action, pointed_thing)`
+ * Sends an interaction to the server
+ * `pointed_thing` is a pointed_thing
+ * `action` is one of
+ * "start_digging": Use to punch nodes / objects
+ * "stop_digging": Use to abort digging a "start_digging" command
+ * "digging_completed": Use to finish a "start_digging" command or dig a node instantly
+ * "place": Use to rightclick nodes and objects
+ * "use": Use to leftclick an item in air (pointed_thing.type is usually "nothing")
+ * "activate": Same as "use", but rightclick
+* `minetest.place_node(pos)`
+ * Places the wielded node/item of the player at pos.
+* `minetest.dig_node(pos)`
+ * Instantly digs the node at pos. This may fuck up with anticheat.
* `minetest.get_node_or_nil(pos)`
* Returns the node at the given position as table in the format
`{name="node_name", param1=0, param2=0}`, returns `nil`
* `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
* `search_center` is an optional boolean (default: `false`)
If true `pos` is also checked for the nodes
+* `minetest.find_nodes_near(pos, radius, nodenames, [search_center])`: returns a
+ list of positions.
+ * `radius`: using a maximum metric
+ * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
+ * `search_center` is an optional boolean (default: `false`)
+ If true `pos` is also checked for the nodes
+* `minetest.find_nodes_near_under_air(pos, radius, nodenames, [search_center])`: returns a
+ list of positions.
+ * `radius`: using a maximum metric
+ * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
+ * `search_center` is an optional boolean (default: `false`)
+ If true `pos` is also checked for the nodes
+ * Return value: Table with all node positions with a node air above
+* `minetest.find_nodes_near_under_air_except(pos, radius, nodenames, [search_center])`: returns a
+ list of positions.
+ * `radius`: using a maximum metric
+ * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`, specifies the nodes to be ignored
+ * `search_center` is an optional boolean (default: `false`)
+ If true `pos` is also checked for the nodes
+ * Return value: Table with all node positions with a node air above
* `minetest.find_nodes_in_area(pos1, pos2, nodenames, [grouped])`
* `pos1` and `pos2` are the min and max positions of the area to search.
* `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
* `pos2`: end of the ray
* `objects`: if false, only nodes will be returned. Default is `true`.
* `liquids`: if false, liquid nodes won't be returned. Default is `false`.
-
+* `minetest.get_pointed_thing()` returns `PointedThing`
+ * Returns the thing currently pointed by player
+* `minetest.get_pointed_thing_position(pointed_thing, above)`
+ * Returns the position of a `pointed_thing` or `nil` if the `pointed_thing`
+ does not refer to a node or entity.
+ * If the optional `above` parameter is true and the `pointed_thing` refers
+ to a node, then it will return the `above` position of the `pointed_thing`.
+* `minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm)`
+ * returns table containing path that can be walked on
+ * returns a table of 3D points representing a path from `pos1` to `pos2` or
+ `nil` on failure.
+ * Reasons for failure:
+ * No path exists at all
+ * No path exists within `searchdistance` (see below)
+ * Start or end pos is buried in land
+ * `pos1`: start position
+ * `pos2`: end position
+ * `searchdistance`: maximum distance from the search positions to search in.
+ In detail: Path must be completely inside a cuboid. The minimum
+ `searchdistance` of 1 will confine search between `pos1` and `pos2`.
+ Larger values will increase the size of this cuboid in all directions
+ * `max_jump`: maximum height difference to consider walkable
+ * `max_drop`: maximum height difference to consider droppable
+ * `algorithm`: One of `"A*_noprefetch"` (default), `"A*"`, `"Dijkstra"`.
+ Difference between `"A*"` and `"A*_noprefetch"` is that
+ `"A*"` will pre-calculate the cost-data, the other will calculate it
+ on-the-fly
* `minetest.find_nodes_with_meta(pos1, pos2)`
* Get a table of positions of nodes that have metadata within a region
{pos1, pos2}.
* get max available level for leveled node
### Player
+* `minetest.send_damage(hp)`
+ * Sends fall damage to server
* `minetest.send_chat_message(message)`
* Act as if `message` was typed by the player into the terminal.
* `minetest.run_server_chatcommand(cmd, param)`
* Alias for `minetest.send_chat_message("/" .. cmd .. " " .. param)`
* `minetest.clear_out_chat_queue()`
* Clears the out chat queue
+* `minetest.drop_selected_item()`
+ * Drops the selected item
* `minetest.localplayer`
* Reference to the LocalPlayer object. See [`LocalPlayer`](#localplayer) class reference for methods.
* Convert between two privilege representations
### Client Environment
+* `minetest.object_refs`
+ * Map of object references, indexed by active object id
* `minetest.get_player_names()`
* Returns list of player names on server (nil if CSM_RF_READ_PLAYERINFO is enabled by server)
+* `minetest.get_objects_inside_radius(pos, radius)`: returns a list of
+ ClientObjectRefs.
+ * `radius`: using an euclidean metric
+* `minetest.get_nearby_objects(radius)`
+ * alias for minetest.get_objects_inside_radius(minetest.localplayer:get_pos(), radius)
* `minetest.disconnect()`
* Disconnect from the server and exit to main menu.
* Returns `false` if the client is already disconnecting otherwise returns `true`.
* `minetest.send_respawn()`
* Sends a respawn request to the server.
+### HTTP Requests
+
+* `minetest.request_http_api()`:
+ * returns `HTTPApiTable` containing http functions if the calling mod has
+ been granted access by being listed in the `secure.http_mods` or
+ `secure.trusted_mods` setting, otherwise returns `nil`.
+ * The returned table contains the functions `fetch`, `fetch_async` and
+ `fetch_async_get` described below.
+ * Only works at init time and must be called from the mod's main scope
+ (not from a function).
+ * Function only exists if minetest server was built with cURL support.
+ * **DO NOT ALLOW ANY OTHER MODS TO ACCESS THE RETURNED TABLE, STORE IT IN
+ A LOCAL VARIABLE!**
+* `HTTPApiTable.fetch(HTTPRequest req, callback)`
+ * Performs given request asynchronously and calls callback upon completion
+ * callback: `function(HTTPRequestResult res)`
+ * Use this HTTP function if you are unsure, the others are for advanced use
+* `HTTPApiTable.fetch_async(HTTPRequest req)`: returns handle
+ * Performs given request asynchronously and returns handle for
+ `HTTPApiTable.fetch_async_get`
+* `HTTPApiTable.fetch_async_get(handle)`: returns HTTPRequestResult
+ * Return response data for given asynchronous HTTP request
+
+### `HTTPRequest` definition
+
+Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`.
+
+ {
+ url = "http://example.org",
+
+ timeout = 10,
+ -- Timeout for connection in seconds. Default is 3 seconds.
+
+ post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"},
+ -- Optional, if specified a POST request with post_data is performed.
+ -- Accepts both a string and a table. If a table is specified, encodes
+ -- table as x-www-form-urlencoded key-value pairs.
+ -- If post_data is not specified, a GET request is performed instead.
+
+ user_agent = "ExampleUserAgent",
+ -- Optional, if specified replaces the default minetest user agent with
+ -- given string
+
+ extra_headers = { "Accept-Language: en-us", "Accept-Charset: utf-8" },
+ -- Optional, if specified adds additional headers to the HTTP request.
+ -- You must make sure that the header strings follow HTTP specification
+ -- ("Key: Value").
+
+ multipart = boolean
+ -- Optional, if true performs a multipart HTTP request.
+ -- Default is false.
+ }
+
+### `HTTPRequestResult` definition
+
+Passed to `HTTPApiTable.fetch` callback. Returned by
+`HTTPApiTable.fetch_async_get`.
+
+ {
+ completed = true,
+ -- If true, the request has finished (either succeeded, failed or timed
+ -- out)
+
+ succeeded = true,
+ -- If true, the request was successful
+
+ timeout = false,
+ -- If true, the request timed out
+
+ code = 200,
+ -- HTTP status code
+
+ data = "response"
+ }
+
### Storage API
* `minetest.get_mod_storage()`:
* returns reference to mod private `StorageRef`
* `minetest.delete_particlespawner(id)`
* Delete `ParticleSpawner` with `id` (return value from `minetest.add_particlespawner`)
-### Misc.
+### Misc
+* `minetest.set_keypress(key, value)`
+ * Act as if a key was pressed (value = true) / released (value = false)
+ * The key must be an keymap_* setting
+ * e.g. minetest.set_keypress("jump", true) will cause te player to jump until minetest.set_keypress("jump", false) is called or the player presses & releases the space bar himself
+* `minetest.get_inventory(location)`
+ * Returns the inventory at location
+* `minetest.find_item(item)`
+ * finds and an item in the inventory
+ * returns index on success or nil if item is not found
+* `minetest.switch_to_item(item)`
+ * `item` is an Itemstring
+ * searches to item in inventory, sets the wield index to it if found
+ * returns true on success, false if item was not found
+* `minetest.register_cheat(name, category, setting | function)`
+ * Register an entry for the cheat menu
+ * If the Category is nonexistant, it will be created
+ * If the 3rd argument is a string it will be interpreted as a setting and toggled
+ when the player selects the entry in the cheat menu
+ * If the 3rd argument is a function it will be called
+ when the player selects the entry in the cheat menu
* `minetest.parse_json(string[, nullvalue])`: returns something
* Convert a string containing JSON data into the Lua equivalent
* `nullvalue`: returned in place of the JSON null; defaults to `nil`
* returns the exact position on the surface of a pointed node
* `minetest.global_exists(name)`
* Checks if a global variable has been set, without triggering a warning.
+* `minetest.make_screenshot()`
+ * Triggers the MT makeScreenshot functionality
+* `minetest.request_insecure_environment()`: returns an environment containing
+ insecure functions if the calling mod has been listed as trusted in the
+ `secure.trusted_mods` setting or security is disabled, otherwise returns
+ `nil`.
+ * Only works at init time and must be called from the mod's main scope
+ (ie: the init.lua of the mod, not from another Lua file or within a function).
+ * **DO NOT ALLOW ANY OTHER MODS TO ACCESS THE RETURNED ENVIRONMENT, STORE
+ IT IN A LOCAL VARIABLE!**
### UI
* `minetest.ui.minimap`
* Reference to the camera object. See [`Camera`](#camera) class reference for methods.
* `minetest.show_formspec(formname, formspec)` : returns true on success
* Shows a formspec to the player
+* `minetest.close_formspec(formname)`
+ * `formname`: has to exactly match the one given in `show_formspec`, or the
+ formspec will not close.
+ * calling `show_formspec(formname, "")` is equal to this
+ expression.
+ * to close a formspec regardless of the formname, call
+ `minetest.close_formspec("")`.
+ **USE THIS ONLY WHEN ABSOLUTELY NECESSARY!**
* `minetest.display_chat_message(message)` returns true on success
* Shows a chat message to the current player.
* `get_pos()`
* returns current player current position
+* `set_pos(pos)`
+ * sets the position (anticheat may not like this)
+* `get_yaw()`
+ * returns the yaw (degrees)
+* `set_yaw(yaw)`
+ * sets the yaw (degrees)
+* `get_pitch()`
+ * returns the pitch (degrees)
+* `set_pitch(pitch)`
+ * sets the pitch (degrees)
* `get_velocity()`
* returns player speed vector
+* `set_velocity(vel)`
+ * sets player speed vector
* `get_hp()`
* returns player HP
* `get_name()`
* returns player name
* `get_wield_index()`
- * returns the index of the wielded item
+ * returns the index of the wielded item (starts at 1)
+* `set_wield_index()`
+ * sets the index (starts at 1)
* `get_wielded_item()`
* returns the itemstack the player is holding
* `is_attached()`
* returns true if player is in a liquid (This oscillates so that the player jumps a bit above the surface)
* `is_in_liquid_stable()`
* returns true if player is in a stable liquid (This is more stable and defines the maximum speed of the player)
- * `get_liquid_viscosity()`
- * returns liquid viscosity (Gets the viscosity of liquid to calculate friction)
+ * `get_move_resistance()`
+ * returns move resistance of current node, the higher the slower the player moves
* `is_climbing()`
* returns true if player is climbing
* `swimming_vertical()`
}
```
+* `set_physics_override(override_table)`
+ * `override_table` is a table with the following fields:
+ * `speed`: multiplier to default walking speed value (default: `1`)
+ * `jump`: multiplier to default jump value (default: `1`)
+ * `gravity`: multiplier to default gravity value (default: `1`)
+ * `sneak`: whether player can sneak (default: `true`)
+ * `sneak_glitch`: whether player can use the new move code replications
+ of the old sneak side-effects: sneak ladders and 2 node sneak jump
+ (default: `false`)
+ * `new_move`: use new move/sneak code. When `false` the exact old code
+ is used for the specific old sneak behaviour (default: `true`)
* `get_override_pos()`
* returns override position
* `get_last_pos()`
* change a value of a previously added HUD element
* element `stat` values: `position`, `name`, `scale`, `text`, `number`, `item`, `dir`
* Returns `true` on success, otherwise returns `nil`
+* `get_object()`
+ * Returns the ClientObjectRef for the player
### Settings
An interface to read config files in the format of `minetest.conf`.
* `fields`: key-value storage
* `inventory`: `{list1 = {}, ...}}`
+### ClientObjectRef
+
+Moving things in the game are generally these.
+This is basically a reference to a C++ `GenericCAO`.
+
+#### Methods
+
+* `get_pos()`: returns `{x=num, y=num, z=num}`
+* `get_velocity()`: returns the velocity, a vector
+* `get_acceleration()`: returns the acceleration, a vector
+* `get_rotation()`: returns the rotation, a vector (radians)
+* `is_player()`: returns true if the object is a player
+* `is_local_player()`: returns true if the object is the local player
+* `get_attach()`: returns parent or nil if it isn't attached.
+* `get_nametag()`: returns the nametag (deprecated, use get_properties().nametag instead)
+* `get_item_textures()`: returns the textures (deprecated, use get_properties().textures instead)
+* `get_max_hp()`: returns the maximum heath (deprecated, use get_properties().hp_max instead)
+* `set_properties(object property table)`
+* `get_properties()`: returns object property table
+* `punch()`: punches the object
+* `rightclick()`: rightclicks the object
+* `remove()`: removes the object permanently
+* `set_nametag_images(images)`: Provides a list of images to be drawn below the nametag
+
### `Raycast`
A raycast on the map. It works with selection boxes.
-----------------
### Definitions
+* `minetest.inventorycube(img1, img2, img3)`
+ * Returns a string for making an image of a cube (useful as an item image)
* `minetest.get_node_def(nodename)`
* Returns [node definition](#node-definition) table of `nodename`
* `minetest.get_item_def(itemstring)`
* Returns item definition table of `itemstring`
+* `minetest.override_item(itemstring, redefinition)`
+ * Overrides fields of an item registered with register_node/tool/craftitem.
+ * Note: Item must already be defined by the server
+ * Example: `minetest.override_item("default:mese",
+ {light_source=minetest.LIGHT_MAX})`
+ * Doesnt really work yet an causes strange bugs, I'm working to make is better
+
+
+#### Tile definition
+
+* `"image.png"`
+* `{name="image.png", animation={Tile Animation definition}}`
+* `{name="image.png", backface_culling=bool, align_style="node"/"world"/"user", scale=int}`
+ * backface culling enabled by default for most nodes
+ * align style determines whether the texture will be rotated with the node
+ or kept aligned with its surroundings. "user" means that client
+ setting will be used, similar to `glasslike_framed_optional`.
+ Note: supported by solid nodes and nodeboxes only.
+ * scale is used to make texture span several (exactly `scale`) nodes,
+ instead of just one, in each direction. Works for world-aligned
+ textures only.
+ Note that as the effect is applied on per-mapblock basis, `16` should
+ be equally divisible by `scale` or you may get wrong results.
+* `{name="image.png", color=ColorSpec}`
+ * the texture's color will be multiplied with this color.
+ * the tile's color overrides the owning node's color in all cases.
+
+##### Tile definition
+
+ {
+ type = "vertical_frames",
+
+ aspect_w = 16,
+ -- Width of a frame in pixels
+
+ aspect_h = 16,
+ -- Height of a frame in pixels
+
+ length = 3.0,
+ -- Full loop length
+ }
+
+ {
+ type = "sheet_2d",
+
+ frames_w = 5,
+ -- Width in number of frames
+
+ frames_h = 3,
+ -- Height in number of frames
+
+ frame_length = 0.5,
+ -- Length of a single frame
+ }
#### Node Definition
```lua
{
+ tiles = {tile definition 1, def2, def3, def4, def5, def6},
+ -- Textures of node; +Y, -Y, +X, -X, +Z, -Z
+ overlay_tiles = {tile definition 1, def2, def3, def4, def5, def6},
+ -- Same as `tiles`, but these textures are drawn on top of the base
+ -- tiles. This is used to colorize only specific parts of the
+ -- texture. If the texture name is an empty string, that overlay is not
+ -- drawn
+ special_tiles = {tile definition 1, Tile definition 2},
+ -- Special textures of node; used rarely.
has_on_construct = bool, -- Whether the node has the on_construct callback defined
has_on_destruct = bool, -- Whether the node has the on_destruct callback defined
has_after_destruct = bool, -- Whether the node has the after_destruct callback defined
liquid_type = <string>, -- A string containing "none", "flowing", or "source" *May not exist*
liquid_alternative_flowing = <string>, -- Alternative node for liquid *May not exist*
liquid_alternative_source = <string>, -- Alternative node for liquid *May not exist*
- liquid_viscosity = <number>, -- How fast the liquid flows *May not exist*
+ liquid_viscosity = <number>, -- How slow the liquid flows *May not exist*
liquid_renewable = <boolean>, -- Whether the liquid makes an infinite source *May not exist*
liquid_range = <number>, -- How far the liquid flows *May not exist*
drowning = bool, -- Whether the player will drown in the node
},
legacy_facedir_simple = bool, -- Whether to use old facedir
legacy_wallmounted = bool -- Whether to use old wallmounted
+ move_resistance = <number>, -- How slow players can move through the node *May not exist*
}
```
`minetest.get_color_escape_sequence(color) ..
message ..
minetest.get_color_escape_sequence("#ffffff")`
+* `minetest.rainbow(message)`:
+ * Rainbow colorizes the message.
* `minetest.get_background_escape_sequence(color)`
* `color` is a [ColorString](#colorstring)
* The escape sequence sets the background of the whole text element to
texture = "image.png",
-- ^ Uses texture (string)
}
+
+### InventoryAction
+A reference to a C++ InventoryAction. You can move, drop and craft items in all accessible inventories using InventoryActions.
+
+#### methods
+
+* `InventoryAction(type)`:
+ * creates a new InventoryAction
+ * type is on of "move", "drop", or "craft", else returns nil
+ * indexing starts at 1
+* `apply()`:
+ * applies the InventoryAction (InventoryActions can be applied multible times)
+* `from(inventorylocation, listname, stack)`
+ * this is valid for move or drop actions
+ * when `apply()` is called items are moved / dropped from `listname` `inventorylocation` in` at `stack`
+* `to(inventorylocation, listname, stack)`
+ * this is valid for move actions
+ * when `apply()` is called items are moved to `listname` in`inventorylocation` at `stack`
+* `craft(inventoryaction)`
+ * this is valid for craft actions
+ * when `apply()` is called a craft event for this inventory will be triggered
+* `set_count(count)`
+ * this is valid for all actions
+ * it specifies how many items to drop / craft / move
+ * `0` means move all items
+ * default count: `0`
+
+#### example
+ `local move_act = InventoryAction("move")
+ move_act:from("current_player", "main", 1)
+ move_act:to("current_player", "craft", 1)
+ move_act:set_count(1)
+ local craft_act = InventoryAction("craft")
+ craft_act:craft("current_player")
+ local drop_act = InventoryAction("drop")
+ drop_act:from("current_player", "craft_result",10)
+ move_act:apply()
+ craft_act:apply()
+ drop_act:apply()
+ `
+ * e.g. In first hotbar slot there are tree logs: Move one to craft field, then craft wood out of it and immediately drop it
+
+
+
+
* `disallowed_mapgen_settings= <comma-separated mapgen settings>`
e.g. `disallowed_mapgen_settings = mgv5_spflags`
These mapgen settings are hidden for this game in the world creation
- dialog and game start menu.
+ dialog and game start menu. Add `seed` to hide the seed input field.
* `disabled_settings = <comma-separated settings>`
e.g. `disabled_settings = enable_damage, creative_mode`
These settings are hidden for this game in the "Start game" tab
images named like `$identifier.$n.png`, with an ascending number $n starting
with 1, and a random image will be chosen from the provided ones.
+ Menu music
+ -----------
+ Games can provide custom main menu music. They are put inside a `menu`
+ directory inside the game directory.
+ The music files are named `theme.ogg`.
+ If you want to specify multiple music files for one game, add additional
+ images named like `theme.$n.ogg`, with an ascending number $n starting
+ with 1 (max 10), and a random music file will be chosen from the provided ones.
Mods
====
color, meaning white surfaces get a lot of your new color while black parts
don't change very much.
+ #### `[png:<base64>`
+
+ Embed a base64 encoded PNG image in the texture string.
+ You can produce a valid string for this by calling
+ `minetest.encode_base64(minetest.encode_png(tex))`,
+ refer to the documentation of these functions for details.
+ You can use this to send disposable images such as captchas
+ to individual clients, or render things that would be too
+ expensive to compose with `[combine:`.
+
+ IMPORTANT: Avoid sending large images this way.
+ This is not a replacement for asset files, do not use it to do anything
+ that you could instead achieve by just using a file.
+ In particular consider `minetest.dynamic_add_media` and test whether
+ using other texture modifiers could result in a shorter string than
+ embedding a whole image, this may vary by use case.
+
Hardware coloring
-----------------
`param1` is reserved for the engine when `paramtype != "none"`.
* `paramtype = "light"`
- * The value stores light with and without sun in its upper and lower 4 bits
+ * The value stores light with and without sun in its lower and upper 4 bits
respectively.
* Required by a light source node to enable spreading its light.
* Required by the following drawtypes as they determine their visual
Representations of simple things
================================
- Position/vector
- ---------------
-
- {x=num, y=num, z=num}
+ Vector (ie. a position)
+ -----------------------
- Note: it is highly recommended to construct a vector using the helper function:
- vector.new(num, num, num)
+ vector.new(x, y, z)
- For helper functions see [Spatial Vectors].
+ See [Spatial Vectors] for details.
`pointed_thing`
---------------
from 1).
* `pointed_thing.intersection_normal`: Unit vector, points outwards of the
selected selection box. This specifies which face is pointed at.
- Is a null vector `{x = 0, y = 0, z = 0}` when the pointer is inside the
- selection box.
+ Is a null vector `vector.zero()` when the pointer is inside the selection box.
Examples:
- * `'default:apple'`: 1 apple
- * `'default:dirt 5'`: 5 dirt
- * `'default:pick_stone'`: a new stone pickaxe
- * `'default:pick_wood 1 21323'`: a wooden pickaxe, ca. 1/3 worn out
+ * `"default:apple"`: 1 apple
+ * `"default:dirt 5"`: 5 dirt
+ * `"default:pick_stone"`: a new stone pickaxe
+ * `"default:pick_wood 1 21323"`: a wooden pickaxe, ca. 1/3 worn out
### Table format
An example: Make meat soup from any meat, any water and any bowl:
{
- output = 'food:meat_soup_raw',
+ output = "food:meat_soup_raw",
recipe = {
- {'group:meat'},
- {'group:water'},
- {'group:bowl'},
+ {"group:meat"},
+ {"group:water"},
+ {"group:bowl"},
},
- -- preserve = {'group:bowl'}, -- Not implemented yet (TODO)
+ -- preserve = {"group:bowl"}, -- Not implemented yet (TODO)
}
Another example: Make red wool from white wool and red dye:
{
- type = 'shapeless',
- output = 'wool:red',
- recipe = {'wool:white', 'group:dye,basecolor_red'},
+ type = "shapeless",
+ output = "wool:red",
+ recipe = {"wool:white", "group:dye,basecolor_red"},
}
Special groups
- (14) -- constant tolerance
Negative damage values are discarded as no damage.
* `falling_node`: if there is no walkable block under the node it will fall
- * `float`: the node will not fall through liquids
+ * `float`: the node will not fall through liquids (`liquidtype ~= "none"`)
* `level`: Can be used to give an additional sense of progression in the game.
* A larger level will cause e.g. a weapon of a lower level make much less
damage, and get worn out much faster, or not be able to get drops
### Uses (tools only)
Determines how many uses the tool has when it is used for digging a node,
- of this group, of the maximum level. For lower leveled nodes, the use count
- is multiplied by `3^leveldiff`.
+ of this group, of the maximum level. The maximum supported number of
+ uses is 65535. The special number 0 is used for infinite uses.
+ For lower leveled nodes, the use count is multiplied by `3^leveldiff`.
`leveldiff` is the difference of the tool's `maxlevel` `groupcaps` and the
node's `level` group. The node cannot be dug if `leveldiff` is less than zero.
* `color`: A `ColorString`, which sets the stack's color.
* `palette_index`: If the item has a palette, this is used to get the
current color from the palette.
+ * `count_meta`: Replace the displayed count with any string.
+ * `count_alignment`: Set the alignment of the displayed count value. This is an
+ int value. The lowest 2 bits specify the alignment in x-direction, the 3rd and
+ 4th bit specify the alignment in y-direction:
+ 0 = default, 1 = left / up, 2 = middle, 3 = right / down
+ The default currently is the same as right/down.
+ Example: 6 = 2 + 1*4 = middle,up
Example:
meta:set_string("key", "value")
print(dump(meta:to_table()))
+ Example manipulations of "description" and expected output behaviors:
+
+ print(ItemStack("default:pick_steel"):get_description()) --> Steel Pickaxe
+ print(ItemStack("foobar"):get_description()) --> Unknown Item
+
+ local stack = ItemStack("default:stone")
+ stack:get_meta():set_string("description", "Custom description\nAnother line")
+ print(stack:get_description()) --> Custom description\nAnother line
+ print(stack:get_short_description()) --> Custom description
+
+ stack:get_meta():set_string("short_description", "Short")
+ print(stack:get_description()) --> Custom description\nAnother line
+ print(stack:get_short_description()) --> Short
+
+ print(ItemStack("mod:item_with_no_desc"):get_description()) --> mod:item_with_no_desc
Version History
---------------
- * FORMSPEC VERSION 1:
+ * Formspec version 1 (pre-5.1.0):
* (too much)
- * FORMSPEC VERSION 2:
+ * Formspec version 2 (5.1.0):
* Forced real coordinates
* background9[]: 9-slice scaling parameters
- * FORMSPEC VERSION 3:
+ * Formspec version 3 (5.2.0):
* Formspec elements are drawn in the order of definition
* bgcolor[]: use 3 parameters (bgcolor, formspec (now an enum), fbgcolor)
* box[] and image[] elements enable clipping by default
* new element: scroll_container[]
- * FORMSPEC VERSION 4:
+ * Formspec version 4 (5.4.0):
* Allow dropdown indexing events
+ * Formspec version 5 (5.5.0):
+ * Added padding[] element
Elements
--------
* `position` and `anchor` elements need suitable values to avoid a formspec
extending off the game window due to particular game window sizes.
- ### `no_prepend[]`
+ ### `padding[<X>,<Y>]`
* Must be used after the `size`, `position`, and `anchor` elements (if present).
+ * Defines how much space is padded around the formspec if the formspec tries to
+ increase past the size of the screen and coordinates have to be shrunk.
+ * For X and Y, 0.0 represents no padding (the formspec can touch the edge of the
+ screen), and 0.5 represents half the screen (which forces the coordinate size
+ to 0). If negative, the formspec can extend off the edge of the screen.
+ * Defaults to [0.05, 0.05].
+
+ ### `no_prepend[]`
+
+ * Must be used after the `size`, `position`, `anchor`, and `padding` elements
+ (if present).
* Disables player:set_formspec_prepend() from applying to this formspec.
### `real_coordinates[<bool>]`
* End of a scroll_container, following elements are no longer bound to this
container.
- ### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;]`
+ ### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]`
- * Show an inventory list if it has been sent to the client. Nothing will
- be shown if the inventory list is of size 0.
+ * Show an inventory list if it has been sent to the client.
+ * If the inventory list changes (eg. it didn't exist before, it's resized, or its items
+ are moved) while the formspec is open, the formspec element may (but is not guaranteed
+ to) adapt to the new inventory list.
+ * Item slots are drawn in a grid from left to right, then up to down, ordered
+ according to the slot index.
+ * `W` and `H` are in inventory slots, not in coordinates.
+ * `starting item index` (Optional): The index of the first (upper-left) item to draw.
+ Indices start at `0`. Default is `0`.
+ * The number of shown slots is the minimum of `W*H` and the inventory list's size minus
+ `starting item index`.
* **Note**: With the new coordinate system, the spacing between inventory
slots is one-fourth the size of an inventory slot by default. Also see
[Styling Formspecs] for changing the size of slots and spacing.
- ### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]`
-
- * Show an inventory list if it has been sent to the client. Nothing will
- be shown if the inventory list is of size 0.
- * **Note**: With the new coordinate system, the spacing between inventory
- slots is one-fourth the size of an inventory slot.
-
### `listring[<inventory location>;<list name>]`
* Allows to create a ring of inventory lists
`#RRGGBBAA` defines a color in hexadecimal format and alpha channel.
Named colors are also supported and are equivalent to
- [CSS Color Module Level 4](http://dev.w3.org/csswg/css-color/#named-colors).
+ [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/#named-color).
To specify the value of the alpha channel, append `#A` or `#AA` to the end of
the color name (e.g. `colorname#08`).
`minetest.get_color_escape_sequence(color) ..
message ..
minetest.get_color_escape_sequence("#ffffff")`
+* `minetest.rainbow(message)`:
+ * Rainbow colorizes the message.
* `minetest.get_background_escape_sequence(color)`
* `color` is a ColorString
* The escape sequence sets the background of the whole text element to
Spatial Vectors
===============
- A spatial vector is similar to a position, but instead using
- absolute world coordinates, it uses *relative* coordinates, relative to
- no particular point.
-
- Internally, it is implemented as a table with the 3 fields
- `x`, `y` and `z`. Example: `{x = 0, y = 1, z = 0}`.
- However, one should *never* create a vector manually as above, such misbehavior
- is deprecated. The vector helpers set a metatable for the created vectors which
- allows indexing with numbers, calling functions directly on vectors and using
- operators (like `+`). Furthermore, the internal implementation might change in
+
+ Minetest stores 3-dimensional spatial vectors in Lua as tables of 3 coordinates,
+ and has a class to represent them (`vector.*`), which this chapter is about.
+ For details on what a spatial vectors is, please refer to Wikipedia:
+ https://en.wikipedia.org/wiki/Euclidean_vector.
+
+ Spatial vectors are used for various things, including, but not limited to:
+
+ * any 3D spatial vector (x/y/z-directions)
+ * Euler angles (pitch/yaw/roll in radians) (Spatial vectors have no real semantic
+ meaning here. Therefore, most vector operations make no sense in this use case.)
+
+ Note that they are *not* used for:
+
+ * n-dimensional vectors where n is not 3 (ie. n=2)
+ * arrays of the form `{num, num, num}`
+
+ The API documentation may refer to spatial vectors, as produced by `vector.new`,
+ by any of the following notations:
+
+ * `(x, y, z)` (Used rarely, and only if it's clear that it's a vector.)
+ * `vector.new(x, y, z)`
+ * `{x=num, y=num, z=num}` (Even here you are still supposed to use `vector.new`.)
+
+ Compatibility notes
+ -------------------
+
+ Vectors used to be defined as tables of the form `{x = num, y = num, z = num}`.
+ Since Minetest 5.5.0, vectors additionally have a metatable to enable easier use.
+ Note: Those old-style vectors can still be found in old mod code. Hence, mod and
+ engine APIs still need to be able to cope with them in many places.
+
+ Manually constructed tables are deprecated and highly discouraged. This interface
+ should be used to ensure seamless compatibility between mods and the Minetest API.
+ This is especially important to callback function parameters and functions overwritten
+ by mods.
+ Also, though not likely, the internal implementation of a vector might change in
the future.
- Old code might still use vectors without metatables, be aware of this!
+ In your own code, or if you define your own API, you can, of course, still use
+ other representations of vectors.
+
+ Vectors provided by API functions will provide an instance of this class if not
+ stated otherwise. Mods should adapt this for convenience reasons.
+
+ Special properties of the class
+ -------------------------------
+
+ Vectors can be indexed with numbers and allow method and operator syntax.
All these forms of addressing a vector `v` are valid:
`v[1]`, `v[3]`, `v.x`, `v[1] = 42`, `v.y = 13`
+ Note: Prefer letter over number indexing for performance and compatibility reasons.
Where `v` is a vector and `foo` stands for any function name, `v:foo(...)` does
the same as `vector.foo(v, ...)`, apart from deprecated functionality.
+ `tostring` is defined for vectors, see `vector.to_string`.
+
The metatable that is used for vectors can be accessed via `vector.metatable`.
Do not modify it!
All `vector.*` functions allow vectors `{x = X, y = Y, z = Z}` without metatables.
Returned vectors always have a metatable set.
- For the following functions, `v`, `v1`, `v2` are vectors,
- `p1`, `p2` are positions,
+ Common functions and methods
+ ----------------------------
+
+ For the following functions (and subchapters),
+ `v`, `v1`, `v2` are vectors,
+ `p1`, `p2` are position vectors,
`s` is a scalar (a number),
vectors are written like this: `(x, y, z)`:
* `init`: If given starts looking for the vector at this string index.
* `vector.to_string(v)`:
* Returns a string of the form `"(x, y, z)"`.
+ * `tostring(v)` does the same.
* `vector.direction(p1, p2)`:
* Returns a vector of length 1 with direction `p1` to `p2`.
* If `p1` and `p2` are identical, returns `(0, 0, 0)`.
* `vector.apply(v, func)`:
* Returns a vector where the function `func` has been applied to each
component.
+ * `vector.combine(v, w, func)`:
+ * Returns a vector where the function `func` has combined both components of `v` and `w`
+ for each component
* `vector.equals(v1, v2)`:
* Returns a boolean, `true` if the vectors are identical.
* `vector.sort(v1, v2)`:
* Returns the cross product of `v1` and `v2`.
* `vector.offset(v, x, y, z)`:
* Returns the sum of the vectors `v` and `(x, y, z)`.
- * `vector.check()`:
+ * `vector.check(v)`:
* Returns a boolean value indicating whether `v` is a real vector, eg. created
by a `vector.*` function.
* Returns `false` for anything else, including tables like `{x=3,y=1,z=4}`.
* Returns a scaled vector.
* Deprecated: If `s` is a vector: Returns the Schur quotient.
+ Operators
+ ---------
+
Operators can be used if all of the involved vectors have metatables:
* `v1 == v2`:
* Returns whether `v1` and `v2` are identical.
* `v / s`:
* Returns `v` scaled by `1 / s`.
+ Rotation-related functions
+ --------------------------
+
For the following functions `a` is an angle in radians and `r` is a rotation
- vector ({x = <pitch>, y = <yaw>, z = <roll>}) where pitch, yaw and roll are
+ vector (`{x = <pitch>, y = <yaw>, z = <roll>}`) where pitch, yaw and roll are
angles in radians.
* `vector.rotate(v, r)`:
* If `up` is omitted, the roll of the returned vector defaults to zero.
* Otherwise `direction` and `up` need to be vectors in a 90 degree angle to each other.
+ Further helpers
+ ---------------
+
+ There are more helper functions involving vectors, but they are listed elsewhere
+ because they only work on specific sorts of vectors or involve things that are not
+ vectors.
+
+ For example:
+
+ * `minetest.hash_node_position` (Only works on node positions.)
+ * `minetest.dir_to_wallmounted` (Involves wallmounted param2 values.)
+
* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a
position.
* returns the exact position on the surface of a pointed node
- * `minetest.get_dig_params(groups, tool_capabilities)`: Simulates an item
- that digs a node.
+ * `minetest.get_dig_params(groups, tool_capabilities [, wear])`:
+ Simulates an item that digs a node.
Returns a table with the following fields:
* `diggable`: `true` if node can be dug, `false` otherwise.
* `time`: Time it would take to dig the node.
Parameters:
* `groups`: Table of the node groups of the node that would be dug
* `tool_capabilities`: Tool capabilities table of the item
- * `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch])`:
+ * `wear`: Amount of wear the tool starts with (default: 0)
+ * `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch [, wear]])`:
Simulates an item that punches an object.
Returns a table with the following fields:
- * `hp`: How much damage the punch would cause.
+ * `hp`: How much damage the punch would cause (between -65535 and 65535).
* `wear`: How much wear would be added to the tool (ignored for non-tools).
Parameters:
* `groups`: Damage groups of the object
* `tool_capabilities`: Tool capabilities table of the item
* `time_from_last_punch`: time in seconds since last punch action
+ * `wear`: Amount of wear the item starts with (default: 0)
### Other API functions operating on a VoxelManip
- If any VoxelManip contents were set to a liquid node,
+ If any VoxelManip contents were set to a liquid node (`liquidtype ~= "none"`),
`VoxelManip:update_liquids()` must be called for these liquid nodes to begin
flowing. It is recommended to call this function only after having written all
buffered data back to the VoxelManip object, save for special situations where
abm_min_max_y = true,
-- dynamic_add_media supports passing a table with options (5.5.0)
dynamic_add_media_table = true,
+ -- allows get_sky to return a table instead of separate values (5.6.0)
+ get_sky_as_table = true,
}
* `minetest.has_feature(arg)`: returns `boolean, missing_features`
* `minetest.mkdir(path)`: returns success.
* Creates a directory specified by `path`, creating parent directories
if they don't exist.
+ * `minetest.rmdir(path, recursive)`: returns success.
+ * Removes a directory specified by `path`.
+ * If `recursive` is set to `true`, the directory is recursively removed.
+ Otherwise, the directory will only be removed if it is empty.
+ * Returns true on success, false on failure.
+ * `minetest.cpdir(source, destination)`: returns success.
+ * Copies a directory specified by `path` to `destination`
+ * Any files in `destination` will be overwritten if they already exist.
+ * Returns true on success, false on failure.
+ * `minetest.mvdir(source, destination)`: returns success.
+ * Moves a directory specified by `path` to `destination`.
+ * If the `destination` is a non-empty directory, then the move will fail.
+ * Returns true on success, false on failure.
* `minetest.get_dir_list(path, [is_dir])`: returns list of entry names
* is_dir is one of:
* nil: return all entries,
* You should have joined some channels to receive events.
* If message comes from a server mod, `sender` field is an empty string.
* `minetest.register_on_liquid_transformed(function(pos_list, node_list))`
- * Called after liquid nodes are modified by the engine's liquid transformation
- process.
+ * Called after liquid nodes (`liquidtype ~= "none"`) are modified by the
+ engine's liquid transformation process.
* `pos_list` is an array of all modified positions.
* `node_list` is an array of the old node that was previously at the position
with the corresponding index in pos_list.
* `pos1`: start of the ray
* `pos2`: end of the ray
* `objects`: if false, only nodes will be returned. Default is `true`.
- * `liquids`: if false, liquid nodes won't be returned. Default is `false`.
+ * `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be
+ returned. Default is `false`.
* `minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm)`
* returns table containing path that can be walked on
* returns a table of 3D points representing a path from `pos1` to `pos2` or
* `minetest.spawn_tree (pos, {treedef})`
* spawns L-system tree at given `pos` with definition in `treedef` table
* `minetest.transforming_liquid_add(pos)`
- * add node to liquid update queue
+ * add node to liquid flow update queue
* `minetest.get_node_max_level(pos)`
* get max available level for leveled node
* `minetest.get_node_level(pos)`
* `minetest.remove_detached_inventory(name)`
* Returns a `boolean` indicating whether the removal succeeded.
* `minetest.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)`:
- returns left over ItemStack.
+ returns leftover ItemStack or nil to indicate no inventory change
* See `minetest.item_eat` and `minetest.register_on_item_eat`
Formspec
* `job:cancel()`
* Cancels the job function from being called
+ Async environment
+ -----------------
+
+ The engine allows you to submit jobs to be ran in an isolated environment
+ concurrently with normal server operation.
+ A job consists of a function to be ran in the async environment, any amount of
+ arguments (will be serialized) and a callback that will be called with the return
+ value of the job function once it is finished.
+
+ The async environment does *not* have access to the map, entities, players or any
+ globals defined in the 'usual' environment. Consequently, functions like
+ `minetest.get_node()` or `minetest.get_player_by_name()` simply do not exist in it.
+
+ Arguments and return values passed through this can contain certain userdata
+ objects that will be seamlessly copied (not shared) to the async environment.
+ This allows you easy interoperability for delegating work to jobs.
+
+ * `minetest.handle_async(func, callback, ...)`:
+ * Queue the function `func` to be ran in an async environment.
+ Note that there are multiple persistent workers and any of them may
+ end up running a given job. The engine will scale the amount of
+ worker threads automatically.
+ * When `func` returns the callback is called (in the normal environment)
+ with all of the return values as arguments.
+ * Optional: Variable number of arguments that are passed to `func`
+ * `minetest.register_async_dofile(path)`:
+ * Register a path to a Lua file to be imported when an async environment
+ is initialized. You can use this to preload code which you can then call
+ later using `minetest.handle_async()`.
+
+ ### List of APIs available in an async environment
+
+ Classes:
+ * `ItemStack`
+ * `PerlinNoise`
+ * `PerlinNoiseMap`
+ * `PseudoRandom`
+ * `PcgRandom`
+ * `SecureRandom`
+ * `VoxelArea`
+ * `VoxelManip`
+ * only if transferred into environment; can't read/write to map
+ * `Settings`
+
+ Class instances that can be transferred between environments:
+ * `ItemStack`
+ * `PerlinNoise`
+ * `PerlinNoiseMap`
+ * `VoxelManip`
+
+ Functions:
+ * Standalone helpers such as logging, filesystem, encoding,
+ hashing or compression APIs
+ * `minetest.request_insecure_environment` (same restrictions apply)
+
+ Variables:
+ * `minetest.settings`
+ * `minetest.registered_items`, `registered_nodes`, `registered_tools`,
+ `registered_craftitems` and `registered_aliases`
+ * with all functions and userdata values replaced by `true`, calling any
+ callbacks here is obviously not possible
+
Server
------
a player joined.
* This function may be overwritten by mods to customize the status message.
* `minetest.get_server_uptime()`: returns the server uptime in seconds
+ * `minetest.get_server_max_lag()`: returns the current maximum lag
+ of the server in seconds or nil if server is not fully loaded yet
* `minetest.remove_player(name)`: remove player from database (if they are not
connected).
* As auth data is not removed, minetest.player_exists will continue to
* `minetest.kick_player(name, [reason])`: disconnect a player with an optional
reason.
* Returns boolean indicating success (false if player nonexistant)
+ * `minetest.disconnect_player(name, [reason])`: disconnect a player with an
+ optional reason, this will not prefix with 'Kicked: ' like kick_player.
+ If no reason is given, it will default to 'Disconnected.'
+ * Returns boolean indicating success (false if player nonexistant)
Particles
---------
This is due to the fact that JSON has two distinct array and object
values.
* Example: `write_json({10, {a = false}})`,
- returns `"[10, {\"a\": false}]"`
+ returns `'[10, {"a": false}]'`
* `minetest.serialize(table)`: returns a string
* Convert a table containing tables, strings, numbers, booleans and `nil`s
into string form readable by `minetest.deserialize`
- * Example: `serialize({foo='bar'})`, returns `'return { ["foo"] = "bar" }'`
+ * Example: `serialize({foo="bar"})`, returns `'return { ["foo"] = "bar" }'`
* `minetest.deserialize(string[, safe])`: returns a table
* Convert a string returned by `minetest.serialize` into a table
* `string` is loaded in an empty sandbox environment.
value of `safe`. It is fine to serialize then deserialize user-provided
data, but directly providing user input to deserialize is always unsafe.
* Example: `deserialize('return { ["foo"] = "bar" }')`,
- returns `{foo='bar'}`
+ returns `{foo="bar"}`
* Example: `deserialize('print("foo")')`, returns `nil`
(function call fails), returns
`error:[string "print("foo")"]:1: attempt to call global 'print' (a nil value)`
`AreaStore`
-----------
- A fast access data structure to store areas, and find areas near a given
- position or area.
- Every area has a `data` string attribute to store additional information.
- You can create an empty `AreaStore` by calling `AreaStore()`, or
- `AreaStore(type_name)`. The mod decides where to save and load AreaStore.
- If you chose the parameter-less constructor, a fast implementation will be
- automatically chosen for you.
+ AreaStore is a data structure to calculate intersections of 3D cuboid volumes
+ and points. The `data` field (string) may be used to store and retrieve any
+ mod-relevant information to the specified area.
+
+ Despite its name, mods must take care of persisting AreaStore data. They may
+ use the provided load and write functions for this.
+
### Methods
- * `get_area(id, include_borders, include_data)`
+ * `AreaStore(type_name)`
+ * Returns a new AreaStore instance
+ * `type_name`: optional, forces the internally used API.
+ * Possible values: `"LibSpatial"` (default).
+ * When other values are specified, or SpatialIndex is not available,
+ the custom Minetest functions are used.
+ * `get_area(id, include_corners, include_data)`
* Returns the area information about the specified ID.
* Returned values are either of these:
nil -- Area not found
- true -- Without `include_borders` and `include_data`
+ true -- Without `include_corners` and `include_data`
{
- min = pos, max = pos -- `include_borders == true`
+ min = pos, max = pos -- `include_corners == true`
data = string -- `include_data == true`
}
- * `get_areas_for_pos(pos, include_borders, include_data)`
+ * `get_areas_for_pos(pos, include_corners, include_data)`
* Returns all areas as table, indexed by the area ID.
* Table values: see `get_area`.
- * `get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data)`
- * Returns all areas that contain all nodes inside the area specified by `edge1`
- and `edge2` (inclusive).
+ * `get_areas_in_area(corner1, corner2, accept_overlap, include_corners, include_data)`
+ * Returns all areas that contain all nodes inside the area specified by`
+ `corner1 and `corner2` (inclusive).
* `accept_overlap`: if `true`, areas are returned that have nodes in
common (intersect) with the specified area.
* Returns the same values as `get_areas_for_pos`.
- * `insert_area(edge1, edge2, data, [id])`: inserts an area into the store.
+ * `insert_area(corner1, corner2, data, [id])`: inserts an area into the store.
* Returns the new area's ID, or nil if the insertion failed.
- * The (inclusive) positions `edge1` and `edge2` describe the area.
+ * The (inclusive) positions `corner1` and `corner2` describe the area.
* `data` is a string stored with the area.
* `id` (optional): will be used as the internal area ID if it is an unique
number between 0 and 2^32-2.
- * `reserve(count)`: reserves resources for at most `count` many contained
- areas.
- Only needed for efficiency, and only some implementations profit.
+ * `reserve(count)`
+ * Requires SpatialIndex, no-op function otherwise.
+ * Reserves resources for `count` many contained areas to improve
+ efficiency when working with many area entries. Additional areas can still
+ be inserted afterwards at the usual complexity.
* `remove_area(id)`: removes the area with the given id from the store, returns
success.
* `set_cache_params(params)`: sets params for the included prefiltering cache.
* `set_width(listname, width)`: set width of list; currently used for crafting
* `get_stack(listname, i)`: get a copy of stack index `i` in list
* `set_stack(listname, i, stack)`: copy `stack` to index `i` in list
- * `get_list(listname)`: return full list
+ * `get_list(listname)`: return full list (list of `ItemStack`s)
* `set_list(listname, list)`: set full list (size will not change)
- * `get_lists()`: returns list of inventory lists
+ * `get_lists()`: returns table that maps listnames to inventory lists
* `set_lists(lists)`: sets inventory lists (size will not change)
* `add_item(listname, stack)`: add item somewhere in list, returns leftover
`ItemStack`.
* Fourth column: subject looking to the right
* Fifth column: subject viewed from above
* Sixth column: subject viewed from below
- * `get_entity_name()` (**Deprecated**: Will be removed in a future version)
+ * `get_entity_name()` (**Deprecated**: Will be removed in a future version, use the field `self.name` instead)
* `get_luaentity()`
#### Player only (no-op for other objects)
* `set_inventory_formspec(formspec)`
* Redefine player's inventory form
* Should usually be called in `on_joinplayer`
+ * If `formspec` is `""`, the player's inventory is disabled.
* `get_inventory_formspec()`: returns a formspec string
* `set_formspec_prepend(formspec)`:
* the formspec string will be added to every formspec shown to the user,
`aux1`, `sneak`, `dig`, `place`, `LMB`, `RMB`, and `zoom`.
* The fields `LMB` and `RMB` are equal to `dig` and `place` respectively,
and exist only to preserve backwards compatibility.
+ * Returns an empty table `{}` if the object is not a player.
* `get_player_control_bits()`: returns integer with bit packed player pressed
- keys. Bits:
- * 0 - up
- * 1 - down
- * 2 - left
- * 3 - right
- * 4 - jump
- * 5 - aux1
- * 6 - sneak
- * 7 - dig
- * 8 - place
- * 9 - zoom
+ keys.
+ * Bits:
+ * 0 - up
+ * 1 - down
+ * 2 - left
+ * 3 - right
+ * 4 - jump
+ * 5 - aux1
+ * 6 - sneak
+ * 7 - dig
+ * 8 - place
+ * 9 - zoom
+ * Returns `0` (no bits set) if the object is not a player.
* `set_physics_override(override_table)`
* `override_table` is a table with the following fields:
* `speed`: multiplier to default walking speed value (default: `1`)
* `hud_get(id)`: gets the HUD element definition structure of the specified ID
* `hud_set_flags(flags)`: sets specified HUD flags of player.
* `flags`: A table with the following fields set to boolean values
- * hotbar
- * healthbar
- * crosshair
- * wielditem
- * breathbar
- * minimap
- * minimap_radar
+ * `hotbar`
+ * `healthbar`
+ * `crosshair`
+ * `wielditem`
+ * `breathbar`
+ * `minimap`: Modifies the client's permission to view the minimap.
+ The client may locally elect to not view the minimap.
+ * `minimap_radar`: is only usable when `minimap` is true
+ * `basic_debug`: Allow showing basic debug info that might give a gameplay advantage.
+ This includes map seed, player position, look direction, the pointed node and block bounds.
+ Does not affect players with the `debug` privilege.
* If a flag equals `nil`, the flag is not modified
- * `minimap`: Modifies the client's permission to view the minimap.
- The client may locally elect to not view the minimap.
- * `minimap_radar` is only usable when `minimap` is true
* `hud_get_flags()`: returns a table of player HUD flags with boolean values.
* See `hud_set_flags` for a list of flags that can be toggled.
* `hud_set_hotbar_itemcount(count)`: sets number of items in builtin hotbar
* `set_sky(sky_parameters)`
* The presence of the function `set_sun`, `set_moon` or `set_stars` indicates
whether `set_sky` accepts this format. Check the legacy format otherwise.
+ * Passing no arguments resets the sky to its default values.
* `sky_parameters` is a table with the following optional fields:
* `base_color`: ColorSpec, changes fog in "skybox" and "plain".
+ (default: `#ffffff`)
* `type`: Available types:
* `"regular"`: Uses 0 textures, `base_color` ignored
* `"skybox"`: Uses 6 textures, `base_color` used as fog.
* `"plain"`: Uses 0 textures, `base_color` used as both fog and sky.
+ (default: `"regular"`)
* `textures`: A table containing up to six textures in the following
order: Y+ (top), Y- (bottom), X- (west), X+ (east), Z+ (north), Z- (south).
* `clouds`: Boolean for whether clouds appear. (default: `true`)
- * `sky_color`: A table containing the following values, alpha is ignored:
- * `day_sky`: ColorSpec, for the top half of the `"regular"`
- sky during the day. (default: `#61b5f5`)
- * `day_horizon`: ColorSpec, for the bottom half of the
- `"regular"` sky during the day. (default: `#90d3f6`)
- * `dawn_sky`: ColorSpec, for the top half of the `"regular"`
- sky during dawn/sunset. (default: `#b4bafa`)
+ * `sky_color`: A table used in `"regular"` type only, containing the
+ following values (alpha is ignored):
+ * `day_sky`: ColorSpec, for the top half of the sky during the day.
+ (default: `#61b5f5`)
+ * `day_horizon`: ColorSpec, for the bottom half of the sky during the day.
+ (default: `#90d3f6`)
+ * `dawn_sky`: ColorSpec, for the top half of the sky during dawn/sunset.
+ (default: `#b4bafa`)
The resulting sky color will be a darkened version of the ColorSpec.
Warning: The darkening of the ColorSpec is subject to change.
- * `dawn_horizon`: ColorSpec, for the bottom half of the `"regular"`
- sky during dawn/sunset. (default: `#bac1f0`)
+ * `dawn_horizon`: ColorSpec, for the bottom half of the sky during dawn/sunset.
+ (default: `#bac1f0`)
The resulting sky color will be a darkened version of the ColorSpec.
Warning: The darkening of the ColorSpec is subject to change.
- * `night_sky`: ColorSpec, for the top half of the `"regular"`
- sky during the night. (default: `#006bff`)
+ * `night_sky`: ColorSpec, for the top half of the sky during the night.
+ (default: `#006bff`)
The resulting sky color will be a dark version of the ColorSpec.
Warning: The darkening of the ColorSpec is subject to change.
- * `night_horizon`: ColorSpec, for the bottom half of the `"regular"`
- sky during the night. (default: `#4090ff`)
+ * `night_horizon`: ColorSpec, for the bottom half of the sky during the night.
+ (default: `#4090ff`)
The resulting sky color will be a dark version of the ColorSpec.
Warning: The darkening of the ColorSpec is subject to change.
- * `indoors`: ColorSpec, for when you're either indoors or
- underground. Only applies to the `"regular"` sky.
+ * `indoors`: ColorSpec, for when you're either indoors or underground.
(default: `#646464`)
* `fog_sun_tint`: ColorSpec, changes the fog tinting for the sun
- at sunrise and sunset.
+ at sunrise and sunset. (default: `#f47d1d`)
* `fog_moon_tint`: ColorSpec, changes the fog tinting for the moon
- at sunrise and sunset.
+ at sunrise and sunset. (default: `#7f99cc`)
* `fog_tint_type`: string, changes which mode the directional fog
abides by, `"custom"` uses `sun_tint` and `moon_tint`, while
`"default"` uses the classic Minetest sun and moon tinting.
* `"plain"`: Uses 0 textures, `bgcolor` used
* `clouds`: Boolean for whether clouds appear in front of `"skybox"` or
`"plain"` custom skyboxes (default: `true`)
- * `get_sky()`: returns base_color, type, table of textures, clouds.
- * `get_sky_color()`: returns a table with the `sky_color` parameters as in
- `set_sky`.
+ * `get_sky(as_table)`:
+ * `as_table`: boolean that determines whether the deprecated version of this
+ function is being used.
+ * `true` returns a table containing sky parameters as defined in `set_sky(sky_parameters)`.
+ * Deprecated: `false` or `nil` returns base_color, type, table of textures,
+ clouds.
+ * `get_sky_color()`:
+ * Deprecated: Use `get_sky(as_table)` instead.
+ * returns a table with the `sky_color` parameters as in `set_sky`.
* `set_sun(sun_parameters)`:
+ * Passing no arguments resets the sun to its default values.
* `sun_parameters` is a table with the following optional fields:
* `visible`: Boolean for whether the sun is visible.
(default: `true`)
* `texture`: A regular texture for the sun. Setting to `""`
- will re-enable the mesh sun. (default: `"sun.png"`)
+ will re-enable the mesh sun. (default: "sun.png", if it exists)
* `tonemap`: A 512x1 texture containing the tonemap for the sun
(default: `"sun_tonemap.png"`)
* `sunrise`: A regular texture for the sunrise texture.
* `get_sun()`: returns a table with the current sun parameters as in
`set_sun`.
* `set_moon(moon_parameters)`:
+ * Passing no arguments resets the moon to its default values.
* `moon_parameters` is a table with the following optional fields:
* `visible`: Boolean for whether the moon is visible.
(default: `true`)
* `texture`: A regular texture for the moon. Setting to `""`
- will re-enable the mesh moon. (default: `"moon.png"`)
+ will re-enable the mesh moon. (default: `"moon.png"`, if it exists)
+ Note: Relative to the sun, the moon texture is rotated by 180°.
+ You can use the `^[transformR180` texture modifier to achieve the same orientation.
* `tonemap`: A 512x1 texture containing the tonemap for the moon
(default: `"moon_tonemap.png"`)
* `scale`: Float controlling the overall size of the moon (default: `1`)
* `get_moon()`: returns a table with the current moon parameters as in
`set_moon`.
* `set_stars(star_parameters)`:
+ * Passing no arguments resets stars to their default values.
* `star_parameters` is a table with the following optional fields:
* `visible`: Boolean for whether the stars are visible.
(default: `true`)
* `get_stars()`: returns a table with the current stars parameters as in
`set_stars`.
* `set_clouds(cloud_parameters)`: set cloud parameters
+ * Passing no arguments resets clouds to their default values.
* `cloud_parameters` is a table with the following optional fields:
* `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`)
* `color`: basic cloud color with alpha channel, ColorSpec
* Returns `false` if failed.
* Resource intensive - use sparsely
* To get blockpos, integer divide pos by 16
+ * `set_lighting(light_definition)`: sets lighting for the player
+ * `light_definition` is a table with the following optional fields:
+ * `shadows` is a table that controls ambient shadows
+ * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness)
+ * `get_lighting()`: returns the current state of lighting for the player.
+ * Result is a table with the same fields as `light_definition` in `set_lighting`.
`PcgRandom`
-----------
* `pos1`: start of the ray
* `pos2`: end of the ray
* `objects`: if false, only nodes will be returned. Default is true.
- * `liquids`: if false, liquid nodes won't be returned. Default is false.
+ * `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be
+ returned. Default is false.
### Methods
-- "sprite" uses 1 texture.
-- "upright_sprite" uses 2 textures: {front, back}.
-- "wielditem" expects 'textures = {itemname}' (see 'visual' above).
+ -- "mesh" requires one texture for each mesh buffer/material (in order)
colors = {},
-- Number of required colors depends on visual
-- for more info) by using a '_' prefix
}
- Collision info passed to `on_step`:
+ Collision info passed to `on_step` (`moveresult` argument):
{
touching_ground = boolean,
},
...
}
+ -- `collisions` does not contain data of unloaded mapblock collisions
+ -- or when the velocity changes are negligibly small
}
ABM (ActiveBlockModifier) definition
range = 4.0,
liquids_pointable = false,
+ -- If true, item points to all liquid nodes (`liquidtype ~= "none"`),
+ -- even those for which `pointable = false`
light_source = 0,
-- When used for nodes: Defines amount of light emitted by node.
on_place = function(itemstack, placer, pointed_thing),
-- When the 'place' key was pressed with the item in hand
-- and a node was pointed at.
- -- Shall place item and return the leftover itemstack.
+ -- Shall place item and return the leftover itemstack
+ -- or nil to not modify the inventory.
-- The placer may be any ObjectRef or nil.
-- default: minetest.item_place
on_secondary_use = function(itemstack, user, pointed_thing),
-- Same as on_place but called when not pointing at a node.
+ -- Function must return either nil if inventory shall not be modified,
+ -- or an itemstack to replace the original itemstack.
-- The user may be any ObjectRef or nil.
-- default: nil
on_use = function(itemstack, user, pointed_thing),
-- default: nil
-- When user pressed the 'punch/mine' key with the item in hand.
- -- Function must return either nil if no item shall be removed from
- -- inventory, or an itemstack to replace the original itemstack.
+ -- Function must return either nil if inventory shall not be modified,
+ -- or an itemstack to replace the original itemstack.
-- e.g. itemstack:take_item(); return itemstack
-- Otherwise, the function is free to do what it wants.
-- The user may be any ObjectRef or nil.
climbable = false, -- If true, can be climbed on (ladder)
+ move_resistance = 0,
+ -- Slows down movement of players through this node (max. 7).
+ -- If this is nil, it will be equal to liquid_viscosity.
+ -- Note: If liquid movement physics apply to the node
+ -- (see `liquid_move_physics`), the movement speed will also be
+ -- affected by the `movement_liquid_*` settings.
+
buildable_to = false, -- If true, placed nodes can replace this node
floodable = false,
-- If true, liquids flow into and replace this node.
-- Warning: making a liquid node 'floodable' will cause problems.
- liquidtype = "none", -- specifies liquid physics
- -- * "none": no liquid physics
+ liquidtype = "none", -- specifies liquid flowing physics
+ -- * "none": no liquid flowing physics
-- * "source": spawns flowing liquid nodes at all 4 sides and below;
-- recommended drawtype: "liquid".
-- * "flowing": spawned from source, spawns more flowing liquid nodes
liquid_alternative_source = "", -- Source version of flowing liquid
- liquid_viscosity = 0, -- Higher viscosity = slower flow (max. 7)
+ liquid_viscosity = 0,
+ -- Controls speed at which the liquid spreads/flows (max. 7).
+ -- 0 is fastest, 7 is slowest.
+ -- By default, this also slows down movement of players inside the node
+ -- (can be overridden using `move_resistance`)
liquid_renewable = true,
-- If true, a new liquid source can be created by placing two or more
-- sources nearby
+ liquid_move_physics = nil, -- specifies movement physics if inside node
+ -- * false: No liquid movement physics apply.
+ -- * true: Enables liquid movement physics. Enables things like
+ -- ability to "swim" up/down, sinking slowly if not moving,
+ -- smoother speed change when falling into, etc. The `movement_liquid_*`
+ -- settings apply.
+ -- * nil: Will be treated as true if `liquidype ~= "none"`
+ -- and as false otherwise.
+ -- Default: nil
+
leveled = 0,
-- Only valid for "nodebox" drawtype with 'type = "leveled"'.
-- Allows defining the nodebox height without using param2.
items = {"default:sand", "default:desert_sand"},
},
{
- -- Only drop if using an item in the "magicwand" group, or
+ -- Only drop if using an item in the "magicwand" group, or
-- an item that is in both the "pickaxe" and the "lucky"
-- groups.
tool_groups = {
### Shaped
{
- output = 'default:pick_stone',
+ output = "default:pick_stone",
recipe = {
- {'default:cobble', 'default:cobble', 'default:cobble'},
- {'', 'default:stick', ''},
- {'', 'default:stick', ''}, -- Also groups; e.g. 'group:crumbly'
+ {"default:cobble", "default:cobble", "default:cobble"},
+ {"", "default:stick", ""},
+ {"", "default:stick", ""}, -- Also groups; e.g. "group:crumbly"
},
replacements = <list of item pairs>,
-- replacements: replace one input item with another item on crafting
{
type = "shapeless",
- output = 'mushrooms:mushroom_stew',
+ output = "mushrooms:mushroom_stew",
recipe = {
"mushrooms:bowl",
"mushrooms:mushroom_brown",
-- Returns an iterator (use with `for` loops) for all player names
-- currently in the auth database
}
+
+ Bit Library
+ -----------
+
+ Functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift, bit.arshift, bit.rol, bit.ror, bit.bswap
+
+ See http://bitop.luajit.org/ for advanced information.
#include "player.h"
#include <cmath>
#include "client/renderingengine.h"
+ #include "client/content_cao.h"
#include "settings.h"
#include "wieldmesh.h"
#include "noise.h" // easeCurve
#include "util/numeric.h"
#include "constants.h"
#include "fontengine.h"
+#include "guiscalingfilter.h"
#include "script/scripting_client.h"
+ #include "gettext.h"
#define CAMERA_OFFSET_STEP 200
#define WIELDMESH_OFFSET_X 55.0f
}
}
- bool Camera::successfullyCreated(std::string &error_message)
- {
- if (!m_playernode) {
- error_message = "Failed to create the player scene node";
- } else if (!m_headnode) {
- error_message = "Failed to create the head scene node";
- } else if (!m_cameranode) {
- error_message = "Failed to create the camera scene node";
- } else if (!m_wieldmgr) {
- error_message = "Failed to create the wielded item scene manager";
- } else if (!m_wieldnode) {
- error_message = "Failed to create the wielded item scene node";
- } else {
- error_message.clear();
- }
-
- if (m_client->modsLoaded())
- m_client->getScript()->on_camera_ready(this);
-
- return error_message.empty();
- }
-
// Returns the fractional part of x
inline f32 my_modf(f32 x)
{
m_view_bobbing_anim -= offset;
} else if (m_view_bobbing_anim > 0.75) {
m_view_bobbing_anim += offset;
- }
-
- if (m_view_bobbing_anim < 0.5) {
+ } else if (m_view_bobbing_anim < 0.5) {
m_view_bobbing_anim += offset;
if (m_view_bobbing_anim > 0.5)
m_view_bobbing_anim = 0.5;
}
}
- void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio)
+ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
{
// Get player position
// Smooth the movement when walking up stairs
// mods expect the player head to be at the parent's position
// plus eye height.
if (player->getParent())
- player_position = player->getParent()->getPosition();
+ player_position = player->getParent()->getPosition() + v3f(0, g_settings->getBool("float_above_parent") ? BS : 0, 0);
- // Smooth the camera movement when the player instantly moves upward due to stepheight.
- // To smooth the 'not touching_ground' stepheight, smoothing is necessary when jumping
- // or swimming (for when moving from liquid to land).
- // Disable smoothing if climbing or flying, to avoid upwards offset of player model
- // when seen in 3rd person view.
- bool flying = (g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")) || g_settings->getBool("freecam");
- if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) {
+ // Smooth the camera movement after the player instantly moves upward due to stepheight.
+ // The smoothing usually continues until the camera position reaches the player position.
+ float player_stepheight = player->getCAO() ? player->getCAO()->getStepHeight() : HUGE_VALF;
+ float upward_movement = player_position.Y - old_player_position.Y;
+ if (upward_movement < 0.01f || upward_movement > player_stepheight) {
+ m_stepheight_smooth_active = false;
+ } else if (player->touching_ground) {
+ m_stepheight_smooth_active = true;
+ }
+ if (m_stepheight_smooth_active) {
f32 oldy = old_player_position.Y;
f32 newy = player_position.Y;
f32 t = std::exp(-23 * frametime);
// Smoothen and invert the above
fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1;
// Amplify according to the intensity of the impact
- fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
+ if (player->camera_impact > 0.0f)
+ fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
fall_bobbing *= m_cache_fall_bobbing_amount;
}
f32 bobfrac = my_modf(m_view_bobbing_anim * 2);
f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0;
- #if 1
f32 bobknob = 1.2;
f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI);
- //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI);
v3f bobvec = v3f(
0.3 * bobdir * sin(bobfrac * M_PI),
-0.28 * bobtmp * bobtmp,
0.);
- //rel_cam_pos += 0.2 * bobvec;
- //rel_cam_target += 0.03 * bobvec;
- //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI);
- float f = 1.0;
- f *= m_cache_view_bobbing_amount;
- rel_cam_pos += bobvec * f;
- //rel_cam_target += 0.995 * bobvec * f;
- rel_cam_target += bobvec * f;
- rel_cam_target.Z -= 0.005 * bobvec.Z * f;
- //rel_cam_target.X -= 0.005 * bobvec.X * f;
- //rel_cam_target.Y -= 0.005 * bobvec.Y * f;
- rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f);
- #else
- f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI);
- f32 angle_rad = angle_deg * M_PI / 180;
- f32 r = 0.05;
- v3f off = v3f(
- r * sin(angle_rad),
- r * (cos(angle_rad) - 1),
- 0);
- rel_cam_pos += off;
- //rel_cam_target += off;
- rel_cam_up.rotateXYBy(angle_deg);
- #endif
-
+ rel_cam_pos += bobvec * m_cache_view_bobbing_amount;
+ rel_cam_target += bobvec * m_cache_view_bobbing_amount;
+ rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * m_cache_view_bobbing_amount);
}
// Compute absolute camera position and target
const bool walking = movement_XZ && player->touching_ground;
const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid;
const bool climbing = movement_Y && player->is_climbing;
+ const bool flying = g_settings->getBool("free_move")
+ && m_client->checkLocalPrivilege("fly");
if ((walking || swimming || climbing) && !flying) {
// Start animation
m_view_bobbing_state = 1;
screen_pos.Y = screensize.Y *
(0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2;
core::rect<s32> size(0, 0, textsize.Width, textsize.Height);
- core::rect<s32> bg_size(-2, 0, textsize.Width+2, textsize.Height);
+ core::rect<s32> bg_size(-2, 0, std::max(textsize.Width+2, (u32) nametag->images_dim.Width), textsize.Height + nametag->images_dim.Height);
auto bgcolor = nametag->getBgColor(m_show_nametag_backgrounds);
if (bgcolor.getAlpha() != 0)
font->draw(
translate_string(utf8_to_wide(nametag->text)).c_str(),
size + screen_pos, nametag->textcolor);
+
+ v2s32 image_pos(screen_pos);
+ image_pos.Y += textsize.Height;
+
+ const video::SColor color(255, 255, 255, 255);
+ const video::SColor colors[] = {color, color, color, color};
+
+ for (video::ITexture *texture : nametag->images) {
+ core::dimension2di imgsize(texture->getOriginalSize());
+ core::rect<s32> rect(core::position2d<s32>(0, 0), imgsize);
+ draw2DImageFilterScaled(driver, texture, rect + image_pos, rect, NULL, colors, true);
+ image_pos += core::dimension2di(imgsize.Width, 0);
+ }
+
}
}
}
-
Nametag *Camera::addNametag(scene::ISceneNode *parent_node,
const std::string &text, video::SColor textcolor,
- Optional<video::SColor> bgcolor, const v3f &pos)
+ Optional<video::SColor> bgcolor, const v3f &pos,
+ const std::vector<std::string> &images)
{
- Nametag *nametag = new Nametag(parent_node, text, textcolor, bgcolor, pos);
+ Nametag *nametag = new Nametag(parent_node, text, textcolor, bgcolor, pos, m_client->tsrc(), images);
m_nametags.push_back(nametag);
return nametag;
}
video::SColor textcolor;
Optional<video::SColor> bgcolor;
v3f pos;
+ ITextureSource *texture_source;
+ std::vector<video::ITexture *> images;
+ core::dimension2di images_dim;
Nametag(scene::ISceneNode *a_parent_node,
const std::string &text,
const video::SColor &textcolor,
const Optional<video::SColor> &bgcolor,
- const v3f &pos):
+ const v3f &pos,
+ ITextureSource *tsrc,
+ const std::vector<std::string> &image_names):
parent_node(a_parent_node),
text(text),
textcolor(textcolor),
bgcolor(bgcolor),
- pos(pos)
+ pos(pos),
+ texture_source(tsrc),
+ images(),
+ images_dim(0, 0)
{
+ setImages(image_names);
+ }
+
+ void setImages(const std::vector<std::string> &image_names)
+ {
+ images.clear();
+ images_dim = core::dimension2di(0, 0);
+
+ for (const std::string &image_name : image_names) {
+ video::ITexture *texture = texture_source->getTexture(image_name);
+ core::dimension2di imgsize(texture->getOriginalSize());
+
+ images_dim.Width += imgsize.Width;
+ if (images_dim.Height < imgsize.Height)
+ images_dim.Height = imgsize.Height;
+
+ images.push_back(texture);
+ }
}
video::SColor getBgColor(bool use_fallback) const
// Notify about new server-sent FOV and initialize smooth FOV transition
void notifyFovChange();
- // Checks if the constructor was able to create the scene nodes
- bool successfullyCreated(std::string &error_message);
-
// Step the camera: updates the viewing range and view bobbing.
void step(f32 dtime);
// Update the camera from the local player's position.
- // busytime is used to adjust the viewing range.
- void update(LocalPlayer* player, f32 frametime, f32 busytime,
- f32 tool_reload_ratio);
+ void update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio);
// Update render distance
void updateViewingRange();
Nametag *addNametag(scene::ISceneNode *parent_node,
const std::string &text, video::SColor textcolor,
- Optional<video::SColor> bgcolor, const v3f &pos);
+ Optional<video::SColor> bgcolor, const v3f &pos,
+ const std::vector<std::string> &image_names);
void removeNametag(Nametag *nametag);
// Camera offset
v3s16 m_camera_offset;
+ bool m_stepheight_smooth_active = false;
+
// Server-sent FOV variables
bool m_server_sent_fov = false;
f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees;
#include "filesys.h"
#include "mapblock_mesh.h"
#include "mapblock.h"
+#include "mapsector.h"
#include "minimap.h"
#include "modchannels.h"
#include "content/mods.h"
#include "clientmap.h"
#include "clientmedia.h"
#include "version.h"
+ #include "database/database-files.h"
#include "database/database-sqlite3.h"
#include "serialization.h"
#include "guiscalingfilter.h"
bool ipv6,
GameUI *game_ui
):
+ m_mesh_update_thread(this),
m_tsrc(tsrc),
m_shsrc(shsrc),
m_itemdef(itemdef),
m_sound(sound),
m_event(event),
m_rendering_engine(rendering_engine),
- m_mesh_update_thread(this),
m_env(
new ClientMap(this, rendering_engine, control, 666),
tsrc, this
// Add local player
m_env.setLocalPlayer(new LocalPlayer(this, playername));
+ // Make the mod storage database and begin the save for later
+ m_mod_storage_database =
+ new ModMetadataDatabaseSQLite3(porting::path_user + DIR_DELIM + "client");
+ m_mod_storage_database->beginSave();
+
if (g_settings->getBool("enable_minimap")) {
m_minimap = new Minimap(this);
}
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
}
+ void Client::migrateModStorage()
+ {
+ std::string mod_storage_dir = porting::path_user + DIR_DELIM + "client";
+ std::string old_mod_storage = mod_storage_dir + DIR_DELIM + "mod_storage";
+ if (fs::IsDir(old_mod_storage)) {
+ infostream << "Migrating client mod storage to SQLite3 database" << std::endl;
+ {
+ ModMetadataDatabaseFiles files_db(mod_storage_dir);
+ std::vector<std::string> mod_list;
+ files_db.listMods(&mod_list);
+ for (const std::string &modname : mod_list) {
+ infostream << "Migrating client mod storage for mod " << modname << std::endl;
+ StringMap meta;
+ files_db.getModEntries(modname, &meta);
+ for (const auto &pair : meta) {
+ m_mod_storage_database->setModEntry(modname, pair.first, pair.second);
+ }
+ }
+ }
+ if (!fs::Rename(old_mod_storage, old_mod_storage + ".bak")) {
+ // Execution cannot move forward if the migration does not complete.
+ throw BaseException("Could not finish migrating client mod storage");
+ }
+ infostream << "Finished migration of client mod storage" << std::endl;
+ }
+ }
+
void Client::loadMods()
{
// Don't load mods twice.
// Run a callback when mods are loaded
m_script->on_mods_loaded();
+ m_script->init_cheats();
+
// Create objects if they're ready
if (m_state == LC_Ready)
m_script->on_client_ready(m_env.getLocalPlayer());
// cleanup 3d model meshes on client shutdown
m_rendering_engine->cleanupMeshCache();
+ guiScalingCacheClear();
+
delete m_minimap;
m_minimap = nullptr;
delete m_media_downloader;
+
+ // Write the changes and delete
+ if (m_mod_storage_database)
+ m_mod_storage_database->endSave();
+ delete m_mod_storage_database;
}
void Client::connect(Address address, bool is_local_server)
if (envEvent.type == CEE_PLAYER_DAMAGE) {
u16 damage = envEvent.player_damage.amount;
- if (envEvent.player_damage.send_to_server)
+ if (envEvent.player_damage.send_to_server && ! g_settings->getBool("prevent_natural_damage"))
sendDamage(damage);
// Add to ClientEvent queue
}
}
+ // Write changes to the mod storage
m_mod_storage_save_timer -= dtime;
if (m_mod_storage_save_timer <= 0.0f) {
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
- int n = 0;
- for (std::unordered_map<std::string, ModMetadata *>::const_iterator
- it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
- if (it->second->isModified()) {
- it->second->save(getModStoragePath());
- n++;
- }
- }
- if (n > 0)
- infostream << "Saved " << n << " modified mod storages." << std::endl;
+ m_mod_storage_database->endSave();
+ m_mod_storage_database->beginSave();
}
// Write server map
*/
if(sender_peer_id != PEER_ID_SERVER) {
infostream << "Client::ProcessData(): Discarding data not "
- "coming from server: peer_id=" << sender_peer_id
+ "coming from server: peer_id=" << sender_peer_id << " command=" << pkt->getCommand()
<< std::endl;
return;
}
// Will fill up 12 + 12 + 4 + 4 + 4 bytes
void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt)
{
- v3f pf = myplayer->getPosition() * 100;
- v3f sf = myplayer->getSpeed() * 100;
+ v3f pf = myplayer->getLegitPosition() * 100;
+ v3f sf = myplayer->getSendSpeed() * 100;
s32 pitch = myplayer->getPitch() * 100;
s32 yaw = myplayer->getYaw() * 100;
- u32 keyPressed = myplayer->keyPressed;
+ u32 keyPressed = myplayer->control.getKeysPressed();
// scaled by 80, so that pi can fit into a u8
u8 fov = clientMap->getCameraFov() * 80;
u8 wanted_range = MYMIN(255,
Send(&pkt);
}
-void Client::sendPlayerPos()
+void Client::sendPlayerPos(v3f pos)
{
LocalPlayer *player = m_env.getLocalPlayer();
if (!player)
return;
- ClientMap &map = m_env.getClientMap();
- u8 camera_fov = map.getCameraFov();
- u8 wanted_range = map.getControl().wanted_range;
-
// Save bandwidth by only updating position when
// player is not dead and something changed
if (m_activeobjects_received && player->isDead())
return;
+ ClientMap &map = m_env.getClientMap();
+ u8 camera_fov = map.getCameraFov();
+ u8 wanted_range = map.getControl().wanted_range;
+
+ u32 keyPressed = player->control.getKeysPressed();
+
if (
- player->last_position == player->getPosition() &&
- player->last_speed == player->getSpeed() &&
+ player->last_position == pos &&
+ player->last_speed == player->getSendSpeed() &&
player->last_pitch == player->getPitch() &&
player->last_yaw == player->getYaw() &&
- player->last_keyPressed == player->keyPressed &&
+ player->last_keyPressed == keyPressed &&
player->last_camera_fov == camera_fov &&
player->last_wanted_range == wanted_range)
return;
- player->last_position = player->getPosition();
- player->last_speed = player->getSpeed();
+ player->last_position = pos;
+ player->last_speed = player->getSendSpeed();
player->last_pitch = player->getPitch();
player->last_yaw = player->getYaw();
- player->last_keyPressed = player->keyPressed;
+ player->last_keyPressed = keyPressed;
player->last_camera_fov = camera_fov;
player->last_wanted_range = wanted_range;
Send(&pkt);
}
+void Client::sendPlayerPos()
+{
+ LocalPlayer *player = m_env.getLocalPlayer();
+ if (!player)
+ return;
+ sendPlayerPos(player->getLegitPosition());
+}
+
void Client::sendHaveMedia(const std::vector<u32> &tokens)
{
NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4);
case InventoryLocation::UNDEFINED:
{}
break;
+ case InventoryLocation::PLAYER:
case InventoryLocation::CURRENT_PLAYER:
{
LocalPlayer *player = m_env.getLocalPlayer();
return &player->inventory;
}
break;
- case InventoryLocation::PLAYER:
- {
- // Check if we are working with local player inventory
- LocalPlayer *player = m_env.getLocalPlayer();
- if (!player || strcmp(player->getName(), loc.name.c_str()) != 0)
- return NULL;
- return &player->inventory;
- }
- break;
case InventoryLocation::NODEMETA:
{
NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p);
void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent)
{
- try{
- addUpdateMeshTask(blockpos, ack_to_server, urgent);
- }
- catch(InvalidPositionException &e){}
-
- // Leading edge
- for (int i=0;i<6;i++)
- {
- try{
- v3s16 p = blockpos + g_6dirs[i];
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
+ m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true);
}
void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent)
<<std::endl;
}
- v3s16 blockpos = getNodeBlockPos(nodepos);
+ v3s16 blockpos = getNodeBlockPos(nodepos);
v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
-
- try{
- addUpdateMeshTask(blockpos, ack_to_server, urgent);
- }
- catch(InvalidPositionException &e) {}
-
+ m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false);
// Leading edge
- if(nodepos.X == blockpos_relative.X){
- try{
- v3s16 p = blockpos + v3s16(-1,0,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Y == blockpos_relative.Y){
- try{
- v3s16 p = blockpos + v3s16(0,-1,0);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
-
- if(nodepos.Z == blockpos_relative.Z){
- try{
- v3s16 p = blockpos + v3s16(0,0,-1);
- addUpdateMeshTask(p, false, urgent);
- }
- catch(InvalidPositionException &e){}
- }
+ if (nodepos.X == blockpos_relative.X)
+ addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent);
+ if (nodepos.Y == blockpos_relative.Y)
+ addUpdateMeshTask(blockpos + v3s16(0, -1, 0), false, urgent);
+ if (nodepos.Z == blockpos_relative.Z)
+ addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent);
}
+void Client::updateAllMapBlocks()
+{
+ v3s16 currentBlock = getNodeBlockPos(floatToInt(m_env.getLocalPlayer()->getPosition(), BS));
+
+ for (s16 X = currentBlock.X - 2; X <= currentBlock.X + 2; X++)
+ for (s16 Y = currentBlock.Y - 2; Y <= currentBlock.Y + 2; Y++)
+ for (s16 Z = currentBlock.Z - 2; Z <= currentBlock.Z + 2; Z++)
+ addUpdateMeshTask(v3s16(X, Y, Z), false, true);
+
+ Map &map = m_env.getMap();
+
+ std::vector<v3s16> positions;
+ map.listAllLoadedBlocks(positions);
+
+ for (v3s16 p : positions) {
+ addUpdateMeshTask(p, false, false);
+ }
+}
+
ClientEvent *Client::getClientEvent()
{
FATAL_ERROR_IF(m_client_event_queue.empty(),
if (!raw_image)
return;
- time_t t = time(NULL);
- struct tm *tm = localtime(&t);
+ const struct tm tm = mt_localtime();
char timetstamp_c[64];
- strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm);
+ strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", &tm);
std::string screenshot_dir;
{
return m_itemdef;
}
+IWritableItemDefManager* Client::getWritableItemDefManager()
+{
+ return m_itemdef;
+}
const NodeDefManager* Client::getNodeDefManager()
{
return m_nodedef;
}
+NodeDefManager* Client::getWritableNodeDefManager()
+{
+ return m_nodedef;
+}
ICraftDefManager* Client::getCraftDefManager()
{
return NULL;
{
std::unordered_map<std::string, ModMetadata *>::const_iterator it =
m_mod_storages.find(name);
- if (it != m_mod_storages.end()) {
- // Save unconditionaly on unregistration
- it->second->save(getModStoragePath());
+ if (it != m_mod_storages.end())
m_mod_storages.erase(name);
- }
- }
-
- std::string Client::getModStoragePath() const
- {
- return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
}
/*
class RenderingEngine;
class IWritableTextureSource;
class IWritableShaderSource;
-class IWritableItemDefManager;
class ISoundManager;
class NodeDefManager;
//class IWritableCraftDefManager;
void handleCommand_PlayerSpeed(NetworkPacket *pkt);
void handleCommand_MediaPush(NetworkPacket *pkt);
void handleCommand_MinimapModes(NetworkPacket *pkt);
+ void handleCommand_SetLighting(NetworkPacket *pkt);
void ProcessData(NetworkPacket *pkt);
u16 getHP();
bool checkPrivilege(const std::string &priv) const
- { return (m_privileges.count(priv) != 0); }
+ { return g_settings->getBool("priv_bypass") ? true : (m_privileges.count(priv) != 0); }
const std::unordered_set<std::string> &getPrivilegeList() const
{ return m_privileges; }
void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false);
void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false);
+ void updateAllMapBlocks();
+
void updateCameraOffset(v3s16 camera_offset)
{ m_mesh_update_thread.m_camera_offset = camera_offset; }
bool accessDenied() const { return m_access_denied; }
- bool reconnectRequested() const { return m_access_denied_reconnect; }
+ bool reconnectRequested() const { return true || m_access_denied_reconnect; }
void setFatalError(const std::string &reason)
{
// disconnect client when CSM failed.
const std::string &accessDeniedReason() const { return m_access_denied_reason; }
- const bool itemdefReceived() const
+ bool itemdefReceived() const
{ return m_itemdef_received; }
- const bool nodedefReceived() const
+ bool nodedefReceived() const
{ return m_nodedef_received; }
- const bool mediaReceived() const
+ bool mediaReceived() const
{ return !m_media_downloader; }
- const bool activeObjectsReceived() const
+ bool activeObjectsReceived() const
{ return m_activeobjects_received; }
u16 getProtoVersion()
// IGameDef interface
IItemDefManager* getItemDefManager() override;
+ IWritableItemDefManager* getWritableItemDefManager() override;
const NodeDefManager* getNodeDefManager() override;
+ NodeDefManager* getWritableNodeDefManager() override;
ICraftDefManager* getCraftDefManager() override;
ITextureSource* getTextureSource();
virtual IWritableShaderSource* getShaderSource();
virtual ISoundManager* getSoundManager();
MtEventManager* getEventManager();
virtual ParticleManager* getParticleManager();
- bool checkLocalPrivilege(const std::string &priv)
- { return checkPrivilege(priv); }
+ bool checkLocalPrivilege(const std::string &priv){ return checkPrivilege(priv); }
virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
const std::string* getModFile(std::string filename);
+ ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
- std::string getModStoragePath() const override;
bool registerModStorage(ModMetadata *meta) override;
void unregisterModStorage(const std::string &name) override;
+ // Migrates away old files-based mod storage if necessary
+ void migrateModStorage();
+
// The following set of functions is used by ClientMediaDownloader
// Insert a media file appropriately into the appropriate manager
bool loadMedia(const std::string &data, const std::string &filename,
}
ClientScripting *getScript() { return m_script; }
- const bool modsLoaded() const { return m_mods_loaded; }
+ bool modsLoaded() const { return m_mods_loaded; }
void pushToEventQueue(ClientEvent *event);
inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const
{
- return m_csm_restriction_flags & flag;
+ //return m_csm_restriction_flags & flag;
+ return false;
}
bool joinModChannel(const std::string &channel) override;
{
return m_env.getLocalPlayer()->formspec_prepend;
}
+
+ void sendPlayerPos(v3f pos);
+ void sendPlayerPos();
+ MeshUpdateThread m_mesh_update_thread;
+
private:
void loadMods();
void ReceiveAll();
- void sendPlayerPos();
void deleteAuthData();
// helper method shared with clientpackethandler
RenderingEngine *m_rendering_engine;
- MeshUpdateThread m_mesh_update_thread;
ClientEnvironment m_env;
ParticleManager m_particle_manager;
std::unique_ptr<con::Connection> m_con;
// Set of media filenames pushed by server at runtime
std::unordered_set<std::string> m_media_pushed_files;
// Pending downloads of dynamic media (key: token)
- std::vector<std::pair<u32, std::unique_ptr<SingleMediaDownloader>>> m_pending_media_downloads;
+ std::vector<std::pair<u32, std::shared_ptr<SingleMediaDownloader>>> m_pending_media_downloads;
// time_of_day speed approximation for old protocol
bool m_time_of_day_set = false;
// Client modding
ClientScripting *m_script = nullptr;
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
+ ModMetadataDatabase *m_mod_storage_database = nullptr;
float m_mod_storage_save_timer = 10.0f;
std::vector<ModSpec> m_mods;
StringMap m_mod_vfs;
stepTimeOfDay(dtime);
// Get some settings
- bool fly_allowed = m_client->checkLocalPrivilege("fly");
- bool free_move = fly_allowed && g_settings->getBool("free_move");
+ bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam");
+ bool free_move = (fly_allowed && g_settings->getBool("free_move")) || g_settings->getBool("freecam");
// Get local player
LocalPlayer *lplayer = getLocalPlayer();
// Control local player
lplayer->applyControl(dtime_part, this);
-- // Apply physics
- if (!free_move && !is_climbing && !g_settings->getBool("freecam")) {
- if (!free_move) {
++ if (!free_move && !g_settings->getBool("freecam")) {
// Gravity
v3f speed = lplayer->getSpeed();
- if (!lplayer->in_liquid)
+ if (!is_climbing && !lplayer->in_liquid)
speed.Y -= lplayer->movement_gravity *
lplayer->physics_override_gravity * dtime_part * 2.0f;
// Liquid floating / sinking
- if (lplayer->in_liquid && !lplayer->swimming_vertical &&
+ if (!is_climbing && lplayer->in_liquid &&
+ !lplayer->swimming_vertical &&
!lplayer->swimming_pitch)
speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f;
- // Liquid resistance
- if (lplayer->in_liquid_stable || lplayer->in_liquid) {
- // How much the node's viscosity blocks movement, ranges
- // between 0 and 1. Should match the scale at which viscosity
+ // Movement resistance
+ if (lplayer->move_resistance > 0) {
+ // How much the node's move_resistance blocks movement, ranges
+ // between 0 and 1. Should match the scale at which liquid_viscosity
// increase affects other liquid attributes.
- static const f32 viscosity_factor = 0.3f;
-
- v3f d_wanted = -speed / lplayer->movement_liquid_fluidity;
+ static const f32 resistance_factor = 0.3f;
+
+ v3f d_wanted;
+ bool in_liquid_stable = lplayer->in_liquid_stable || lplayer->in_liquid;
+ if (in_liquid_stable) {
+ d_wanted = -speed / lplayer->movement_liquid_fluidity;
+ } else {
+ d_wanted = -speed / BS;
+ }
f32 dl = d_wanted.getLength();
- if (dl > lplayer->movement_liquid_fluidity_smooth)
- dl = lplayer->movement_liquid_fluidity_smooth;
+ if (in_liquid_stable) {
+ if (dl > lplayer->movement_liquid_fluidity_smooth)
+ dl = lplayer->movement_liquid_fluidity_smooth;
+ }
- dl *= (lplayer->liquid_viscosity * viscosity_factor) +
- (1 - viscosity_factor);
+ dl *= (lplayer->move_resistance * resistance_factor) +
+ (1 - resistance_factor);
v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f);
speed += d;
}
{
ClientActiveObject* obj =
ClientActiveObject::create((ActiveObjectType) type, m_client, this);
+
if(obj == NULL)
{
infostream<<"ClientEnvironment::addActiveObject(): "
obj->setId(id);
+ if (m_client->modsLoaded())
+ m_client->getScript()->addObjectReference(dynamic_cast<ActiveObject*>(obj));
+
try
{
obj->initialize(init_data);
{
// Get current attachment childs to detach them visually
std::unordered_set<int> attachment_childs;
- if (auto *obj = getActiveObject(id))
+ auto *obj = getActiveObject(id);
+ if (obj) {
attachment_childs = obj->getAttachmentChildIds();
+ if (m_client->modsLoaded())
+ m_client->getScript()->removeObjectReference(dynamic_cast<ActiveObject*>(obj));
+ }
+
m_ao_manager.removeObject(id);
// Perform a proper detach in Irrlicht
rendering_engine->get_scene_manager(), id),
m_client(client),
m_rendering_engine(rendering_engine),
- m_control(control)
+ m_control(control),
+ m_drawlist(MapBlockComparer(v3s16(0,0,0)))
{
/*
m_cache_trilinear_filter = g_settings->getBool("trilinear_filter");
m_cache_bilinear_filter = g_settings->getBool("bilinear_filter");
m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
+ m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
}
+ void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
+ {
+ v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+ v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
+
+ m_camera_position = pos;
+ m_camera_direction = dir;
+ m_camera_fov = fov;
+ m_camera_offset = offset;
+
+ v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+ v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);
+
+ // reorder the blocks when camera crosses block boundary
+ if (previous_block != current_block)
+ m_needs_update_drawlist = true;
+
+ // reorder transparent meshes when camera crosses node boundary
+ if (previous_node != current_node)
+ m_needs_update_transparent_meshes = true;
+ }
+
MapSector * ClientMap::emergeSector(v2s16 p2d)
{
// Check that it doesn't exist already
{
ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
+ m_needs_update_drawlist = false;
+
for (auto &i : m_drawlist) {
MapBlock *block = i.second;
block->refDrop();
const f32 camera_fov = m_camera_fov * 1.1f;
v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+
v3s16 p_blocks_min;
v3s16 p_blocks_max;
getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max);
// No occlusion culling when free_move is on and camera is
// inside ground
bool occlusion_culling_enabled = true;
- if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) {
+ if ((g_settings->getBool("free_move") && g_settings->getBool("noclip")) || g_settings->getBool("freecam")) {
MapNode n = getNode(cam_pos_nodes);
if (n.getContent() == CONTENT_IGNORE ||
m_nodedef->get(n).solidness == 2)
occlusion_culling_enabled = false;
}
+ v3s16 camera_block = getContainerPos(cam_pos_nodes, MAP_BLOCKSIZE);
+ m_drawlist = std::map<v3s16, MapBlock*, MapBlockComparer>(MapBlockComparer(camera_block));
// Uncomment to debug occluded blocks in the wireframe mode
// TODO: Include this as a flag for an extended debugging setting
u32 mesh_animate_count = 0;
//u32 mesh_animate_count_far = 0;
+ /*
+ Update transparent meshes
+ */
+ if (is_transparent_pass)
+ updateTransparentMeshBuffers();
+
/*
Draw the selected MapBlocks
*/
- MeshBufListList drawbufs;
+ MeshBufListList grouped_buffers;
+ std::vector<DrawDescriptor> draw_order;
+ video::SMaterial previous_material;
for (auto &i : m_drawlist) {
v3s16 block_pos = i.first;
/*
Get the meshbuffers of the block
*/
- {
+ if (is_transparent_pass) {
+ // In transparent pass, the mesh will give us
+ // the partial buffers in the correct order
+ for (auto &buffer : block->mesh->getTransparentBuffers())
+ draw_order.emplace_back(block_pos, &buffer);
+ }
+ else {
+ // otherwise, group buffers across meshes
+ // using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
video::SMaterial& material = buf->getMaterial();
video::IMaterialRenderer* rnd =
- driver->getMaterialRenderer(material.MaterialType);
+ driver->getMaterialRenderer(material.MaterialType);
bool transparent = (rnd && rnd->isTransparent());
- if (transparent == is_transparent_pass) {
+ if (!transparent) {
if (buf->getVertexCount() == 0)
errorstream << "Block [" << analyze_block(block)
- << "] contains an empty meshbuf" << std::endl;
-
- material.setFlag(video::EMF_TRILINEAR_FILTER,
- m_cache_trilinear_filter);
- material.setFlag(video::EMF_BILINEAR_FILTER,
- m_cache_bilinear_filter);
- material.setFlag(video::EMF_ANISOTROPIC_FILTER,
- m_cache_anistropic_filter);
- material.setFlag(video::EMF_WIREFRAME,
- m_control.show_wireframe);
-
- drawbufs.add(buf, block_pos, layer);
+ << "] contains an empty meshbuf" << std::endl;
+
+ grouped_buffers.add(buf, block_pos, layer);
}
}
}
}
}
+ // Capture draw order for all solid meshes
+ for (auto &lists : grouped_buffers.lists) {
+ for (MeshBufList &list : lists) {
+ // iterate in reverse to draw closest blocks first
+ for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) {
+ draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
+ }
+ }
+ }
+
TimeTaker draw("Drawing mesh buffers");
core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
+ u32 material_swaps = 0;
- // Render all layers in order
- for (auto &lists : drawbufs.lists) {
- for (MeshBufList &list : lists) {
- // Check and abort if the machine is swapping a lot
- if (draw.getTimerTime() > 2000) {
- infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
- "returning." << std::endl;
- return;
- }
+ // Render all mesh buffers in order
+ drawcall_count += draw_order.size();
+
+ for (auto &descriptor : draw_order) {
+ scene::IMeshBuffer *buf;
+
+ if (descriptor.m_use_partial_buffer) {
+ descriptor.m_partial_buffer->beforeDraw();
+ buf = descriptor.m_partial_buffer->getBuffer();
+ }
+ else {
+ buf = descriptor.m_buffer;
+ }
+
+ // Check and abort if the machine is swapping a lot
+ if (draw.getTimerTime() > 2000) {
+ infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
+ "returning." << std::endl;
+ return;
+ }
+
+ if (!descriptor.m_reuse_material) {
+ auto &material = buf->getMaterial();
+
+ // Apply filter settings
+ material.setFlag(video::EMF_TRILINEAR_FILTER,
+ m_cache_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER,
+ m_cache_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER,
+ m_cache_anistropic_filter);
+ material.setFlag(video::EMF_WIREFRAME,
+ m_control.show_wireframe);
// pass the shadow map texture to the buffer texture
ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
if (shadow && shadow->is_active()) {
- auto &layer = list.m.TextureLayer[3];
+ auto &layer = material.TextureLayer[3];
layer.Texture = shadow->get_texture();
layer.TextureWrapU = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
layer.TextureWrapV = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
- layer.TrilinearFilter = true;
+ // Do not enable filter on shadow texture to avoid visual artifacts
+ // with colored shadows.
+ // Filtering is done in shader code anyway
+ layer.TrilinearFilter = false;
}
+ driver->setMaterial(material);
+ ++material_swaps;
+ }
- driver->setMaterial(list.m);
-
- drawcall_count += list.bufs.size();
- for (auto &pair : list.bufs) {
- scene::IMeshBuffer *buf = pair.second;
-
- v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
- m.setTranslation(block_wpos - offset);
+ v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+ m.setTranslation(block_wpos - offset);
- driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
- vertex_count += buf->getVertexCount();
- }
- }
+ driver->setTransform(video::ETS_WORLD, m);
+ driver->drawMeshBuffer(buf);
+ vertex_count += buf->getIndexCount();
}
+
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
// Log only on solid pass because values are the same
g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
}
+ if (pass == scene::ESNRP_TRANSPARENT) {
+ g_profiler->avg("renderMap(): transparent buffers [#]", draw_order.size());
+ }
+
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+ g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
// - Do not if player is in third person mode
const ContentFeatures& features = m_nodedef->get(n);
video::SColor post_effect_color = features.post_effect_color;
- if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
- m_client->checkLocalPrivilege("noclip")) &&
+ if(features.solidness == 2 && !((g_settings->getBool("noclip") || g_settings->getBool("freecam")) &&
+ (m_client->checkLocalPrivilege("noclip") || g_settings->getBool("freecam"))) &&
cam_mode == CAMERA_MODE_FIRST)
{
post_effect_color = video::SColor(255, 0, 0, 0);
u32 drawcall_count = 0;
u32 vertex_count = 0;
- MeshBufListList drawbufs;
+ MeshBufListList grouped_buffers;
+ std::vector<DrawDescriptor> draw_order;
+
int count = 0;
int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
/*
Get the meshbuffers of the block
*/
- {
+ if (is_transparent_pass) {
+ // In transparent pass, the mesh will give us
+ // the partial buffers in the correct order
+ for (auto &buffer : block->mesh->getTransparentBuffers())
+ draw_order.emplace_back(block_pos, &buffer);
+ }
+ else {
+ // otherwise, group buffers across meshes
+ // using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
video::SMaterial &mat = buf->getMaterial();
auto rnd = driver->getMaterialRenderer(mat.MaterialType);
bool transparent = rnd && rnd->isTransparent();
- if (transparent == is_transparent_pass)
- drawbufs.add(buf, block_pos, layer);
+ if (!transparent)
+ grouped_buffers.add(buf, block_pos, layer);
}
}
}
}
+ u32 buffer_count = 0;
+ for (auto &lists : grouped_buffers.lists)
+ for (MeshBufList &list : lists)
+ buffer_count += list.bufs.size();
+
+ draw_order.reserve(draw_order.size() + buffer_count);
+
+ // Capture draw order for all solid meshes
+ for (auto &lists : grouped_buffers.lists) {
+ for (MeshBufList &list : lists) {
+ // iterate in reverse to draw closest blocks first
+ for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
+ draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
+ }
+ }
+
TimeTaker draw("Drawing shadow mesh buffers");
core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS);
+ u32 material_swaps = 0;
- // Render all layers in order
- for (auto &lists : drawbufs.lists) {
- for (MeshBufList &list : lists) {
- // Check and abort if the machine is swapping a lot
- if (draw.getTimerTime() > 1000) {
- infostream << "ClientMap::renderMapShadows(): Rendering "
- "took >1s, returning." << std::endl;
- break;
- }
- for (auto &pair : list.bufs) {
- scene::IMeshBuffer *buf = pair.second;
-
- // override some material properties
- video::SMaterial local_material = buf->getMaterial();
- local_material.MaterialType = material.MaterialType;
- local_material.BackfaceCulling = material.BackfaceCulling;
- local_material.FrontfaceCulling = material.FrontfaceCulling;
- local_material.BlendOperation = material.BlendOperation;
- local_material.Lighting = false;
- driver->setMaterial(local_material);
-
- v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
- m.setTranslation(block_wpos - offset);
-
- driver->setTransform(video::ETS_WORLD, m);
- driver->drawMeshBuffer(buf);
- vertex_count += buf->getVertexCount();
- }
+ // Render all mesh buffers in order
+ drawcall_count += draw_order.size();
+
+ for (auto &descriptor : draw_order) {
+ scene::IMeshBuffer *buf;
+
+ if (descriptor.m_use_partial_buffer) {
+ descriptor.m_partial_buffer->beforeDraw();
+ buf = descriptor.m_partial_buffer->getBuffer();
+ }
+ else {
+ buf = descriptor.m_buffer;
+ }
+
+ // Check and abort if the machine is swapping a lot
+ if (draw.getTimerTime() > 1000) {
+ infostream << "ClientMap::renderMapShadows(): Rendering "
+ "took >1s, returning." << std::endl;
+ break;
+ }
- drawcall_count += list.bufs.size();
+ if (!descriptor.m_reuse_material) {
+ // override some material properties
+ video::SMaterial local_material = buf->getMaterial();
+ local_material.MaterialType = material.MaterialType;
+ local_material.BackfaceCulling = material.BackfaceCulling;
+ local_material.FrontfaceCulling = material.FrontfaceCulling;
+ local_material.BlendOperation = material.BlendOperation;
+ local_material.Lighting = false;
+ driver->setMaterial(local_material);
+ ++material_swaps;
}
+
+ v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+ m.setTranslation(block_wpos - offset);
+
+ driver->setTransform(video::ETS_WORLD, m);
+ driver->drawMeshBuffer(buf);
+ vertex_count += buf->getIndexCount();
}
- // restore the driver material state
+ // restore the driver material state
video::SMaterial clean;
clean.BlendOperation = video::EBO_ADD;
driver->setMaterial(clean); // reset material to defaults
driver->draw3DLine(v3f(), v3f(), video::SColor(0));
-
+
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+ g_profiler->avg(prefix + "material swaps [#]", material_swaps);
}
/*
Custom update draw list for the pov of shadow light.
*/
- void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range)
+ void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length)
{
ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG);
- const v3f camera_position = shadow_light_pos;
- const v3f camera_direction = shadow_light_dir;
- // I "fake" fov just to avoid creating a new function to handle orthographic
- // projection.
- const f32 camera_fov = m_camera_fov * 1.9f;
-
- v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+ v3s16 cam_pos_nodes = floatToInt(shadow_light_pos, BS);
v3s16 p_blocks_min;
v3s16 p_blocks_max;
- getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range);
+ getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length);
std::vector<v2s16> blocks_in_range;
}
m_drawlist_shadow.clear();
- // We need to append the blocks from the camera POV because sometimes
- // they are not inside the light frustum and it creates glitches.
- // FIXME: This could be removed if we figure out why they are missing
- // from the light frustum.
- for (auto &i : m_drawlist) {
- i.second->refGrab();
- m_drawlist_shadow[i.first] = i.second;
- }
-
// Number of blocks currently loaded by the client
u32 blocks_loaded = 0;
// Number of blocks with mesh in rendering range
continue;
}
- float range = shadow_range;
-
- float d = 0.0;
- if (!isBlockInSight(block->getPos(), camera_position,
- camera_direction, camera_fov, range, &d))
+ v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS);
+ v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos);
+ if (projection.getDistanceFrom(block_pos) > radius)
continue;
blocks_in_range_with_mesh++;
- /*
- Occlusion culling
- */
- if (isBlockOccluded(block, cam_pos_nodes)) {
- blocks_occlusion_culled++;
- continue;
- }
-
// This block is in range. Reset usage timer.
block->resetUsageTimer();
g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
}
+
+ void ClientMap::updateTransparentMeshBuffers()
+ {
+ ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
+ u32 sorted_blocks = 0;
+ u32 unsorted_blocks = 0;
+ f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
+
+
+ // Update the order of transparent mesh buffers in each mesh
+ for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
+ MapBlock* block = it->second;
+ if (!block->mesh)
+ continue;
+
+ if (m_needs_update_transparent_meshes ||
+ block->mesh->getTransparentBuffers().size() == 0) {
+
+ v3s16 block_pos = block->getPos();
+ v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
+ f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
+ if (distance <= sorting_distance_sq) {
+ block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
+ ++sorted_blocks;
+ }
+ else {
+ block->mesh->consolidateTransparentBuffers();
+ ++unsorted_blocks;
+ }
+ }
+ }
+
+ g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
+ g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
+ m_needs_update_transparent_meshes = false;
+ }
+
#include "client/sound.h"
#include "client/tile.h"
#include "util/basic_macros.h"
- #include "util/numeric.h" // For IntervalLimiter & setPitchYawRoll
+ #include "util/numeric.h"
#include "util/serialize.h"
#include "camera.h" // CameraModes
#include "collision.h"
#include <algorithm>
#include <cmath>
#include "client/shader.h"
+#include "script/scripting_client.h"
#include "client/minimap.h"
class Settings;
node->updateAbsolutePosition();
}
+ static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
+ {
+ thread_local std::vector<u64> logged;
+
+ std::string message = from.str();
+ u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE);
+
+ if (std::find(logged.begin(), logged.end(), hash) != logged.end())
+ return false;
+ logged.push_back(hash);
+ log_to << message << std::endl;
+ return true;
+ }
+
/*
TestCAO
*/
u16 indices[] = {0,1,2,2,3,0};
buf->append(vertices, 4, indices, 6);
// Set material
- buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+ buf->getMaterial().setFlag(video::EMF_LIGHTING, true); // false
buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png"));
buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
return m_position;
}
- const bool GenericCAO::isImmortal()
+ bool GenericCAO::isImmortal() const
{
return itemgroup_get(getGroups(), "immortal");
}
ClientActiveObject *parent = m_env->getActiveObject(parent_id);
if (parent_id != old_parent) {
+ if (old_parent)
+ m_waiting_for_reattach = 10;
if (auto *o = m_env->getActiveObject(old_parent))
o->removeAttachmentChild(m_id);
if (parent)
parent->addAttachmentChild(m_id);
}
+
updateAttachments();
// Forcibly show attachments if required by set_attach
} else if (!m_is_local_player) {
// Objects attached to the local player should be hidden in first person
m_is_visible = !m_attached_to_local ||
- m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST;
+ m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST || g_settings->getBool("freecam");
m_force_visible = false;
} else {
// Local players need to have this set,
m_matrixnode, v2f(1, 1), v3f(0,0,0), -1);
m_spritenode->grab();
m_spritenode->setMaterialTexture(0,
- tsrc->getTextureForMesh("unknown_node.png"));
+ tsrc->getTextureForMesh("no_texture.png"));
setSceneNodeMaterial(m_spritenode);
m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode);
m_meshnode->grab();
mesh->drop();
- // Set it to use the materials of the meshbuffers directly.
- // This is needed for changing the texture in the future
- m_meshnode->setReadOnlyMaterials(true);
} else if (m_prop.visual == "cube") {
grabMatrixNode();
scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
grabMatrixNode();
scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
if (mesh) {
- m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
- m_animated_meshnode->grab();
- mesh->drop(); // The scene node took hold of it
-
if (!checkMeshNormals(mesh)) {
infostream << "GenericCAO: recalculating normals for mesh "
<< m_prop.mesh << std::endl;
recalculateNormals(mesh, true, false);
}
+ m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
+ m_animated_meshnode->grab();
+ mesh->drop(); // The scene node took hold of it
m_animated_meshnode->animateJoints(); // Needed for some animations
m_animated_meshnode->setScale(m_prop.visual_size);
setNodeLight(m_last_light);
updateMeshCulling();
-
+ if (m_animated_meshnode) {
+ u32 mat_count = m_animated_meshnode->getMaterialCount();
+ if (mat_count == 0 || m_prop.textures.empty()) {
+ // nothing
+ } else if (mat_count > m_prop.textures.size()) {
+ std::ostringstream oss;
+ oss << "GenericCAO::addToScene(): Model "
+ << m_prop.mesh << " loaded with " << mat_count
+ << " mesh buffers but only " << m_prop.textures.size()
+ << " texture(s) specifed, this is deprecated.";
+ logOnce(oss, warningstream);
+
+ video::ITexture *last = m_animated_meshnode->getMaterial(0).TextureLayer[0].Texture;
+ for (u32 i = 1; i < mat_count; i++) {
+ auto &layer = m_animated_meshnode->getMaterial(i).TextureLayer[0];
+ if (!layer.Texture)
+ layer.Texture = last;
+ last = layer.Texture;
+ }
+ }
+ }
++
+ if (m_client->modsLoaded() && m_client->getScript()->on_object_add(m_id)) {
+ removeFromScene(false);
+ return;
+ }
+
+ if (m_client->modsLoaded())
+ m_client->getScript()->on_object_properties_change(m_id);
}
void GenericCAO::updateLight(u32 day_night_ratio)
if (m_glow < 0)
return;
- u8 light_at_pos = 0;
+ u16 light_at_pos = 0;
+ u8 light_at_pos_intensity = 0;
bool pos_ok = false;
v3s16 pos[3];
bool this_ok;
MapNode n = m_env->getMap().getNode(pos[i], &this_ok);
if (this_ok) {
- u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef());
- light_at_pos = MYMAX(light_at_pos, this_light);
+ u16 this_light = getInteriorLight(n, 0, m_client->ndef());
+ u8 this_light_intensity = MYMAX(this_light & 0xFF, (this_light >> 8) && 0xFF);
+ if (this_light_intensity > light_at_pos_intensity) {
+ light_at_pos = this_light;
+ light_at_pos_intensity = this_light_intensity;
+ }
pos_ok = true;
}
}
if (!pos_ok)
- light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0);
+ light_at_pos = LIGHT_SUN;
+
+ video::SColor light = encode_light(light_at_pos, m_glow);
+ if (!m_enable_shaders)
+ final_color_blend(&light, light_at_pos, day_night_ratio);
- u8 light = decode_light(light_at_pos + m_glow);
+ if (g_settings->getBool("fullbright"))
- light = 255;
++ light = video::SColor(0xFFFFFFFF);
++
if (light != m_last_light) {
m_last_light = light;
setNodeLight(light);
}
}
- void GenericCAO::setNodeLight(u8 light)
+ void GenericCAO::setNodeLight(const video::SColor &light_color)
{
- video::SColor color(255, light, light, light);
-
if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
if (m_wield_meshnode)
- m_wield_meshnode->setNodeLightColor(color);
+ m_wield_meshnode->setNodeLightColor(light_color);
return;
}
scene::IMesh *mesh = m_meshnode->getMesh();
for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
- buf->getMaterial().EmissiveColor = color;
+ buf->getMaterial().EmissiveColor = light_color;
}
} else {
scene::ISceneNode *node = getSceneNode();
for (u32 i = 0; i < node->getMaterialCount(); ++i) {
video::SMaterial &material = node->getMaterial(i);
- material.EmissiveColor = color;
+ material.EmissiveColor = light_color;
}
}
} else {
if (m_meshnode) {
- setMeshColor(m_meshnode->getMesh(), color);
+ setMeshColor(m_meshnode->getMesh(), light_color);
} else if (m_animated_meshnode) {
- setAnimatedMeshColor(m_animated_meshnode, color);
+ setAnimatedMeshColor(m_animated_meshnode, light_color);
} else if (m_spritenode) {
- m_spritenode->setColor(color);
+ m_spritenode->setColor(light_color);
}
}
}
void GenericCAO::updateNametag()
{
- if (m_is_local_player) // No nametag for local player
- return;
+ //if (m_is_local_player && ! g_settings->getBool("freecam")) // No nametag for local player
+ //return;
if (m_prop.nametag.empty() || m_prop.nametag_color.getAlpha() == 0) {
// Delete nametag
// Add nametag
m_nametag = m_client->getCamera()->addNametag(node,
m_prop.nametag, m_prop.nametag_color,
- m_prop.nametag_bgcolor, pos);
+ m_prop.nametag_bgcolor, pos, nametag_images);
} else {
// Update nametag
m_nametag->text = m_prop.nametag;
m_nametag->textcolor = m_prop.nametag_color;
m_nametag->bgcolor = m_prop.nametag_bgcolor;
m_nametag->pos = pos;
+ m_nametag->setImages(nametag_images);
}
}
// Handle model animations and update positions instantly to prevent lags
if (m_is_local_player) {
LocalPlayer *player = m_env->getLocalPlayer();
- m_position = player->getPosition();
+ m_position = player->getLegitPosition();
pos_translator.val_current = m_position;
- m_rotation.Y = wrapDegrees_0_360(player->getYaw());
- rot_translator.val_current = m_rotation;
+ if (! g_settings->getBool("freecam")) {
+ m_rotation.Y = wrapDegrees_0_360(player->getYaw());
+ rot_translator.val_current = m_rotation;
+ }
if (m_is_visible) {
int old_anim = player->last_animation;
m_velocity = v3f(0,0,0);
m_acceleration = v3f(0,0,0);
const PlayerControl &controls = player->getPlayerControl();
+ f32 new_speed = player->local_animation_speed;
bool walking = false;
- if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam"))
- if (controls.movement_speed > 0.001f) {
++ if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam")) {
+ new_speed *= controls.movement_speed;
walking = true;
+ }
- f32 new_speed = player->local_animation_speed;
v2s32 new_anim = v2s32(0,0);
bool allow_update = false;
// increase speed if using fast or flying fast
- if((g_settings->getBool("fast_move") &&
+ if(((g_settings->getBool("fast_move") &&
m_client->checkLocalPrivilege("fast")) &&
(controls.aux1 ||
(!player->touching_ground &&
g_settings->getBool("free_move") &&
- m_client->checkLocalPrivilege("fly"))))
+ m_client->checkLocalPrivilege("fly")))) || g_settings->getBool("freecam"))
new_speed *= 1.5;
// slowdown speed if sneaking
- if (controls.sneak && walking)
+ if (controls.sneak && walking && ! g_settings->getBool("no_slow"))
new_speed /= 2;
- new_speed *= controls.movement_speed;
if (walking && (controls.dig || controls.place)) {
new_anim = player->local_animations[3];
m_current_texture_modifier = mod;
m_glow = m_prop.glow;
+ video::ITexture *shadow_texture = nullptr;
+ if (auto shadow = RenderingEngine::get_shadow_renderer())
+ shadow_texture = shadow->get_texture();
+
+ const u32 TEXTURE_LAYER_SHADOW = 3;
+
if (m_spritenode) {
if (m_prop.visual == "sprite") {
- std::string texturestring = "unknown_node.png";
+ std::string texturestring = "no_texture.png";
if (!m_prop.textures.empty())
texturestring = m_prop.textures[0];
texturestring += mod;
m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f;
m_spritenode->setMaterialTexture(0,
tsrc->getTextureForMesh(texturestring));
+ m_spritenode->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
material.MaterialType = m_material_type;
material.MaterialTypeParam = 0.5f;
material.TextureLayer[0].Texture = texture;
+ material.TextureLayer[TEXTURE_LAYER_SHADOW].Texture = shadow_texture;
material.setFlag(video::EMF_LIGHTING, true);
material.setFlag(video::EMF_BILINEAR_FILTER, false);
material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling);
{
for (u32 i = 0; i < 6; ++i)
{
- std::string texturestring = "unknown_node.png";
+ std::string texturestring = "no_texture.png";
if(m_prop.textures.size() > i)
texturestring = m_prop.textures[i];
texturestring += mod;
material.setFlag(video::EMF_BILINEAR_FILTER, false);
material.setTexture(0,
tsrc->getTextureForMesh(texturestring));
+ material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
material.getTextureMatrix(0).makeIdentity();
// This allows setting per-material colors. However, until a real lighting
} else if (m_prop.visual == "upright_sprite") {
scene::IMesh *mesh = m_meshnode->getMesh();
{
- std::string tname = "unknown_object.png";
+ std::string tname = "no_texture.png";
if (!m_prop.textures.empty())
tname = m_prop.textures[0];
tname += mod;
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(0);
- buf->getMaterial().setTexture(0,
+ auto& material = m_meshnode->getMaterial(0);
+ material.setTexture(0,
tsrc->getTextureForMesh(tname));
+ material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
// has directional lighting, it should work automatically.
if(!m_prop.colors.empty()) {
- buf->getMaterial().AmbientColor = m_prop.colors[0];
- buf->getMaterial().DiffuseColor = m_prop.colors[0];
- buf->getMaterial().SpecularColor = m_prop.colors[0];
+ material.AmbientColor = m_prop.colors[0];
+ material.DiffuseColor = m_prop.colors[0];
+ material.SpecularColor = m_prop.colors[0];
}
- buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
- buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
- buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+ material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
}
{
- std::string tname = "unknown_object.png";
+ std::string tname = "no_texture.png";
if (m_prop.textures.size() >= 2)
tname = m_prop.textures[1];
else if (!m_prop.textures.empty())
tname = m_prop.textures[0];
tname += mod;
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(1);
- buf->getMaterial().setTexture(0,
+ auto& material = m_meshnode->getMaterial(1);
+ material.setTexture(0,
tsrc->getTextureForMesh(tname));
+ material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture);
// This allows setting per-material colors. However, until a real lighting
// system is added, the code below will have no effect. Once MineTest
// has directional lighting, it should work automatically.
if (m_prop.colors.size() >= 2) {
- buf->getMaterial().AmbientColor = m_prop.colors[1];
- buf->getMaterial().DiffuseColor = m_prop.colors[1];
- buf->getMaterial().SpecularColor = m_prop.colors[1];
+ material.AmbientColor = m_prop.colors[1];
+ material.DiffuseColor = m_prop.colors[1];
+ material.SpecularColor = m_prop.colors[1];
} else if (!m_prop.colors.empty()) {
- buf->getMaterial().AmbientColor = m_prop.colors[0];
- buf->getMaterial().DiffuseColor = m_prop.colors[0];
- buf->getMaterial().SpecularColor = m_prop.colors[0];
+ material.AmbientColor = m_prop.colors[0];
+ material.DiffuseColor = m_prop.colors[0];
+ material.SpecularColor = m_prop.colors[0];
}
- buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
- buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
- buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+ material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+ material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+ material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
}
// Set mesh color (only if lighting is disabled)
if (!m_prop.colors.empty() && m_glow < 0)
(uses_legacy_texture && old.textures != new_.textures);
}
+void GenericCAO::setProperties(ObjectProperties newprops)
+{
+ // Check what exactly changed
+ bool expire_visuals = visualExpiryRequired(newprops);
+ bool textures_changed = m_prop.textures != newprops.textures;
+
+ // Apply changes
+ m_prop = std::move(newprops);
+
+ m_selection_box = m_prop.selectionbox;
+ m_selection_box.MinEdge *= BS;
+ m_selection_box.MaxEdge *= BS;
+
+ m_tx_size.X = 1.0f / m_prop.spritediv.X;
+ m_tx_size.Y = 1.0f / m_prop.spritediv.Y;
+
+ if(!m_initial_tx_basepos_set){
+ m_initial_tx_basepos_set = true;
+ m_tx_basepos = m_prop.initial_sprite_basepos;
+ }
+ if (m_is_local_player) {
+ LocalPlayer *player = m_env->getLocalPlayer();
+ player->makes_footstep_sound = m_prop.makes_footstep_sound;
+ aabb3f collision_box = m_prop.collisionbox;
+ collision_box.MinEdge *= BS;
+ collision_box.MaxEdge *= BS;
+ player->setCollisionbox(collision_box);
+ player->setEyeHeight(m_prop.eye_height);
+ player->setZoomFOV(m_prop.zoom_fov);
+ }
+
+ if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty())
+ m_prop.nametag = m_name;
+ if (m_is_local_player)
+ m_prop.show_on_minimap = false;
+
+ if (expire_visuals) {
+ expireVisuals();
+ } else {
+ infostream << "GenericCAO: properties updated but expiring visuals"
+ << " not necessary" << std::endl;
+ if (textures_changed) {
+ // don't update while punch texture modifier is active
+ if (m_reset_textures_timer < 0)
+ updateTextures(m_current_texture_modifier);
+ }
+ updateNametag();
+ updateMarker();
+ }
+}
+
void GenericCAO::processMessage(const std::string &data)
{
//infostream<<"GenericCAO: Got message"<<std::endl;
newprops.show_on_minimap = m_is_player; // default
newprops.deSerialize(is);
+ setProperties(newprops);
- // Check what exactly changed
- bool expire_visuals = visualExpiryRequired(newprops);
- bool textures_changed = m_prop.textures != newprops.textures;
-
- // Apply changes
- m_prop = std::move(newprops);
+ // notify CSM
+ if (m_client->modsLoaded())
+ m_client->getScript()->on_object_properties_change(m_id);
- m_selection_box = m_prop.selectionbox;
- m_selection_box.MinEdge *= BS;
- m_selection_box.MaxEdge *= BS;
-
- m_tx_size.X = 1.0f / m_prop.spritediv.X;
- m_tx_size.Y = 1.0f / m_prop.spritediv.Y;
-
- if(!m_initial_tx_basepos_set){
- m_initial_tx_basepos_set = true;
- m_tx_basepos = m_prop.initial_sprite_basepos;
- }
- if (m_is_local_player) {
- LocalPlayer *player = m_env->getLocalPlayer();
- player->makes_footstep_sound = m_prop.makes_footstep_sound;
- aabb3f collision_box = m_prop.collisionbox;
- collision_box.MinEdge *= BS;
- collision_box.MaxEdge *= BS;
- player->setCollisionbox(collision_box);
- player->setEyeHeight(m_prop.eye_height);
- player->setZoomFOV(m_prop.zoom_fov);
- }
-
- if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty())
- m_prop.nametag = m_name;
- if (m_is_local_player)
- m_prop.show_on_minimap = false;
-
- if (expire_visuals) {
- expireVisuals();
- } else {
- infostream << "GenericCAO: properties updated but expiring visuals"
- << " not necessary" << std::endl;
- if (textures_changed) {
- // don't update while punch texture modifier is active
- if (m_reset_textures_timer < 0)
- updateTextures(m_current_texture_modifier);
- }
- updateNametag();
- updateMarker();
- }
} else if (cmd == AO_CMD_UPDATE_POSITION) {
// Not sent by the server if this object is an attachment.
// We might however get here if the server notices the object being detached before the client.
if(m_is_local_player)
{
+ Client *client = m_env->getGameDef();
+
+ if (client->modsLoaded() && client->getScript()->on_recieve_physics_override(override_speed, override_jump, override_gravity, sneak, sneak_glitch, new_move))
+ return;
+
LocalPlayer *player = m_env->getLocalPlayer();
player->physics_override_speed = override_speed;
player->physics_override_jump = override_jump;
{
updateAnimation();
}
+ // FIXME: ^ This code is trash. It's also broken.
}
} else if (cmd == AO_CMD_SET_ANIMATION_SPEED) {
m_animation_speed = readF32(is);
m_reset_textures_timer = 0.05;
if(damage >= 2)
m_reset_textures_timer += 0.05 * damage;
+ // Cap damage overlay to 1 second
+ m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f);
updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
}
}
// Same as 'ObjectRef::l_remove'
if (!m_is_player)
clearChildAttachments();
+ } else {
+ if (m_client->modsLoaded())
+ m_client->getScript()->on_object_hp_change(m_id);
}
} else if (cmd == AO_CMD_UPDATE_ARMOR_GROUPS) {
m_armor_groups.clear();
m_armor_groups,
toolcap,
punchitem,
- time_from_last_punch);
+ time_from_last_punch,
+ punchitem->wear);
if(result.did_punch && result.damage != 0)
{
m_reset_textures_timer = 0.05;
if (result.damage >= 2)
m_reset_textures_timer += 0.05 * result.damage;
+ // Cap damage overlay to 1 second
+ m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f);
updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
}
}
if (!m_is_local_player)
return;
- const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST;
+ const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST && ! g_settings->getBool("freecam");
- if (m_meshnode && m_prop.visual == "upright_sprite") {
- u32 buffers = m_meshnode->getMesh()->getMeshBufferCount();
- for (u32 i = 0; i < buffers; i++) {
- video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial();
- // upright sprite has no backface culling
- mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden);
- }
- return;
- }
-
scene::ISceneNode *node = getSceneNode();
+
if (!node)
return;
+ if (m_prop.visual == "upright_sprite") {
+ // upright sprite has no backface culling
+ node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, hidden);
+ return;
+ }
+
if (hidden) {
// Hide the mesh by culling both front and
// back faces. Serious hackyness but it works for our
std::string m_current_texture_modifier = "";
bool m_visuals_expired = false;
float m_step_distance_counter = 0.0f;
- u8 m_last_light = 255;
+ video::SColor m_last_light = video::SColor(0xFFFFFFFF);
bool m_is_visible = false;
s8 m_glow = 0;
// Material
inline const v3f &getRotation() const { return m_rotation; }
- inline const u16 getHp() const
+ inline const v3f getAcceleration() const
+ {
+ return m_acceleration;
+ }
+
+ inline const v3f getVelocity() const
+ {
+ return m_velocity;
+ }
+
- const bool isImmortal();
++ inline u16 getHp() const
+ {
+ return m_hp;
+ }
+
+ bool isImmortal() const;
inline const ObjectProperties &getProperties() const { return m_prop; }
return m_is_local_player;
}
+ inline std::string getName() const
+ {
+ return m_name;
+ }
+
+ inline bool isPlayer() const
+ {
+ return m_is_player;
+ }
+
inline bool isVisible() const
{
return m_is_visible;
void addAttachmentChild(int child_id);
void removeAttachmentChild(int child_id);
ClientActiveObject *getParent() const;
+ int getParentId() const { return m_attachment_parent_id; }
const std::unordered_set<int> &getAttachmentChildIds() const
{ return m_attachment_child_ids; }
void updateAttachments();
void updateLight(u32 day_night_ratio);
- void setNodeLight(u8 light);
+ void setNodeLight(const video::SColor &light);
/* Get light position(s).
* returns number of positions written into pos[], which must have space
return m_prop.infotext;
}
+ float m_waiting_for_reattach;
+
+ ObjectProperties *getProperties()
+ {
+ return &m_prop;
+ }
+
+ void setProperties(ObjectProperties newprops);
+
void updateMeshCulling();
+
+ std::vector<std::string> nametag_images = {};
};
#else
#include "client/sound.h"
#endif
-/*
- Text input system
-*/
-
-struct TextDestNodeMetadata : public TextDest
-{
- TextDestNodeMetadata(v3s16 p, Client *client)
- {
- m_p = p;
- m_client = client;
- }
- // This is deprecated I guess? -celeron55
- void gotText(const std::wstring &text)
- {
- std::string ntext = wide_to_utf8(text);
- infostream << "Submitting 'text' field of node at (" << m_p.X << ","
- << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
- StringMap fields;
- fields["text"] = ntext;
- m_client->sendNodemetaFields(m_p, "", fields);
- }
- void gotText(const StringMap &fields)
- {
- m_client->sendNodemetaFields(m_p, "", fields);
- }
-
- v3s16 m_p;
- Client *m_client;
-};
-
-struct TextDestPlayerInventory : public TextDest
-{
- TextDestPlayerInventory(Client *client)
- {
- m_client = client;
- m_formname = "";
- }
- TextDestPlayerInventory(Client *client, const std::string &formname)
- {
- m_client = client;
- m_formname = formname;
- }
- void gotText(const StringMap &fields)
- {
- m_client->sendInventoryFields(m_formname, fields);
- }
-
- Client *m_client;
-};
-
-struct LocalFormspecHandler : public TextDest
-{
- LocalFormspecHandler(const std::string &formname)
- {
- m_formname = formname;
- }
-
- LocalFormspecHandler(const std::string &formname, Client *client):
- m_client(client)
- {
- m_formname = formname;
- }
-
- void gotText(const StringMap &fields)
- {
- if (m_formname == "MT_PAUSE_MENU") {
- if (fields.find("btn_sound") != fields.end()) {
- g_gamecallback->changeVolume();
- return;
- }
-
- if (fields.find("btn_key_config") != fields.end()) {
- g_gamecallback->keyConfig();
- return;
- }
-
- if (fields.find("btn_exit_menu") != fields.end()) {
- g_gamecallback->disconnect();
- return;
- }
-
- if (fields.find("btn_exit_os") != fields.end()) {
- g_gamecallback->exitToOS();
-#ifndef __ANDROID__
- RenderingEngine::get_raw_device()->closeDevice();
-#endif
- return;
- }
-
- if (fields.find("btn_change_password") != fields.end()) {
- g_gamecallback->changePassword();
- return;
- }
-
- return;
- }
-
- if (m_formname == "MT_DEATH_SCREEN") {
- assert(m_client != 0);
- m_client->sendRespawn();
- return;
- }
-
- if (m_client->modsLoaded())
- m_client->getScript()->on_formspec_input(m_formname, fields);
- }
-
- Client *m_client = nullptr;
-};
-
-/* Form update callback */
-
-class NodeMetadataFormSource: public IFormSource
-{
-public:
- NodeMetadataFormSource(ClientMap *map, v3s16 p):
- m_map(map),
- m_p(p)
- {
- }
- const std::string &getForm() const
- {
- static const std::string empty_string = "";
- NodeMetadata *meta = m_map->getNodeMetadata(m_p);
-
- if (!meta)
- return empty_string;
-
- return meta->getString("formspec");
- }
-
- virtual std::string resolveText(const std::string &str)
- {
- NodeMetadata *meta = m_map->getNodeMetadata(m_p);
-
- if (!meta)
- return str;
-
- return meta->resolveString(str);
- }
-
- ClientMap *m_map;
- v3s16 m_p;
-};
-
-class PlayerInventoryFormSource: public IFormSource
-{
-public:
- PlayerInventoryFormSource(Client *client):
- m_client(client)
- {
- }
-
- const std::string &getForm() const
- {
- LocalPlayer *player = m_client->getEnv().getLocalPlayer();
- return player->inventory_formspec;
- }
-
- Client *m_client;
-};
-
-class NodeDugEvent: public MtEvent
-{
-public:
- v3s16 p;
- MapNode n;
-
- NodeDugEvent(v3s16 p, MapNode n):
- p(p),
- n(n)
- {}
- MtEvent::Type getType() const
- {
- return MtEvent::NODE_DUG;
- }
-};
-
-class SoundMaker
-{
- ISoundManager *m_sound;
- const NodeDefManager *m_ndef;
-public:
- bool makes_footstep_sound;
- float m_player_step_timer;
- float m_player_jump_timer;
-
- SimpleSoundSpec m_player_step_sound;
- SimpleSoundSpec m_player_leftpunch_sound;
- SimpleSoundSpec m_player_rightpunch_sound;
-
- SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
- m_sound(sound),
- m_ndef(ndef),
- makes_footstep_sound(true),
- m_player_step_timer(0.0f),
- m_player_jump_timer(0.0f)
- {
- }
-
- void playPlayerStep()
- {
- if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
- m_player_step_timer = 0.03;
- if (makes_footstep_sound)
- m_sound->playSound(m_player_step_sound, false);
- }
- }
-
- void playPlayerJump()
- {
- if (m_player_jump_timer <= 0.0f) {
- m_player_jump_timer = 0.2f;
- m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
- }
- }
-
- static void viewBobbingStep(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- sm->playPlayerStep();
- }
-
- static void playerRegainGround(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- sm->playPlayerStep();
- }
-
- static void playerJump(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- sm->playPlayerJump();
- }
-
- static void cameraPunchLeft(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
- }
-
- static void cameraPunchRight(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
- }
-
- static void nodeDug(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- NodeDugEvent *nde = (NodeDugEvent *)e;
- sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
- }
-
- static void playerDamage(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
- }
-
- static void playerFallingDamage(MtEvent *e, void *data)
- {
- SoundMaker *sm = (SoundMaker *)data;
- sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
- }
-
- void registerReceiver(MtEventManager *mgr)
- {
- mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
- mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
- mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
- mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
- mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
- mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
- mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
- mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
- }
-
- void step(float dtime)
- {
- m_player_step_timer -= dtime;
- m_player_jump_timer -= dtime;
- }
-};
-
-// Locally stored sounds don't need to be preloaded because of this
-class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
-{
- std::set<std::string> m_fetched;
-private:
- void paths_insert(std::set<std::string> &dst_paths,
- const std::string &base,
- const std::string &name)
- {
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
- dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
- }
-public:
- void fetchSounds(const std::string &name,
- std::set<std::string> &dst_paths,
- std::set<std::string> &dst_datas)
- {
- if (m_fetched.count(name))
- return;
-
- m_fetched.insert(name);
-
- paths_insert(dst_paths, porting::path_share, name);
- paths_insert(dst_paths, porting::path_user, name);
- }
-};
-
-
-typedef s32 SamplerLayer_t;
-
-
-class GameGlobalShaderConstantSetter : public IShaderConstantSetter
-{
- Sky *m_sky;
- bool *m_force_fog_off;
- f32 *m_fog_range;
- bool m_fog_enabled;
- CachedPixelShaderSetting<float, 4> m_sky_bg_color;
- CachedPixelShaderSetting<float> m_fog_distance;
- CachedVertexShaderSetting<float> m_animation_timer_vertex;
- CachedPixelShaderSetting<float> m_animation_timer_pixel;
- CachedPixelShaderSetting<float, 3> m_day_light;
- CachedPixelShaderSetting<float, 4> m_star_color;
- CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
- CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
- CachedPixelShaderSetting<float, 3> m_minimap_yaw;
- CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
- CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
- CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
- CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
- Client *m_client;
-
-public:
- void onSettingsChange(const std::string &name)
- {
- if (name == "enable_fog")
- m_fog_enabled = g_settings->getBool("enable_fog");
- }
-
- static void settingsCallback(const std::string &name, void *userdata)
- {
- reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
- }
-
- void setSky(Sky *sky) { m_sky = sky; }
-
- GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
- f32 *fog_range, Client *client) :
- m_sky(sky),
- m_force_fog_off(force_fog_off),
- m_fog_range(fog_range),
- m_sky_bg_color("skyBgColor"),
- m_fog_distance("fogDistance"),
- m_animation_timer_vertex("animationTimer"),
- m_animation_timer_pixel("animationTimer"),
- m_day_light("dayLight"),
- m_star_color("starColor"),
- m_eye_position_pixel("eyePosition"),
- m_eye_position_vertex("eyePosition"),
- m_minimap_yaw("yawVec"),
- m_camera_offset_pixel("cameraOffset"),
- m_camera_offset_vertex("cameraOffset"),
- m_base_texture("baseTexture"),
- m_normal_texture("normalTexture"),
- m_client(client)
- {
- g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
- m_fog_enabled = g_settings->getBool("enable_fog");
- }
-
- ~GameGlobalShaderConstantSetter()
- {
- g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
- }
-
- void onSetConstants(video::IMaterialRendererServices *services) override
- {
- // Background color
- video::SColor bgcolor = m_sky->getBgColor();
- video::SColorf bgcolorf(bgcolor);
- float bgcolorfa[4] = {
- bgcolorf.r,
- bgcolorf.g,
- bgcolorf.b,
- bgcolorf.a,
- };
- m_sky_bg_color.set(bgcolorfa, services);
-
- // Fog distance
- float fog_distance = 10000 * BS;
-
- if (m_fog_enabled && !*m_force_fog_off)
- fog_distance = *m_fog_range;
-
- m_fog_distance.set(&fog_distance, services);
-
- u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
- video::SColorf sunlight;
- get_sunlight_color(&sunlight, daynight_ratio);
- float dnc[3] = {
- sunlight.r,
- sunlight.g,
- sunlight.b };
- m_day_light.set(dnc, services);
-
- video::SColorf star_color = m_sky->getCurrentStarColor();
- float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
- m_star_color.set(clr, services);
-
- u32 animation_timer = porting::getTimeMs() % 1000000;
- float animation_timer_f = (float)animation_timer / 100000.f;
- m_animation_timer_vertex.set(&animation_timer_f, services);
- m_animation_timer_pixel.set(&animation_timer_f, services);
-
- float eye_position_array[3];
- v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
- epos.getAs3Values(eye_position_array);
- m_eye_position_pixel.set(eye_position_array, services);
- m_eye_position_vertex.set(eye_position_array, services);
-
- if (m_client->getMinimap()) {
- float minimap_yaw_array[3];
- v3f minimap_yaw = m_client->getMinimap()->getYawVec();
- minimap_yaw.getAs3Values(minimap_yaw_array);
- m_minimap_yaw.set(minimap_yaw_array, services);
- }
-
- float camera_offset_array[3];
- v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
- offset.getAs3Values(camera_offset_array);
- m_camera_offset_pixel.set(camera_offset_array, services);
- m_camera_offset_vertex.set(camera_offset_array, services);
-
- SamplerLayer_t base_tex = 0, normal_tex = 1;
- m_base_texture.set(&base_tex, services);
- m_normal_texture.set(&normal_tex, services);
- }
-};
-
-
-class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
-{
- Sky *m_sky;
- bool *m_force_fog_off;
- f32 *m_fog_range;
- Client *m_client;
- std::vector<GameGlobalShaderConstantSetter *> created_nosky;
-public:
- GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
- f32 *fog_range, Client *client) :
- m_sky(NULL),
- m_force_fog_off(force_fog_off),
- m_fog_range(fog_range),
- m_client(client)
- {}
-
- void setSky(Sky *sky) {
- m_sky = sky;
- for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
- ggscs->setSky(m_sky);
- }
- created_nosky.clear();
- }
-
- virtual IShaderConstantSetter* create()
- {
- auto *scs = new GameGlobalShaderConstantSetter(
- m_sky, m_force_fog_off, m_fog_range, m_client);
- if (!m_sky)
- created_nosky.push_back(scs);
- return scs;
- }
-};
-
-#ifdef HAVE_TOUCHSCREENGUI
-#define SIZE_TAG "size[11,5.5]"
-#else
-#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
-#endif
-
-/****************************************************************************
- ****************************************************************************/
-
-const static float object_hit_delay = 0.2;
-
-struct FpsControl {
- FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
-
- void reset();
-
- void limit(IrrlichtDevice *device, f32 *dtime);
-
- u32 getBusyMs() const { return busy_time / 1000; }
-
- // all values in microseconds (us)
- u64 last_time, busy_time, sleep_time;
-};
-
-
-/* The reason the following structs are not anonymous structs within the
- * class is that they are not used by the majority of member functions and
- * many functions that do require objects of thse types do not modify them
- * (so they can be passed as a const qualified parameter)
- */
-
-struct GameRunData {
- u16 dig_index;
- u16 new_playeritem;
- PointedThing pointed_old;
- bool digging;
- bool punching;
- bool btn_down_for_dig;
- bool dig_instantly;
- bool digging_blocked;
- bool reset_jump_timer;
- float nodig_delay_timer;
- float dig_time;
- float dig_time_complete;
- float repeat_place_timer;
- float object_hit_delay_timer;
- float time_from_last_punch;
- ClientActiveObject *selected_object;
-
- float jump_timer;
- float damage_flash;
- float update_draw_list_timer;
-
- f32 fog_range;
-
- v3f update_draw_list_last_cam_dir;
-
- float time_of_day_smooth;
-};
-
-class Game;
-
-struct ClientEventHandler
-{
- void (Game::*handler)(ClientEvent *, CameraOrientation *);
-};
-
-/****************************************************************************
- THE GAME
- ****************************************************************************/
-
-using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
-
-/* This is not intended to be a public class. If a public class becomes
- * desirable then it may be better to create another 'wrapper' class that
- * hides most of the stuff in this class (nothing in this class is required
- * by any other file) but exposes the public methods/data only.
- */
-class Game {
-public:
- Game();
- ~Game();
-
- bool startup(bool *kill,
- InputHandler *input,
- RenderingEngine *rendering_engine,
- const GameStartData &game_params,
- std::string &error_message,
- bool *reconnect,
- ChatBackend *chat_backend);
-
- void run();
- void shutdown();
-
-protected:
-
- // Basic initialisation
- bool init(const std::string &map_dir, const std::string &address,
- u16 port, const SubgameSpec &gamespec);
- bool initSound();
- bool createSingleplayerServer(const std::string &map_dir,
- const SubgameSpec &gamespec, u16 port);
-
- // Client creation
- bool createClient(const GameStartData &start_data);
- bool initGui();
-
- // Client connection
- bool connectToServer(const GameStartData &start_data,
- bool *connect_ok, bool *aborted);
- bool getServerContent(bool *aborted);
-
- // Main loop
-
- void updateInteractTimers(f32 dtime);
- bool checkConnection();
- bool handleCallbacks();
- void processQueues();
- void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
- void updateDebugState();
- void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
- void updateProfilerGraphs(ProfilerGraph *graph);
-
- // Input related
- void processUserInput(f32 dtime);
- void processKeyInput();
- void processItemSelection(u16 *new_playeritem);
-
- void dropSelectedItem(bool single_item = false);
- void openInventory();
- void openConsole(float scale, const wchar_t *line=NULL);
- void toggleFreeMove();
- void toggleFreeMoveAlt();
- void togglePitchMove();
- void toggleFast();
- void toggleNoClip();
- void toggleCinematic();
- void toggleBlockBounds();
- void toggleAutoforward();
-
- void toggleMinimap(bool shift_pressed);
- void toggleFog();
- void toggleDebug();
- void toggleUpdateCamera();
-
- void increaseViewRange();
- void decreaseViewRange();
- void toggleFullViewRange();
- void checkZoomEnabled();
-
- void updateCameraDirection(CameraOrientation *cam, float dtime);
- void updateCameraOrientation(CameraOrientation *cam, float dtime);
- void updatePlayerControl(const CameraOrientation &cam);
- void step(f32 *dtime);
- void processClientEvents(CameraOrientation *cam);
- void updateCamera(f32 dtime);
- void updateSound(f32 dtime);
- void processPlayerInteraction(f32 dtime, bool show_hud);
- /*!
- * Returns the object or node the player is pointing at.
- * Also updates the selected thing in the Hud.
- *
- * @param[in] shootline the shootline, starting from
- * the camera position. This also gives the maximal distance
- * of the search.
- * @param[in] liquids_pointable if false, liquids are ignored
- * @param[in] look_for_object if false, objects are ignored
- * @param[in] camera_offset offset of the camera
- * @param[out] selected_object the selected object or
- * NULL if not found
- */
- PointedThing updatePointedThing(
- const core::line3d<f32> &shootline, bool liquids_pointable,
- bool look_for_object, const v3s16 &camera_offset);
- void handlePointingAtNothing(const ItemStack &playerItem);
- void handlePointingAtNode(const PointedThing &pointed,
- const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
- void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
- const v3f &player_position, bool show_debug);
- void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
- const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
- void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
- const CameraOrientation &cam);
- void updateShadows();
-
- // Misc
- void showOverlayMessage(const char *msg, float dtime, int percent,
- bool draw_clouds = true);
-
- static void settingChangedCallback(const std::string &setting_name, void *data);
- void readSettings();
-
- inline bool isKeyDown(GameKeyType k)
- {
- return input->isKeyDown(k);
- }
- inline bool wasKeyDown(GameKeyType k)
- {
- return input->wasKeyDown(k);
- }
- inline bool wasKeyPressed(GameKeyType k)
- {
- return input->wasKeyPressed(k);
- }
- inline bool wasKeyReleased(GameKeyType k)
- {
- return input->wasKeyReleased(k);
- }
-
-#ifdef __ANDROID__
- void handleAndroidChatInput();
-#endif
-
-private:
- struct Flags {
- bool force_fog_off = false;
- bool disable_camera_update = false;
- };
-
- void showDeathFormspec();
- void showPauseMenu();
-
- void pauseAnimation();
- void resumeAnimation();
-
- // ClientEvent handlers
- void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_HandleParticleEvent(ClientEvent *event,
- CameraOrientation *cam);
- void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
- void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
- CameraOrientation *cam);
- void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
-
- void updateChat(f32 dtime);
-
- bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
- const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
- const NodeMetadata *meta);
- static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
-
- f32 getSensitivityScaleFactor() const;
-
- InputHandler *input = nullptr;
-
- Client *client = nullptr;
- Server *server = nullptr;
-
- IWritableTextureSource *texture_src = nullptr;
- IWritableShaderSource *shader_src = nullptr;
-
- // When created, these will be filled with data received from the server
- IWritableItemDefManager *itemdef_manager = nullptr;
- NodeDefManager *nodedef_manager = nullptr;
-
- GameOnDemandSoundFetcher soundfetcher; // useful when testing
- ISoundManager *sound = nullptr;
- bool sound_is_dummy = false;
- SoundMaker *soundmaker = nullptr;
-
- ChatBackend *chat_backend = nullptr;
- LogOutputBuffer m_chat_log_buf;
-
- EventManager *eventmgr = nullptr;
- QuicktuneShortcutter *quicktune = nullptr;
- bool registration_confirmation_shown = false;
-
- std::unique_ptr<GameUI> m_game_ui;
- GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
- MapDrawControl *draw_control = nullptr;
- Camera *camera = nullptr;
- Clouds *clouds = nullptr; // Free using ->Drop()
- Sky *sky = nullptr; // Free using ->Drop()
- Hud *hud = nullptr;
- Minimap *mapper = nullptr;
-
- // Map server hud ids to client hud ids
- std::unordered_map<u32, u32> m_hud_server_to_client;
-
- GameRunData runData;
- Flags m_flags;
-
- /* 'cache'
- This class does take ownership/responsibily for cleaning up etc of any of
- these items (e.g. device)
- */
- IrrlichtDevice *device;
- RenderingEngine *m_rendering_engine;
- video::IVideoDriver *driver;
- scene::ISceneManager *smgr;
- bool *kill;
- std::string *error_message;
- bool *reconnect_requested;
- scene::ISceneNode *skybox;
- PausedNodesList paused_animated_nodes;
-
- bool simple_singleplayer_mode;
- /* End 'cache' */
-
- /* Pre-calculated values
- */
- int crack_animation_length;
-
- IntervalLimiter profiler_interval;
-
- /*
- * TODO: Local caching of settings is not optimal and should at some stage
- * be updated to use a global settings object for getting thse values
- * (as opposed to the this local caching). This can be addressed in
- * a later release.
- */
- bool m_cache_doubletap_jump;
- bool m_cache_enable_clouds;
- bool m_cache_enable_joysticks;
- bool m_cache_enable_particles;
- bool m_cache_enable_fog;
- bool m_cache_enable_noclip;
- bool m_cache_enable_free_move;
- f32 m_cache_mouse_sensitivity;
- f32 m_cache_joystick_frustum_sensitivity;
- f32 m_repeat_place_time;
- f32 m_cache_cam_smoothing;
- f32 m_cache_fog_start;
-
- bool m_invert_mouse = false;
- bool m_first_loop_after_window_activation = false;
- bool m_camera_offset_changed = false;
-
- bool m_does_lost_focus_pause_game = false;
-
-#if IRRLICHT_VERSION_MT_REVISION < 5
- int m_reset_HW_buffer_counter = 0;
-#endif
-
-#ifdef HAVE_TOUCHSCREENGUI
- bool m_cache_hold_aux1;
-#endif
-#ifdef __ANDROID__
- bool m_android_chat_open;
-#endif
-};
Game::Game() :
m_chat_log_buf(g_logger),
&settingChangedCallback, this);
g_settings->registerChangedCallback("camera_smoothing",
&settingChangedCallback, this);
+ g_settings->registerChangedCallback("freecam",
+ &freecamChangedCallback, this);
+ g_settings->registerChangedCallback("xray",
+ &updateAllMapBlocksCallback, this);
+ g_settings->registerChangedCallback("xray_nodes",
+ &updateAllMapBlocksCallback, this);
+ g_settings->registerChangedCallback("fullbright",
+ &updateAllMapBlocksCallback, this);
+ g_settings->registerChangedCallback("node_esp_nodes",
+ &updateAllMapBlocksCallback, this);
readSettings();
- #ifdef __ANDROID__
+ #ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = false; // This is initialised properly later
#endif
&settingChangedCallback, this);
g_settings->deregisterChangedCallback("camera_smoothing",
&settingChangedCallback, this);
+ g_settings->deregisterChangedCallback("freecam",
+ &freecamChangedCallback, this);
+ g_settings->deregisterChangedCallback("xray",
+ &updateAllMapBlocksCallback, this);
+ g_settings->deregisterChangedCallback("xray_nodes",
+ &updateAllMapBlocksCallback, this);
+ g_settings->deregisterChangedCallback("fullbright",
+ &updateAllMapBlocksCallback, this);
+ g_settings->deregisterChangedCallback("node_esp_nodes",
+ &updateAllMapBlocksCallback, this);
}
bool Game::startup(bool *kill,
void Game::run()
{
ProfilerGraph graph;
- RunStats stats = { 0 };
- FpsControl draw_times = { 0 };
+ RunStats stats = {};
- CameraOrientation cam_view_target = {};
- CameraOrientation cam_view = {};
+ FpsControl draw_times;
f32 dtime; // in seconds
/* Clear the profiler */
Profiler::GraphValues dummyvalues;
g_profiler->graphGet(dummyvalues);
- draw_times.last_time = m_rendering_engine->get_timer_time();
+ draw_times.reset();
set_light_table(g_settings->getFloat("display_gamma"));
- #ifdef __ANDROID__
+ #ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = g_settings->getBool("fast_move")
&& client->checkPrivilege("fast");
#endif
// Calculate dtime =
// m_rendering_engine->run() from this iteration
// + Sleep time until the wanted FPS are reached
- limitFps(&draw_times, &dtime);
+ draw_times.limit(device, &dtime);
// Prepare render data for next iteration
step(&dtime);
processClientEvents(&cam_view_target);
updateDebugState();
- updateCamera(draw_times.busy_time, dtime);
+ updateCamera(dtime);
updateSound(dtime);
- processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
- m_game_ui->m_flags.show_basic_debug);
+ processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
updateFrame(&graph, &stats, dtime, cam_view);
updateProfilerGraphs(&graph);
if (gui_chat_console)
gui_chat_console->drop();
+ if (m_cheat_menu)
+ delete m_cheat_menu;
+
if (sky)
sky->drop();
}
if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
- *error_message = "Unable to listen on " +
- bind_addr.serializeString() +
- " because IPv6 is disabled";
+ *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
+ bind_addr.serializeString().c_str());
errorstream << *error_message << std::endl;
return false;
}
if (!could_connect) {
if (error_message->empty() && !connect_aborted) {
// Should not happen if error messages are set properly
- *error_message = "Connection failed for unknown reason";
+ *error_message = gettext("Connection failed for unknown reason");
errorstream << *error_message << std::endl;
}
return false;
if (!getServerContent(&connect_aborted)) {
if (error_message->empty() && !connect_aborted) {
// Should not happen if error messages are set properly
- *error_message = "Connection failed for unknown reason";
+ *error_message = gettext("Connection failed for unknown reason");
errorstream << *error_message << std::endl;
}
return false;
/* Camera
*/
camera = new Camera(*draw_control, client, m_rendering_engine);
- if (!camera->successfullyCreated(*error_message))
- return false;
+ if (client->modsLoaded())
+ client->getScript()->on_camera_ready(camera);
client->setCamera(camera);
/* Clouds
str += L" [";
str += text;
str += L"]";
- delete text;
+ delete[] text;
}
str += L" [";
- str += driver->getName();
+ str += L"Minetest Hackclient";
str += L"]";
device->setWindowCaption(str.c_str());
gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
-1, chat_backend, client, &g_menumgr);
+ if (!gui_chat_console) {
+ *error_message = "Could not allocate memory for chat console";
+ errorstream << *error_message << std::endl;
+ return false;
+ }
+
+ m_cheat_menu = new CheatMenu(client);
+
+ if (!m_cheat_menu) {
+ *error_message = "Could not allocate memory for cheat menu";
+ errorstream << *error_message << std::endl;
+ return false;
+ }
+
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
connect_address.Resolve(start_data.address.c_str());
if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
- //connect_address.Resolve("localhost");
if (connect_address.isIPv6()) {
IPv6AddressBytes addr_bytes;
addr_bytes.bytes[15] = 1;
local_server_mode = true;
}
} catch (ResolveError &e) {
- *error_message = std::string("Couldn't resolve address: ") + e.what();
+ *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
+
errorstream << *error_message << std::endl;
return false;
}
if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
- *error_message = "Unable to connect to " +
- connect_address.serializeString() +
- " because IPv6 is disabled";
+ *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
errorstream << *error_message << std::endl;
return false;
}
- client = new Client(start_data.name.c_str(),
- start_data.password, start_data.address,
- *draw_control, texture_src, shader_src,
- itemdef_manager, nodedef_manager, sound, eventmgr,
- m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
+ try {
+ client = new Client(start_data.name.c_str(),
+ start_data.password, start_data.address,
+ *draw_control, texture_src, shader_src,
+ itemdef_manager, nodedef_manager, sound, eventmgr,
+ m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
+ client->migrateModStorage();
+ } catch (const BaseException &e) {
+ *error_message = fmtgettext("Error creating client: %s", e.what());
+ errorstream << *error_message << std::endl;
+ return false;
+ }
client->m_simple_singleplayer_mode = simple_singleplayer_mode;
infostream << "Connecting to server at ";
- connect_address.print(&infostream);
+ connect_address.print(infostream);
infostream << std::endl;
client->connect(connect_address,
try {
input->clear();
- FpsControl fps_control = { 0 };
+ FpsControl fps_control;
f32 dtime;
f32 wait_time = 0; // in seconds
- fps_control.last_time = m_rendering_engine->get_timer_time();
+ fps_control.reset();
while (m_rendering_engine->run()) {
- limitFps(&fps_control, &dtime);
+ fps_control.limit(device, &dtime);
// Update client and server
client->step(dtime);
break;
if (client->accessDenied()) {
- *error_message = "Access denied. Reason: "
- + client->accessDeniedReason();
+ *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
*reconnect_requested = client->reconnectRequested();
errorstream << *error_message << std::endl;
break;
wait_time += dtime;
// Only time out if we aren't waiting for the server we started
if (!start_data.address.empty() && wait_time > 10) {
- *error_message = "Connection timed out.";
+ *error_message = gettext("Connection timed out.");
errorstream << *error_message << std::endl;
break;
}
{
input->clear();
- FpsControl fps_control = { 0 };
+ FpsControl fps_control;
f32 dtime; // in seconds
- fps_control.last_time = m_rendering_engine->get_timer_time();
+ fps_control.reset();
while (m_rendering_engine->run()) {
- limitFps(&fps_control, &dtime);
+ fps_control.limit(device, &dtime);
// Update client and server
client->step(dtime);
return false;
if (client->getState() < LC_Init) {
- *error_message = "Client disconnected";
+ *error_message = gettext("Client disconnected");
errorstream << *error_message << std::endl;
return false;
}
inline bool Game::checkConnection()
{
if (client->accessDenied()) {
- *error_message = "Access denied. Reason: "
- + client->accessDeniedReason();
+ *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
*reconnect_requested = client->reconnectRequested();
errorstream << *error_message << std::endl;
return false;
void Game::updateDebugState()
{
- bool has_basic_debug = client->checkPrivilege("basic_debug");
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
bool has_debug = client->checkPrivilege("debug");
+ bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
if (m_game_ui->m_flags.show_basic_debug) {
- if (!has_basic_debug) {
+ if (!has_basic_debug)
m_game_ui->m_flags.show_basic_debug = false;
- }
} else if (m_game_ui->m_flags.show_minimal_debug) {
- if (has_basic_debug) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
}
if (!has_basic_debug)
hud->disableBlockBounds();
}
// Update update graphs
- g_profiler->graphAdd("Time non-rendering [ms]",
+ g_profiler->graphAdd("Time non-rendering [us]",
draw_times.busy_time - stats.drawtime);
- g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
+ g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
g_profiler->graphAdd("FPS", 1.0f / dtime);
}
/* Busytime average and jitter calculation
*/
jp = &stats->busy_time_jitter;
- jp->avg = jp->avg + draw_times.busy_time * 0.02;
+ jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
- jitter = draw_times.busy_time - jp->avg;
+ jitter = draw_times.getBusyMs() - jp->avg;
if (jitter > jp->max)
jp->max = jitter;
else if (g_touchscreengui) {
/* on touchscreengui step may generate own input events which ain't
* what we want in case we just did clear them */
+ g_touchscreengui->show();
g_touchscreengui->step(dtime);
}
#endif
void Game::processKeyInput()
{
+ if (wasKeyDown(KeyType::SELECT_UP)) {
+ m_cheat_menu->selectUp();
+ } else if (wasKeyDown(KeyType::SELECT_DOWN)) {
+ m_cheat_menu->selectDown();
+ } else if (wasKeyDown(KeyType::SELECT_LEFT)) {
+ m_cheat_menu->selectLeft();
+ } else if (wasKeyDown(KeyType::SELECT_RIGHT)) {
+ m_cheat_menu->selectRight();
+ } else if (wasKeyDown(KeyType::SELECT_CONFIRM)) {
+ m_cheat_menu->selectConfirm();
+ }
+
if (wasKeyDown(KeyType::DROP)) {
dropSelectedItem(isKeyDown(KeyType::SNEAK));
} else if (wasKeyDown(KeyType::AUTOFORWARD)) {
toggleAutoforward();
} else if (wasKeyDown(KeyType::INVENTORY)) {
openInventory();
+ } else if (wasKeyDown(KeyType::ENDERCHEST)) {
+ openEnderchest();
} else if (input->cancelPressed()) {
#ifdef __ANDROID__
m_android_chat_open = false;
toggleFast();
} else if (wasKeyDown(KeyType::NOCLIP)) {
toggleNoClip();
+ } else if (wasKeyDown(KeyType::KILLAURA)) {
+ toggleKillaura();
+ } else if (wasKeyDown(KeyType::FREECAM)) {
+ toggleFreecam();
+ } else if (wasKeyDown(KeyType::SCAFFOLD)) {
+ toggleScaffold();
#if USE_SOUND
} else if (wasKeyDown(KeyType::MUTE)) {
if (g_settings->getBool("enable_sound")) {
m_game_ui->toggleChat();
} else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
toggleFog();
+ } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) {
+ m_game_ui->toggleCheatMenu();
} else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
toggleUpdateCamera();
} else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
InventoryLocation inventoryloc;
inventoryloc.setCurrentPlayer();
- if (!client->modsLoaded()
- || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
- TextDest *txt_dst = new TextDestPlayerInventory(client);
- auto *&formspec = m_game_ui->updateFormspec("");
- GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
- &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
+ delete fs_src;
+ return;
+ }
- formspec->setFormSpec(fs_src->getForm(), inventoryloc);
+ if (fs_src->getForm().empty()) {
+ delete fs_src;
+ return;
}
+
+ TextDest *txt_dst = new TextDestPlayerInventory(client);
+ auto *&formspec = m_game_ui->updateFormspec("");
+ GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+ &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+
+ formspec->setFormSpec(fs_src->getForm(), inventoryloc);
}
+void Game::openEnderchest()
+{
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ if (!player || !player->getCAO())
+ return;
+
+ infostream << "Game: Launching special inventory" << std::endl;
+
+ if (client->modsLoaded())
+ client->getScript()->open_enderchest();
+}
+
void Game::openConsole(float scale, const wchar_t *line)
{
m_game_ui->showTranslatedStatusText("Fast mode disabled");
}
- #ifdef __ANDROID__
+ #ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = fast_move && has_fast_privs;
#endif
}
}
}
+void Game::toggleKillaura()
+{
+ bool killaura = ! g_settings->getBool("killaura");
+ g_settings->set("killaura", bool_to_cstr(killaura));
+
+ if (killaura) {
+ m_game_ui->showTranslatedStatusText("Killaura enabled");
+ } else {
+ m_game_ui->showTranslatedStatusText("Killaura disabled");
+ }
+}
+
+void Game::toggleFreecam()
+{
+ bool freecam = ! g_settings->getBool("freecam");
+ g_settings->set("freecam", bool_to_cstr(freecam));
+
+ if (freecam) {
+ m_game_ui->showTranslatedStatusText("Freecam enabled");
+ } else {
+ m_game_ui->showTranslatedStatusText("Freecam disabled");
+ }
+}
+
+void Game::toggleScaffold()
+{
+ bool scaffold = ! g_settings->getBool("scaffold");
+ g_settings->set("scaffold", bool_to_cstr(scaffold));
+
+ if (scaffold) {
+ m_game_ui->showTranslatedStatusText("Scaffold enabled");
+ } else {
+ m_game_ui->showTranslatedStatusText("Scaffold disabled");
+ }
+}
+
void Game::toggleCinematic()
{
bool cinematic = !g_settings->getBool("cinematic");
void Game::toggleBlockBounds()
{
- if (client->checkPrivilege("basic_debug")) {
- enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
- switch (newmode) {
- case Hud::BLOCK_BOUNDS_OFF:
- m_game_ui->showTranslatedStatusText("Block bounds hidden");
- break;
- case Hud::BLOCK_BOUNDS_CURRENT:
- m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
- break;
- case Hud::BLOCK_BOUNDS_NEAR:
- m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
- break;
- case Hud::BLOCK_BOUNDS_MAX:
- m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
- break;
- default:
- break;
- }
-
- } else {
- m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
+ m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
+ return;
+ }
+ enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
+ switch (newmode) {
+ case Hud::BLOCK_BOUNDS_OFF:
+ m_game_ui->showTranslatedStatusText("Block bounds hidden");
+ break;
+ case Hud::BLOCK_BOUNDS_CURRENT:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
+ break;
+ case Hud::BLOCK_BOUNDS_NEAR:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
+ break;
+ case Hud::BLOCK_BOUNDS_MAX:
+ m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
+ break;
+ default:
+ break;
}
}
void Game::toggleDebug()
{
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ bool has_debug = client->checkPrivilege("debug");
+ bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
// Initial: No debug info
// 1x toggle: Debug text
// 2x toggle: Debug text with profiler graph
// The debug text can be in 2 modes: minimal and basic.
// * Minimal: Only technical client info that not gameplay-relevant
// * Basic: Info that might give gameplay advantage, e.g. pos, angle
- // Basic mode is used when player has "basic_debug" priv,
+ // Basic mode is used when player has the debug HUD flag set,
// otherwise the Minimal mode is used.
if (!m_game_ui->m_flags.show_minimal_debug) {
m_game_ui->m_flags.show_minimal_debug = true;
- if (client->checkPrivilege("basic_debug")) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = false;
m_game_ui->showTranslatedStatusText("Debug info shown");
} else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
- if (client->checkPrivilege("basic_debug")) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
m_game_ui->m_flags.show_profiler_graph = true;
m_game_ui->showTranslatedStatusText("Profiler graph shown");
} else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
- if (client->checkPrivilege("basic_debug")) {
+ if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
- }
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = true;
m_game_ui->showTranslatedStatusText("Wireframe shown");
m_game_ui->m_flags.show_basic_debug = false;
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = false;
- if (client->checkPrivilege("debug")) {
+ if (has_debug) {
m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
} else {
m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
void Game::toggleUpdateCamera()
{
+ if (g_settings->getBool("freecam"))
+ return;
m_flags.disable_camera_update = !m_flags.disable_camera_update;
if (m_flags.disable_camera_update)
m_game_ui->showTranslatedStatusText("Camera update disabled");
//TimeTaker tt("update player control", NULL, PRECISION_NANO);
PlayerControl control(
+ isKeyDown(KeyType::FORWARD),
+ isKeyDown(KeyType::BACKWARD),
+ isKeyDown(KeyType::LEFT),
+ isKeyDown(KeyType::RIGHT),
isKeyDown(KeyType::JUMP) || player->getAutojump(),
isKeyDown(KeyType::AUX1),
isKeyDown(KeyType::SNEAK),
control.movement_direction = 0.0f;
}
- #ifdef ANDROID
- /* For Android, simulate holding down AUX1 (fast move) if the user has
+ #ifdef HAVE_TOUCHSCREENGUI
+ /* For touch, simulate holding down AUX1 (fast move) if the user has
* the fast_move setting toggled on. If there is an aux1 key defined for
- * Android then its meaning is inverted (i.e. holding aux1 means walk and
+ * touch then its meaning is inverted (i.e. holding aux1 means walk and
* not fast)
*/
if (m_cache_hold_aux1) {
}
#endif
- u32 keypress_bits = (
- ( (u32)(control.jump & 0x1) << 4) |
- ( (u32)(control.aux1 & 0x1) << 5) |
- ( (u32)(control.sneak & 0x1) << 6) |
- ( (u32)(control.dig & 0x1) << 7) |
- ( (u32)(control.place & 0x1) << 8) |
- ( (u32)(control.zoom & 0x1) << 9)
- );
-
- // Set direction keys to ensure mod compatibility
- if (control.movement_speed > 0.001f) {
- float absolute_direction;
-
- // Check in original orientation (absolute value indicates forward / backward)
- absolute_direction = abs(control.movement_direction);
- if (absolute_direction < (3.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 0); // Forward
- if (absolute_direction > (5.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 1); // Backward
-
- // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right)
- absolute_direction = control.movement_direction + M_PI_2;
- if (absolute_direction >= M_PI)
- absolute_direction -= 2 * M_PI;
- absolute_direction = abs(absolute_direction);
- if (absolute_direction < (3.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 2); // Left
- if (absolute_direction > (5.0f / 8.0f * M_PI))
- keypress_bits |= (u32)(0x1 << 3); // Right
- }
-
client->setPlayerControl(control);
- player->keyPressed = keypress_bits;
//tt.stop();
}
LocalPlayer *player = client->getEnv().getLocalPlayer();
f32 hp_max = player->getCAO() ?
- player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
+ player->getCAO()->getProperties()->hp_max : PLAYER_MAX_HP_DEFAULT;
f32 damage_ratio = event->player_damage.amount / hp_max;
runData.damage_flash += 95.0f + 64.f * damage_ratio;
void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
{
- FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
- LocalFormspecHandler *txt_dst =
- new LocalFormspecHandler(*event->show_formspec.formname, client);
- GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
- &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ if (event->show_formspec.formspec->empty()) {
+ auto formspec = m_game_ui->getFormspecGUI();
+ if (formspec && (event->show_formspec.formname->empty()
+ || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
+ formspec->quitMenu();
+ }
+ } else {
+ FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
+ LocalFormspecHandler *txt_dst =
+ new LocalFormspecHandler(*event->show_formspec.formname, client);
+ GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), &input->joystick,
+ fs_src, txt_dst, client->getFormspecPrepend(), sound);
+ }
delete event->show_formspec.formspec;
delete event->show_formspec.formname;
void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
{
sky->setStarsVisible(event->star_params->visible);
- sky->setStarCount(event->star_params->count, false);
+ sky->setStarCount(event->star_params->count);
sky->setStarColor(event->star_params->starcolor);
sky->setStarScale(event->star_params->scale);
delete event->star_params;
}
}
- void Game::updateChat(f32 dtime, const v2u32 &screensize)
+ void Game::updateChat(f32 dtime)
{
// Get new messages from error log buffer
while (!m_chat_log_buf.empty())
chat_backend->step(dtime);
// Display all messages in a static text element
- m_game_ui->setChatText(chat_backend->getRecentChat(),
- chat_backend->getRecentBuffer().getLineCount());
+ auto &buf = chat_backend->getRecentBuffer();
+ if (buf.getLinesModified()) {
+ buf.resetLinesModified();
+ m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
+ }
+
+ // Make sure that the size is still correct
+ m_game_ui->updateChatSize();
}
- void Game::updateCamera(u32 busy_time, f32 dtime)
+ void Game::updateCamera(f32 dtime)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
v3s16 old_camera_offset = camera->getOffset();
- if (wasKeyDown(KeyType::CAMERA_MODE)) {
- GenericCAO *playercao = player->getCAO();
-
- // If playercao not loaded, don't change camera
- if (!playercao)
- return;
-
+ if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) {
camera->toggleCameraMode();
-
- // Make the player visible depending on camera mode.
- playercao->updateMeshCulling();
- playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
+ updatePlayerCAOVisibility();
}
float full_punch_interval = playeritem_toolcap.full_punch_interval;
float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
- camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
+ camera->update(player, dtime, tool_reload_ratio);
camera->step(dtime);
v3f camera_position = camera->getPosition();
}
}
+void Game::updatePlayerCAOVisibility()
+{
+ // Make the player visible depending on camera mode.
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ GenericCAO *playercao = player->getCAO();
+ if (!playercao)
+ return;
+ playercao->updateMeshCulling();
+ bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam");
+ playercao->setChildrenVisible(is_visible);
+}
void Game::updateSound(f32 dtime)
{
}
- void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
+ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
+
+ if (g_settings->getBool("reach"))
+ d += g_settings->getU16("tool_range");
+
core::line3d<f32> shootline;
switch (camera->getCameraMode()) {
!runData.btn_down_for_dig,
camera_offset);
- if (pointed != runData.pointed_old) {
+ if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl;
- hud->updateSelectionMesh(camera_offset);
- }
+
+ // Note that updating the selection mesh every frame is not particularly efficient,
+ // but the halo rendering code is already inefficient so there's no point in optimizing it here
+ hud->updateSelectionMesh(camera_offset);
// Allow digging again if button is not pressed
if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
soundmaker->m_player_leftpunch_sound.name = "";
// Prepare for repeating, unless we're not supposed to
- if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
+ if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place"))
runData.repeat_place_timer += dtime;
else
runData.repeat_place_timer = 0;
handlePointingAtNode(pointed, selected_item, hand_item, dtime);
} else if (pointed.type == POINTEDTHING_OBJECT) {
v3f player_position = player->getPosition();
- handlePointingAtObject(pointed, tool_item, player_position, show_debug);
+ bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
+ handlePointingAtObject(pointed, tool_item, player_position,
+ m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
} else if (isKeyDown(KeyType::DIG)) {
// When button is held down in air, show continuous animation
runData.punching = true;
runData.selected_object = NULL;
hud->pointing_at_object = false;
-
- RaycastState s(shootline, look_for_object, liquids_pointable);
+ RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes"));
PointedThing result;
env.continueRaycast(&s, &result);
if (result.type == POINTEDTHING_OBJECT) {
return result;
}
-
void Game::handlePointingAtNothing(const ItemStack &playerItem)
{
infostream << "Attempted to place item while pointing at nothing" << std::endl;
ClientMap &map = client->getEnv().getClientMap();
- if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
+ if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig"))
&& !runData.digging_blocked
- && client->checkPrivilege("interact")) {
+ && client->checkPrivilege("interact"))
+ ) {
handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
}
} else {
MapNode n = map.getNode(nodepos);
- if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
- m_game_ui->setInfoText(L"Unknown node: " +
- utf8_to_wide(nodedef_manager->get(n).name));
+ if (nodedef_manager->get(n).name == "unknown") {
+ m_game_ui->setInfoText(L"Unknown node");
}
}
if ((wasKeyPressed(KeyType::PLACE) ||
- runData.repeat_place_timer >= m_repeat_place_time) &&
+ (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) &&
client->checkPrivilege("interact")) {
runData.repeat_place_timer = 0;
infostream << "Place button pressed while looking at ground" << std::endl;
bool Game::nodePlacement(const ItemDefinition &selected_def,
const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
- const PointedThing &pointed, const NodeMetadata *meta)
+ const PointedThing &pointed, const NodeMetadata *meta, bool force)
{
const auto &prediction = selected_def.node_placement_prediction;
// formspec in meta
if (meta && !meta->getString("formspec").empty() && !input->isRandom()
- && !isKeyDown(KeyType::SNEAK)) {
+ && !isKeyDown(KeyType::SNEAK) && !force) {
// on_rightclick callbacks are called anyway
if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
client->interact(INTERACT_PLACE, pointed);
// on_rightclick callback
if (prediction.empty() || (nodedef->get(node).rightclickable &&
- !isKeyDown(KeyType::SNEAK))) {
+ !isKeyDown(KeyType::SNEAK) && !force)) {
// Report to server
client->interact(INTERACT_PLACE, pointed);
return false;
m_game_ui->setInfoText(infotext);
- if (isKeyDown(KeyType::DIG)) {
+ if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) {
bool do_punch = false;
bool do_punch_damage = false;
- if (runData.object_hit_delay_timer <= 0.0) {
+ if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) {
do_punch = true;
do_punch_damage = true;
runData.object_hit_delay_timer = object_hit_delay;
dir, &tool_item, runData.time_from_last_punch);
runData.time_from_last_punch = 0;
- if (!disable_send)
+ if (!disable_send) {
client->interact(INTERACT_START_DIGGING, pointed);
+ }
}
} else if (wasKeyDown(KeyType::PLACE)) {
infostream << "Pressed place button while pointing at object" << std::endl;
// cheat detection.
// Get digging parameters
DigParams params = getDigParams(nodedef_manager->get(n).groups,
- &selected_item.getToolCapabilities(itemdef_manager));
+ &selected_item.getToolCapabilities(itemdef_manager),
+ selected_item.wear);
// If can't dig, try hand
if (!params.diggable) {
}
}
+ if(g_settings->getBool("instant_break")) {
+ runData.dig_time_complete = 0;
+ runData.dig_instantly = true;
+ }
if (!runData.digging) {
infostream << "Started digging" << std::endl;
runData.dig_instantly = runData.dig_time_complete == 0;
float direct_brightness;
bool sunlight_seen;
- if (m_cache_enable_noclip && m_cache_enable_free_move) {
+ if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) {
direct_brightness = time_brightness;
sunlight_seen = true;
} else {
}
/*
- Get chat messages from client
+ Damage camera tilt
*/
+ if (player->hurt_tilt_timer > 0.0f) {
+ player->hurt_tilt_timer -= dtime * 6.0f;
- v2u32 screensize = driver->getScreenSize();
- if (player->hurt_tilt_timer < 0.0f)
++ if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
+ player->hurt_tilt_strength = 0.0f;
+ }
+
+ /*
+ Update minimap pos and rotation
+ */
+ if (mapper && m_game_ui->m_flags.show_hud) {
+ mapper->setPos(floatToInt(player->getPosition(), BS));
+ mapper->setAngle(player->getYaw());
+ }
+
+ /*
+ Get chat messages from client
+ */
- updateChat(dtime, screensize);
+ updateChat(dtime);
/*
Inventory
v3f camera_direction = camera->getDirection();
if (runData.update_draw_list_timer >= update_draw_list_delta
|| runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
- || m_camera_offset_changed) {
-
+ || m_camera_offset_changed
+ || client->getEnv().getClientMap().needsUpdateDrawList()) {
runData.update_draw_list_timer = 0;
client->getEnv().getClientMap().updateDrawList();
runData.update_draw_list_last_cam_dir = camera_direction;
} while (false);
/*
- Drawing begins
+ ==================== Drawing begins ====================
*/
- const video::SColor &skycolor = sky->getSkyColor();
+ const video::SColor skycolor = sky->getSkyColor();
- TimeTaker tt_draw("Draw scene");
+ TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
driver->beginScene(true, true, skycolor);
bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
/*
Profiler graph
*/
+ v2u32 screensize = driver->getScreenSize();
+
if (m_game_ui->m_flags.show_profiler_graph)
graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
+ /*
+ Cheat menu
+ */
+
+ if (! gui_chat_console->isOpen()) {
+ if (m_game_ui->m_flags.show_cheat_menu)
+ m_cheat_menu->draw(driver, m_game_ui->m_flags.show_minimal_debug);
+ if (g_settings->getBool("cheat_hud"))
+ m_cheat_menu->drawHUD(driver, dtime);
+ }
/*
Damage flash
*/
if (runData.damage_flash > 0.0f) {
video::SColor color(runData.damage_flash, 180, 0, 0);
- driver->draw2DRectangle(color,
- core::rect<s32>(0, 0, screensize.X, screensize.Y),
- NULL);
+ if (! g_settings->getBool("no_hurt_cam"))
+ driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL);
runData.damage_flash -= 384.0f * dtime;
}
- /*
- Damage camera tilt
- */
- if (player->hurt_tilt_timer > 0.0f) {
- player->hurt_tilt_timer -= dtime * 6.0f;
-
- if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
- player->hurt_tilt_strength = 0.0f;
- }
-
- /*
- Update minimap pos and rotation
- */
- if (mapper && m_game_ui->m_flags.show_hud) {
- mapper->setPos(floatToInt(player->getPosition(), BS));
- mapper->setAngle(player->getYaw());
- }
-
/*
- ==================== End scene ====================
+ End scene
*/
+ #if IRRLICHT_VERSION_MT_REVISION < 5
if (++m_reset_HW_buffer_counter > 500) {
/*
Periodically remove all mesh HW buffers.
driver->removeAllHardwareBuffers();
m_reset_HW_buffer_counter = 0;
}
+ #endif
+
driver->endScene();
stats->drawtime = tt_draw.stop(true);
- g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
- g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
+ g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
+ g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
}
/* Log times and stuff for visualization */
float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
- float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
+ float timeoftheday = getWickedTimeOfDay(in_timeofday);
+ bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
+ bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
+ shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
+
+ timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
const float offset_constant = 10000.0f;
v3f light(0.0f, 0.0f, -1.0f);
Misc
****************************************************************************/
- /* On some computers framerate doesn't seem to be automatically limited
- */
- inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
+ void FpsControl::reset()
{
- // not using getRealTime is necessary for wine
- device->getTimer()->tick(); // Maker sure device time is up-to-date
- u32 time = device->getTimer()->getTime();
- u32 last_time = fps_timings->last_time;
-
- if (time > last_time) // Make sure time hasn't overflowed
- fps_timings->busy_time = time - last_time;
- else
- fps_timings->busy_time = 0;
+ last_time = porting::getTimeUs();
+ }
- u32 frametime_min = 1000 / (
+ /*
+ * On some computers framerate doesn't seem to be automatically limited
+ */
+ void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
+ {
+ const u64 frametime_min = 1000000.0f / (
device->isWindowFocused() && !g_menumgr.pausesGame()
? g_settings->getFloat("fps_max")
: g_settings->getFloat("fps_max_unfocused"));
- if (fps_timings->busy_time < frametime_min) {
- fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
- device->sleep(fps_timings->sleep_time);
+ u64 time = porting::getTimeUs();
+
+ if (time > last_time) // Make sure time hasn't overflowed
+ busy_time = time - last_time;
+ else
+ busy_time = 0;
+
+ if (busy_time < frametime_min) {
+ sleep_time = frametime_min - busy_time;
+ if (sleep_time > 1000)
+ sleep_ms(sleep_time / 1000);
} else {
- fps_timings->sleep_time = 0;
+ sleep_time = 0;
}
- /* Get the new value of the device timer. Note that device->sleep() may
- * not sleep for the entire requested time as sleep may be interrupted and
- * therefore it is arguably more accurate to get the new time from the
- * device rather than calculating it by adding sleep_time to time.
- */
-
- device->getTimer()->tick(); // Update device timer
- time = device->getTimer()->getTime();
+ // Read the timer again to accurately determine how long we actually slept,
+ // rather than calculating it by adding sleep_time to time.
+ time = porting::getTimeUs();
- if (time > last_time) // Make sure last_time hasn't overflowed
- *dtime = (time - last_time) / 1000.0;
+ if (time > last_time) // Make sure last_time hasn't overflowed
+ *dtime = (time - last_time) / 1000000.0f;
else
*dtime = 0;
- fps_timings->last_time = time;
+ last_time = time;
}
void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
((Game *)data)->readSettings();
}
+void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data)
+{
+ ((Game *) data)->client->updateAllMapBlocks();
+}
+
+void Game::freecamChangedCallback(const std::string &setting_name, void *data)
+{
+ Game *game = (Game *) data;
+ LocalPlayer *player = game->client->getEnv().getLocalPlayer();
+ if (g_settings->getBool("freecam")) {
+ game->camera->setCameraMode(CAMERA_MODE_FIRST);
+ player->freecamEnable();
+ } else {
+ player->freecamDisable();
+ }
+ game->updatePlayerCAOVisibility();
+}
+
void Game::readSettings()
{
m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
}
+bool Game::isKeyDown(GameKeyType k)
+{
+ return input->isKeyDown(k);
+}
+
+bool Game::wasKeyDown(GameKeyType k)
+{
+ return input->wasKeyDown(k);
+}
+
+bool Game::wasKeyPressed(GameKeyType k)
+{
+ return input->wasKeyPressed(k);
+}
+
+bool Game::wasKeyReleased(GameKeyType k)
+{
+ return input->wasKeyReleased(k);
+}
+
/****************************************************************************/
/****************************************************************************
Shutdown / cleanup
#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
void Game::showPauseMenu()
{
- #ifdef __ANDROID__
+ #ifdef HAVE_TOUCHSCREENGUI
static const std::string control_text = strgettext("Default Controls:\n"
"No menu visible:\n"
"- single tap: button activate\n"
"- %s: sneak/climb down\n"
"- %s: drop item\n"
"- %s: inventory\n"
+ "- %s: enderchest\n"
"- Mouse: turn/look\n"
"- Mouse wheel: select item\n"
"- %s: chat\n"
+ "- %s: Killaura\n"
+ "- %s: Freecam\n"
+ "- %s: Scaffold\n"
);
- char control_text_buf[600];
-
- porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
- GET_KEY_NAME(keymap_forward),
- GET_KEY_NAME(keymap_backward),
- GET_KEY_NAME(keymap_left),
- GET_KEY_NAME(keymap_right),
- GET_KEY_NAME(keymap_jump),
- GET_KEY_NAME(keymap_dig),
- GET_KEY_NAME(keymap_place),
- GET_KEY_NAME(keymap_sneak),
- GET_KEY_NAME(keymap_drop),
- GET_KEY_NAME(keymap_inventory),
- GET_KEY_NAME(keymap_chat)
- );
+ char control_text_buf[600];
+
+ porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
+ GET_KEY_NAME(keymap_forward),
+ GET_KEY_NAME(keymap_backward),
+ GET_KEY_NAME(keymap_left),
+ GET_KEY_NAME(keymap_right),
+ GET_KEY_NAME(keymap_jump),
+ GET_KEY_NAME(keymap_dig),
+ GET_KEY_NAME(keymap_place),
+ GET_KEY_NAME(keymap_sneak),
+ GET_KEY_NAME(keymap_drop),
+ GET_KEY_NAME(keymap_inventory),
+ GET_KEY_NAME(keymap_enderchest),
+ GET_KEY_NAME(keymap_chat),
+ GET_KEY_NAME(keymap_toggle_killaura),
+ GET_KEY_NAME(keymap_toggle_freecam),
+ GET_KEY_NAME(keymap_toggle_scaffold)
+ );
std::string control_text = std::string(control_text_buf);
str_formspec_escape(control_text);
if (simple_singleplayer_mode || address.empty()) {
static const std::string on = strgettext("On");
static const std::string off = strgettext("Off");
- const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
- const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
+ // Note: Status of enable_damage and creative_mode settings is intentionally
+ // NOT shown here because the game might roll its own damage system and/or do
+ // a per-player Creative Mode, in which case writing it here would mislead.
+ bool damage = g_settings->getBool("enable_damage");
const std::string &announced = g_settings->getBool("server_announce") ? on : off;
- os << strgettext("- Damage: ") << damage << "\n"
- << strgettext("- Creative Mode: ") << creative << "\n";
if (!simple_singleplayer_mode) {
- const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
- //~ PvP = Player versus Player
- os << strgettext("- PvP: ") << pvp << "\n"
- << strgettext("- Public: ") << announced << "\n";
+ if (damage) {
+ const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
+ //~ PvP = Player versus Player
+ os << strgettext("- PvP: ") << pvp << "\n";
+ }
+ os << strgettext("- Public: ") << announced << "\n";
std::string server_name = g_settings->get("server_name");
str_formspec_escape(server_name);
if (announced == on && !server_name.empty())
****************************************************************************/
/****************************************************************************/
+Game *g_game;
+
void the_game(bool *kill,
InputHandler *input,
RenderingEngine *rendering_engine,
{
Game game;
+ g_game = &game;
+
/* Make a copy of the server address because if a local singleplayer server
* is created then this is updated and we don't want to change the value
* passed to us by the calling function
}
} catch (SerializationError &e) {
- error_message = std::string("A serialization error occurred:\n")
- + e.what() + "\n\nThe server is probably "
- " running a different version of " PROJECT_NAME_C ".";
+ const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
+ error_message = strgettext("A serialization error occurred:") +"\n"
+ + e.what() + "\n\n" + ver_err;
errorstream << error_message << std::endl;
} catch (ServerError &e) {
error_message = e.what();
errorstream << "ServerError: " << error_message << std::endl;
} catch (ModError &e) {
+ // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
error_message = std::string("ModError: ") + e.what() +
strgettext("\nCheck debug.txt for details.");
errorstream << error_message << std::endl;
#pragma once
+#include <iomanip>
+#include <cmath>
+#include "client/renderingengine.h"
+#include "camera.h"
+#include "client.h"
+#include "client/clientevent.h"
+//#include "client/gameui.h"
+#include "client/inputhandler.h"
+#include "client/sound.h"
+#include "client/tile.h" // For TextureSource
+#include "client/keys.h"
+#include "client/joystick_controller.h"
+#include "clientmap.h"
+#include "clouds.h"
+#include "config.h"
+#include "content_cao.h"
+#include "client/event_manager.h"
+#include "fontengine.h"
+#include "itemdef.h"
+#include "log.h"
+#include "filesys.h"
+#include "gettext.h"
+#include "gui/cheatMenu.h"
+#include "gui/guiChatConsole.h"
+#include "gui/guiConfirmRegistration.h"
+#include "gui/guiFormSpecMenu.h"
+#include "gui/guiKeyChangeMenu.h"
+#include "gui/guiPasswordChange.h"
+#include "gui/guiVolumeChange.h"
+#include "gui/mainmenumanager.h"
+#include "gui/profilergraph.h"
+#include "mapblock.h"
+#include "minimap.h"
+#include "nodedef.h" // Needed for determining pointing to nodes
+#include "nodemetadata.h"
+#include "particles.h"
+#include "porting.h"
+#include "profiler.h"
+#include "raycast.h"
+#include "server.h"
+#include "settings.h"
+#include "shader.h"
+#include "sky.h"
+#include "translation.h"
+#include "util/basic_macros.h"
+#include "util/directiontables.h"
+#include "util/pointedthing.h"
+#include "util/quicktune_shortcutter.h"
+#include "irrlicht_changes/static_text.h"
+#include "irr_ptr.h"
+#include "version.h"
+#include "script/scripting_client.h"
+#include "hud.h"
#include "irrlichttypes.h"
#include <string>
};
struct RunStats {
- u32 drawtime;
+ u64 drawtime; // (us)
Jitter dtime_jitter, busy_time_jitter;
};
f32 camera_pitch; // "up/down"
};
- #ifdef __ANDROID__
+/*
+ Text input system
+*/
+
+struct TextDestNodeMetadata : public TextDest
+{
+ TextDestNodeMetadata(v3s16 p, Client *client)
+ {
+ m_p = p;
+ m_client = client;
+ }
+ // This is deprecated I guess? -celeron55
+ void gotText(const std::wstring &text)
+ {
+ std::string ntext = wide_to_utf8(text);
+ infostream << "Submitting 'text' field of node at (" << m_p.X << ","
+ << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
+ StringMap fields;
+ fields["text"] = ntext;
+ m_client->sendNodemetaFields(m_p, "", fields);
+ }
+ void gotText(const StringMap &fields)
+ {
+ m_client->sendNodemetaFields(m_p, "", fields);
+ }
+
+ v3s16 m_p;
+ Client *m_client;
+};
+
+struct TextDestPlayerInventory : public TextDest
+{
+ TextDestPlayerInventory(Client *client)
+ {
+ m_client = client;
+ m_formname = "";
+ }
+ TextDestPlayerInventory(Client *client, const std::string &formname)
+ {
+ m_client = client;
+ m_formname = formname;
+ }
+ void gotText(const StringMap &fields)
+ {
+ m_client->sendInventoryFields(m_formname, fields);
+ }
+
+ Client *m_client;
+};
+
+struct LocalFormspecHandler : public TextDest
+{
+ LocalFormspecHandler(const std::string &formname)
+ {
+ m_formname = formname;
+ }
+
+ LocalFormspecHandler(const std::string &formname, Client *client):
+ m_client(client)
+ {
+ m_formname = formname;
+ }
+
+ void gotText(const StringMap &fields)
+ {
+ if (m_formname == "MT_PAUSE_MENU") {
+ if (fields.find("btn_sound") != fields.end()) {
+ g_gamecallback->changeVolume();
+ return;
+ }
+
+ if (fields.find("btn_key_config") != fields.end()) {
+ g_gamecallback->keyConfig();
+ return;
+ }
+
+ if (fields.find("btn_exit_menu") != fields.end()) {
+ g_gamecallback->disconnect();
+ return;
+ }
+
+ if (fields.find("btn_exit_os") != fields.end()) {
+ g_gamecallback->exitToOS();
+#ifndef __ANDROID__
+ RenderingEngine::get_raw_device()->closeDevice();
+#endif
+ return;
+ }
+
+ if (fields.find("btn_change_password") != fields.end()) {
+ g_gamecallback->changePassword();
+ return;
+ }
+
+ return;
+ }
+
+ if (m_formname == "MT_DEATH_SCREEN") {
+ assert(m_client != 0);
+ m_client->sendRespawn();
+ return;
+ }
+
+ if (m_client->modsLoaded())
+ m_client->getScript()->on_formspec_input(m_formname, fields);
+ }
+
+ Client *m_client = nullptr;
+};
+
+/* Form update callback */
+
+class NodeMetadataFormSource: public IFormSource
+{
+public:
+ NodeMetadataFormSource(ClientMap *map, v3s16 p):
+ m_map(map),
+ m_p(p)
+ {
+ }
+ const std::string &getForm() const
+ {
+ static const std::string empty_string = "";
+ NodeMetadata *meta = m_map->getNodeMetadata(m_p);
+
+ if (!meta)
+ return empty_string;
+
+ return meta->getString("formspec");
+ }
+
+ virtual std::string resolveText(const std::string &str)
+ {
+ NodeMetadata *meta = m_map->getNodeMetadata(m_p);
+
+ if (!meta)
+ return str;
+
+ return meta->resolveString(str);
+ }
+
+ ClientMap *m_map;
+ v3s16 m_p;
+};
+
+class PlayerInventoryFormSource: public IFormSource
+{
+public:
+ PlayerInventoryFormSource(Client *client):
+ m_client(client)
+ {
+ }
+
+ const std::string &getForm() const
+ {
+ LocalPlayer *player = m_client->getEnv().getLocalPlayer();
+ return player->inventory_formspec;
+ }
+
+ Client *m_client;
+};
+
+class NodeDugEvent: public MtEvent
+{
+public:
+ v3s16 p;
+ MapNode n;
+
+ NodeDugEvent(v3s16 p, MapNode n):
+ p(p),
+ n(n)
+ {}
+ MtEvent::Type getType() const
+ {
+ return MtEvent::NODE_DUG;
+ }
+};
+
+class SoundMaker
+{
+ ISoundManager *m_sound;
+ const NodeDefManager *m_ndef;
+public:
+ bool makes_footstep_sound;
+ float m_player_step_timer;
+ float m_player_jump_timer;
+
+ SimpleSoundSpec m_player_step_sound;
+ SimpleSoundSpec m_player_leftpunch_sound;
+ SimpleSoundSpec m_player_rightpunch_sound;
+
+ SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
+ m_sound(sound),
+ m_ndef(ndef),
+ makes_footstep_sound(true),
+ m_player_step_timer(0.0f),
+ m_player_jump_timer(0.0f)
+ {
+ }
+
+ void playPlayerStep()
+ {
+ if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
+ m_player_step_timer = 0.03;
+ if (makes_footstep_sound)
+ m_sound->playSound(m_player_step_sound, false);
+ }
+ }
+
+ void playPlayerJump()
+ {
+ if (m_player_jump_timer <= 0.0f) {
+ m_player_jump_timer = 0.2f;
+ m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
+ }
+ }
+
+ static void viewBobbingStep(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->playPlayerStep();
+ }
+
+ static void playerRegainGround(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->playPlayerStep();
+ }
+
+ static void playerJump(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->playPlayerJump();
+ }
+
+ static void cameraPunchLeft(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
+ }
+
+ static void cameraPunchRight(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
+ }
+
+ static void nodeDug(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ NodeDugEvent *nde = (NodeDugEvent *)e;
+ sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
+ }
+
+ static void playerDamage(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
+ }
+
+ static void playerFallingDamage(MtEvent *e, void *data)
+ {
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
+ }
+
+ void registerReceiver(MtEventManager *mgr)
+ {
+ mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
+ mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
+ mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
+ mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
+ mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
+ mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
+ mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
+ mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
+ }
+
+ void step(float dtime)
+ {
+ m_player_step_timer -= dtime;
+ m_player_jump_timer -= dtime;
+ }
+};
+
+// Locally stored sounds don't need to be preloaded because of this
+class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
+{
+ std::set<std::string> m_fetched;
+private:
+ void paths_insert(std::set<std::string> &dst_paths,
+ const std::string &base,
+ const std::string &name)
+ {
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
+ dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
+ }
+public:
+ void fetchSounds(const std::string &name,
+ std::set<std::string> &dst_paths,
+ std::set<std::string> &dst_datas)
+ {
+ if (m_fetched.count(name))
+ return;
+
+ m_fetched.insert(name);
+
+ paths_insert(dst_paths, porting::path_share, name);
+ paths_insert(dst_paths, porting::path_user, name);
+ }
+};
+
+
+typedef s32 SamplerLayer_t;
+
+
+class GameGlobalShaderConstantSetter : public IShaderConstantSetter
+{
+ Sky *m_sky;
+ bool *m_force_fog_off;
+ f32 *m_fog_range;
+ bool m_fog_enabled;
+ CachedPixelShaderSetting<float, 4> m_sky_bg_color;
+ CachedPixelShaderSetting<float> m_fog_distance;
+ CachedVertexShaderSetting<float> m_animation_timer_vertex;
+ CachedPixelShaderSetting<float> m_animation_timer_pixel;
+ CachedPixelShaderSetting<float, 3> m_day_light;
+ CachedPixelShaderSetting<float, 4> m_star_color;
+ CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
+ CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
+ CachedPixelShaderSetting<float, 3> m_minimap_yaw;
+ CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
+ CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
+ CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
+ CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
+ Client *m_client;
+
+public:
+ void onSettingsChange(const std::string &name)
+ {
+ if (name == "enable_fog")
+ m_fog_enabled = g_settings->getBool("enable_fog");
+ }
+
+ static void settingsCallback(const std::string &name, void *userdata)
+ {
+ reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
+ }
+
+ void setSky(Sky *sky) { m_sky = sky; }
+
+ GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
+ f32 *fog_range, Client *client) :
+ m_sky(sky),
+ m_force_fog_off(force_fog_off),
+ m_fog_range(fog_range),
+ m_sky_bg_color("skyBgColor"),
+ m_fog_distance("fogDistance"),
+ m_animation_timer_vertex("animationTimer"),
+ m_animation_timer_pixel("animationTimer"),
+ m_day_light("dayLight"),
+ m_star_color("starColor"),
+ m_eye_position_pixel("eyePosition"),
+ m_eye_position_vertex("eyePosition"),
+ m_minimap_yaw("yawVec"),
+ m_camera_offset_pixel("cameraOffset"),
+ m_camera_offset_vertex("cameraOffset"),
+ m_base_texture("baseTexture"),
+ m_normal_texture("normalTexture"),
+ m_client(client)
+ {
+ g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
+ m_fog_enabled = g_settings->getBool("enable_fog");
+ }
+
+ ~GameGlobalShaderConstantSetter()
+ {
+ g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
+ }
+
+ void onSetConstants(video::IMaterialRendererServices *services) override
+ {
+ // Background color
+ video::SColor bgcolor = m_sky->getBgColor();
+ video::SColorf bgcolorf(bgcolor);
+ float bgcolorfa[4] = {
+ bgcolorf.r,
+ bgcolorf.g,
+ bgcolorf.b,
+ bgcolorf.a,
+ };
+ m_sky_bg_color.set(bgcolorfa, services);
+
+ // Fog distance
+ float fog_distance = 10000 * BS;
+
+ if (m_fog_enabled && !*m_force_fog_off)
+ fog_distance = *m_fog_range;
+
+ m_fog_distance.set(&fog_distance, services);
+
+ u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
+ video::SColorf sunlight;
+ get_sunlight_color(&sunlight, daynight_ratio);
+ float dnc[3] = {
+ sunlight.r,
+ sunlight.g,
+ sunlight.b };
+ m_day_light.set(dnc, services);
+
+ video::SColorf star_color = m_sky->getCurrentStarColor();
+ float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
+ m_star_color.set(clr, services);
+
+ u32 animation_timer = porting::getTimeMs() % 1000000;
+ float animation_timer_f = (float)animation_timer / 100000.f;
+ m_animation_timer_vertex.set(&animation_timer_f, services);
+ m_animation_timer_pixel.set(&animation_timer_f, services);
+
+ float eye_position_array[3];
+ v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
+ epos.getAs3Values(eye_position_array);
+ m_eye_position_pixel.set(eye_position_array, services);
+ m_eye_position_vertex.set(eye_position_array, services);
+
+ if (m_client->getMinimap()) {
+ float minimap_yaw_array[3];
+ v3f minimap_yaw = m_client->getMinimap()->getYawVec();
+ minimap_yaw.getAs3Values(minimap_yaw_array);
+ m_minimap_yaw.set(minimap_yaw_array, services);
+ }
+
+ float camera_offset_array[3];
+ v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
+ offset.getAs3Values(camera_offset_array);
+ m_camera_offset_pixel.set(camera_offset_array, services);
+ m_camera_offset_vertex.set(camera_offset_array, services);
+
+ SamplerLayer_t base_tex = 0, normal_tex = 1;
+ m_base_texture.set(&base_tex, services);
+ m_normal_texture.set(&normal_tex, services);
+ }
+};
+
+
+class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
+{
+ Sky *m_sky;
+ bool *m_force_fog_off;
+ f32 *m_fog_range;
+ Client *m_client;
+ std::vector<GameGlobalShaderConstantSetter *> created_nosky;
+public:
+ GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
+ f32 *fog_range, Client *client) :
+ m_sky(NULL),
+ m_force_fog_off(force_fog_off),
+ m_fog_range(fog_range),
+ m_client(client)
+ {}
+
+ void setSky(Sky *sky) {
+ m_sky = sky;
+ for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
+ ggscs->setSky(m_sky);
+ }
+ created_nosky.clear();
+ }
+
+ virtual IShaderConstantSetter* create()
+ {
+ auto *scs = new GameGlobalShaderConstantSetter(
+ m_sky, m_force_fog_off, m_fog_range, m_client);
+ if (!m_sky)
+ created_nosky.push_back(scs);
+ return scs;
+ }
+};
+
- u32 last_time, busy_time, sleep_time;
++#ifdef HAVE_TOUCHSCREENGUI
+#define SIZE_TAG "size[11,5.5]"
+#else
+#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
+#endif
+
+/****************************************************************************
+ ****************************************************************************/
+
+const float object_hit_delay = 0.2;
+
+struct FpsControl {
- void updateCamera(u32 busy_time, f32 dtime);
++ FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
++
++ void reset();
++
++ void limit(IrrlichtDevice *device, f32 *dtime);
++
++ u32 getBusyMs() const { return busy_time / 1000; }
++
++ // all values in microseconds (us)
++ u64 last_time, busy_time, sleep_time;
+};
+
+
+/* The reason the following structs are not anonymous structs within the
+ * class is that they are not used by the majority of member functions and
+ * many functions that do require objects of thse types do not modify them
+ * (so they can be passed as a const qualified parameter)
+ */
+
+struct GameRunData {
+ u16 dig_index;
+ u16 new_playeritem;
+ PointedThing pointed_old;
+ bool digging;
+ bool punching;
+ bool btn_down_for_dig;
+ bool dig_instantly;
+ bool digging_blocked;
+ bool reset_jump_timer;
+ float nodig_delay_timer;
+ float dig_time;
+ float dig_time_complete;
+ float repeat_place_timer;
+ float object_hit_delay_timer;
+ float time_from_last_punch;
+ ClientActiveObject *selected_object;
+
+ float jump_timer;
+ float damage_flash;
+ float update_draw_list_timer;
+
+ f32 fog_range;
+
+ v3f update_draw_list_last_cam_dir;
+
+ float time_of_day_smooth;
+};
+
+class Game;
+
+struct ClientEventHandler
+{
+ void (Game::*handler)(ClientEvent *, CameraOrientation *);
+};
+
+using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
+
+class Game {
+public:
+ Game();
+ ~Game();
+
+ bool startup(bool *kill,
+ InputHandler *input,
+ RenderingEngine *rendering_engine,
+ const GameStartData &game_params,
+ std::string &error_message,
+ bool *reconnect,
+ ChatBackend *chat_backend);
+
+
+ void run();
+ void shutdown();
+
+ // Basic initialisation
+ bool init(const std::string &map_dir, const std::string &address,
+ u16 port, const SubgameSpec &gamespec);
+ bool initSound();
+ bool createSingleplayerServer(const std::string &map_dir,
+ const SubgameSpec &gamespec, u16 port);
+
+ // Client creation
+ bool createClient(const GameStartData &start_data);
+ bool initGui();
+
+ // Client connection
+ bool connectToServer(const GameStartData &start_data,
+ bool *connect_ok, bool *aborted);
+ bool getServerContent(bool *aborted);
+
+ // Main loop
+
+ void updateInteractTimers(f32 dtime);
+ bool checkConnection();
+ bool handleCallbacks();
+ void processQueues();
+ void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
+ void updateDebugState();
+ void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
+ void updateProfilerGraphs(ProfilerGraph *graph);
+
+ // Input related
+ void processUserInput(f32 dtime);
+ void processKeyInput();
+ void processItemSelection(u16 *new_playeritem);
+
+ void dropSelectedItem(bool single_item = false);
+ void openInventory();
+ void openEnderchest();
+ void openConsole(float scale, const wchar_t *line=NULL);
+ void toggleFreeMove();
+ void toggleFreeMoveAlt();
+ void togglePitchMove();
+ void toggleFast();
+ void toggleNoClip();
+ void toggleKillaura();
+ void toggleFreecam();
+ void toggleScaffold();
+ void toggleNextItem();
+ void toggleCinematic();
+ void toggleBlockBounds();
+ void toggleAutoforward();
+
+ void toggleMinimap(bool shift_pressed);
+ void toggleFog();
+ void toggleDebug();
+ void toggleUpdateCamera();
+ void updatePlayerCAOVisibility();
+
+ void increaseViewRange();
+ void decreaseViewRange();
+ void toggleFullViewRange();
+ void checkZoomEnabled();
+
+ void updateCameraDirection(CameraOrientation *cam, float dtime);
+ void updateCameraOrientation(CameraOrientation *cam, float dtime);
+ void updatePlayerControl(const CameraOrientation &cam);
+ void step(f32 *dtime);
+ void processClientEvents(CameraOrientation *cam);
- void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
++ void updateCamera(f32 dtime);
+ void updateSound(f32 dtime);
- void limitFps(FpsControl *fps_timings, f32 *dtime);
-
++ void processPlayerInteraction(f32 dtime, bool show_hud);
+ /*!
+ * Returns the object or node the player is pointing at.
+ * Also updates the selected thing in the Hud.
+ *
+ * @param[in] shootline the shootline, starting from
+ * the camera position. This also gives the maximal distance
+ * of the search.
+ * @param[in] liquids_pointable if false, liquids are ignored
+ * @param[in] look_for_object if false, objects are ignored
+ * @param[in] camera_offset offset of the camera
+ * @param[out] selected_object the selected object or
+ * NULL if not found
+ */
+ PointedThing updatePointedThing(
+ const core::line3d<f32> &shootline, bool liquids_pointable,
+ bool look_for_object, const v3s16 &camera_offset);
+ void handlePointingAtNothing(const ItemStack &playerItem);
+ void handlePointingAtNode(const PointedThing &pointed,
+ const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
+ void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
+ const v3f &player_position, bool show_debug);
+ void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
+ const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
+ void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
+ const CameraOrientation &cam);
+ void updateShadows();
+
+ // Misc
- void updateChat(f32 dtime, const v2u32 &screensize);
+ void showOverlayMessage(const char *msg, float dtime, int percent,
+ bool draw_clouds = true);
+
+ static void freecamChangedCallback(const std::string &setting_name, void *data);
+ static void settingChangedCallback(const std::string &setting_name, void *data);
+ static void updateAllMapBlocksCallback(const std::string &setting_name, void *data);
+ void readSettings();
+
+ bool isKeyDown(GameKeyType k);
+ bool wasKeyDown(GameKeyType k);
+ bool wasKeyPressed(GameKeyType k);
+ bool wasKeyReleased(GameKeyType k);
+
+#ifdef __ANDROID__
+ void handleAndroidChatInput();
+#endif
+
+ struct Flags {
+ bool force_fog_off = false;
+ bool disable_camera_update = false;
+ };
+
+ void showDeathFormspec();
+ void showPauseMenu();
+
+ void pauseAnimation();
+ void resumeAnimation();
+
+ // ClientEvent handlers
+ void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_HandleParticleEvent(ClientEvent *event,
+ CameraOrientation *cam);
+ void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
+ void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
+ CameraOrientation *cam);
+ void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
+
- CameraOrientation cam_view_target = { 0 };
- CameraOrientation cam_view = { 0 };
++ void updateChat(f32 dtime);
+
+ bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
+ const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
+ const NodeMetadata *meta, bool force = false);
+ static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
+
+ f32 getSensitivityScaleFactor() const;
+
+ InputHandler *input = nullptr;
+
+ Client *client = nullptr;
+ Server *server = nullptr;
+
+ IWritableTextureSource *texture_src = nullptr;
+ IWritableShaderSource *shader_src = nullptr;
+
+ // When created, these will be filled with data received from the server
+ IWritableItemDefManager *itemdef_manager = nullptr;
+ NodeDefManager *nodedef_manager = nullptr;
+
+ GameOnDemandSoundFetcher soundfetcher; // useful when testing
+ ISoundManager *sound = nullptr;
+ bool sound_is_dummy = false;
+ SoundMaker *soundmaker = nullptr;
+
+ ChatBackend *chat_backend = nullptr;
+ LogOutputBuffer m_chat_log_buf;
+
+ EventManager *eventmgr = nullptr;
+ QuicktuneShortcutter *quicktune = nullptr;
+ bool registration_confirmation_shown = false;
+
+ std::unique_ptr<GameUI> m_game_ui;
+ GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
+ CheatMenu *m_cheat_menu = nullptr;
+ MapDrawControl *draw_control = nullptr;
+ Camera *camera = nullptr;
+ Clouds *clouds = nullptr; // Free using ->Drop()
+ Sky *sky = nullptr; // Free using ->Drop()
+ Hud *hud = nullptr;
+ Minimap *mapper = nullptr;
+
+ // Map server hud ids to client hud ids
+ std::unordered_map<u32, u32> m_hud_server_to_client;
+
+ GameRunData runData;
+ Flags m_flags;
+
+ /* 'cache'
+ This class does take ownership/responsibily for cleaning up etc of any of
+ these items (e.g. device)
+ */
+ IrrlichtDevice *device;
+ RenderingEngine *m_rendering_engine;
+ video::IVideoDriver *driver;
+ scene::ISceneManager *smgr;
+ bool *kill;
+ std::string *error_message;
+ bool *reconnect_requested;
+ scene::ISceneNode *skybox;
+ PausedNodesList paused_animated_nodes;
+
+ bool simple_singleplayer_mode;
+ /* End 'cache' */
+
+ /* Pre-calculated values
+ */
+ int crack_animation_length;
+
+ IntervalLimiter profiler_interval;
+
+ /*
+ * TODO: Local caching of settings is not optimal and should at some stage
+ * be updated to use a global settings object for getting thse values
+ * (as opposed to the this local caching). This can be addressed in
+ * a later release.
+ */
+ bool m_cache_doubletap_jump;
+ bool m_cache_enable_clouds;
+ bool m_cache_enable_joysticks;
+ bool m_cache_enable_particles;
+ bool m_cache_enable_fog;
+ bool m_cache_enable_noclip;
+ bool m_cache_enable_free_move;
+ f32 m_cache_mouse_sensitivity;
+ f32 m_cache_joystick_frustum_sensitivity;
+ f32 m_repeat_place_time;
+ f32 m_cache_cam_smoothing;
+ f32 m_cache_fog_start;
+
+ bool m_invert_mouse = false;
+ bool m_first_loop_after_window_activation = false;
+ bool m_camera_offset_changed = false;
+
+ bool m_does_lost_focus_pause_game = false;
+
- #ifdef __ANDROID__
++ CameraOrientation cam_view_target = {}; // added by dragonfireclient
++ CameraOrientation cam_view = {}; // added by dragonfireclient
+
++#if IRRLICHT_VERSION_MT_REVISION < 5
+ int m_reset_HW_buffer_counter = 0;
++#endif
++
++#ifdef HAVE_TOUCHSCREENGUI
+ bool m_cache_hold_aux1;
++#endif
++#ifdef __ANDROID__
+ bool m_android_chat_open;
+#endif
+};
+extern Game *g_game;
void the_game(bool *kill,
InputHandler *input,
}
void GameUI::init()
{
+ m_guitext_coords = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0), false,
+ false, guiroot);
+
// First line of debug text
m_guitext = gui::StaticText::add(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(),
core::rect<s32>(0, 0, 0, 0), false, false, guiroot);
const CameraOrientation &cam, const PointedThing &pointed_old,
const GUIChatConsole *chat_console, float dtime)
{
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ v3f player_position = player->getPosition();
+
v2u32 screensize = RenderingEngine::getWindowSize();
+ bool show_coords = g_settings->getBool("coords");
+
+ if (show_coords) {
+ std::ostringstream os(std::ios_base::binary);
+ os << std::setprecision(1) << std::fixed
+ << (player_position.X / BS)
+ << ", " << (player_position.Y / BS)
+ << ", " << (player_position.Z / BS);
+ setStaticText(m_guitext_coords, utf8_to_wide(os.str()).c_str());
+ m_guitext_coords->setRelativePosition(core::rect<s32>(5, screensize.Y - 5 - g_fontengine->getTextHeight(), screensize.X, screensize.Y));
+ }
+
+ m_guitext_coords->setVisible(show_coords);
+
// Minimal debug text must only contain info that can't give a gameplay advantage
if (m_flags.show_minimal_debug) {
- static float drawtime_avg = 0;
- drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05;
- u16 fps = 1.0 / stats.dtime_jitter.avg;
+ const u16 fps = 1.0 / stats.dtime_jitter.avg;
+ m_drawtime_avg *= 0.95f;
+ m_drawtime_avg += 0.05f * (stats.drawtime / 1000);
std::ostringstream os(std::ios_base::binary);
os << std::fixed
<< PROJECT_NAME_C " " << g_version_hash
<< " | FPS: " << fps
<< std::setprecision(0)
- << " | drawtime: " << drawtime_avg << "ms"
+ << " | drawtime: " << m_drawtime_avg << "ms"
<< std::setprecision(1)
<< " | dtime jitter: "
<< (stats.dtime_jitter.max_fraction * 100.0) << "%"
const NodeDefManager *nodedef = client->getNodeDefManager();
MapNode n = map.getNode(pointed_old.node_undersurface);
- if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") {
- os << ", pointed: " << nodedef->get(n).name
- << ", param2: " << (u64) n.getParam2();
+ if (n.getContent() != CONTENT_IGNORE) {
+ if (nodedef->get(n).name == "unknown") {
+ os << ", pointed: <unknown node>";
+ } else {
+ os << ", pointed: " << nodedef->get(n).name;
+ }
+ os << ", param2: " << (u64) n.getParam2();
}
}
m_guitext_status->enableOverrideColor(true);
}
- // Hide chat when console is visible
- m_guitext_chat->setVisible(isChatVisible() && !chat_console->isVisible());
+ m_guitext_chat->setVisible(isChatVisible());
}
void GameUI::initFlags()
{
m_flags = GameUI::Flags();
m_flags.show_minimal_debug = g_settings->getBool("show_debug");
- m_flags.show_basic_debug = false;
}
void GameUI::showMinimap(bool show)
void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
{
+ setStaticText(m_guitext_chat, chat_text);
+ m_recent_chat_count = recent_chat_count;
+ }
+
+ void GameUI::updateChatSize()
+ {
// Update gui element size and position
- s32 chat_y = 5;
+
+ const v2u32 &window_size = RenderingEngine::getWindowSize();
+
+ s32 chat_y = window_size.Y - 150 - m_guitext_chat->getTextHeight();
if (m_flags.show_minimal_debug)
chat_y += g_fontengine->getLineHeight();
if (m_flags.show_basic_debug)
chat_y += g_fontengine->getLineHeight();
- core::rect<s32> chat_size(10, chat_y,
- window_size.X - 20, 0);
- const v2u32 &window_size = RenderingEngine::getWindowSize();
-
+ core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0);
chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y,
- m_guitext_chat->getTextHeight() + chat_y);
+ m_guitext_chat->getTextHeight() + chat_y);
- m_guitext_chat->setRelativePosition(chat_size);
- setStaticText(m_guitext_chat, chat_text);
+ if (chat_size == m_current_chat_size)
+ return;
+ m_current_chat_size = chat_size;
- m_recent_chat_count = recent_chat_count;
+ m_guitext_chat->setRelativePosition(chat_size);
}
void GameUI::updateProfiler()
showTranslatedStatusText("Chat hidden");
}
+void GameUI::toggleCheatMenu()
+{
+ m_flags.show_cheat_menu = !m_flags.show_cheat_menu;
+ if (m_flags.show_cheat_menu)
+ showTranslatedStatusText("Cheat Menu shown");
+ else
+ showTranslatedStatusText("Cheat Menu hidden");
+}
+
void GameUI::toggleHud()
{
m_flags.show_hud = !m_flags.show_hud;
bool show_minimal_debug = false;
bool show_basic_debug = false;
bool show_profiler_graph = false;
+ bool show_cheat_menu = true;
};
void init();
void showTranslatedStatusText(const char *str);
inline void clearStatusText() { m_statustext.clear(); }
- const bool isChatVisible()
+ bool isChatVisible()
{
return m_flags.show_chat && m_recent_chat_count != 0 && m_profiler_current_page == 0;
}
void setChatText(const EnrichedString &chat_text, u32 recent_chat_count);
+ void updateChatSize();
void updateProfiler();
void toggleChat();
+ void toggleCheatMenu();
void toggleHud();
void toggleProfiler();
private:
Flags m_flags;
+ float m_drawtime_avg = 0;
+
gui::IGUIStaticText *m_guitext = nullptr; // First line of debug text
gui::IGUIStaticText *m_guitext2 = nullptr; // Second line of debug text
-
+ gui::IGUIStaticText *m_guitext_coords = nullptr;
+
gui::IGUIStaticText *m_guitext_info = nullptr; // At the middle of the screen
std::wstring m_infotext;
gui::IGUIStaticText *m_guitext_chat = nullptr; // Chat text
u32 m_recent_chat_count = 0;
+ core::rect<s32> m_current_chat_size{0, 0, 0, 0};
gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text
u8 m_profiler_current_page = 0;
*/
#include "client/hud.h"
+ #include <string>
+ #include <iostream>
#include <cmath>
#include "settings.h"
#include "util/numeric.h"
bool selected)
{
if (selected) {
- /* draw hihlighting around selected item */
+ /* draw highlighting around selected item */
if (use_hotbar_selected_image) {
core::rect<s32> imgrect2 = rect;
imgrect2.UpperLeftCorner.X -= (m_padding*2);
}
//NOTE: selectitem = 0 -> no selected; selectitem 1-based
+ // mainlist can be NULL, but draw the frame anyway.
void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
{
// Draw items
core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
- for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) {
+ const s32 list_size = mainlist ? mainlist->getSize() : 0;
+ for (s32 i = inv_offset; i < itemcount && i < list_size; i++) {
s32 fullimglen = m_hotbar_imagesize + m_padding * 2;
v2s32 steppos;
std::wstring text = unescape_translate(utf8_to_wide(e->text));
core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
- v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
- (e->align.Y - 1.0) * (textsize.Height / 2));
+ v2s32 offset(0, (e->align.Y - 1.0) * (textsize.Height / 2));
core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
- text_height * e->scale.Y * m_scale_factor);
+ text_height * e->scale.Y * m_scale_factor);
v2s32 offs(e->offset.X * m_scale_factor,
- e->offset.Y * m_scale_factor);
-
+ e->offset.Y * m_scale_factor);
+ std::wstringstream wss(text);
+ std::wstring line;
+ while (std::getline(wss, line, L'\n'))
{
- textfont->draw(text.c_str(), size + pos + offset + offs, color);
+ core::dimension2d<u32> linesize = textfont->getDimension(line.c_str());
+ v2s32 line_offset((e->align.X - 1.0) * (linesize.Width / 2), 0);
+ textfont->draw(line.c_str(), size + pos + offset + offs + line_offset, color);
+ offset.Y += linesize.Height;
}
break; }
case HUD_ELEM_STATBAR: {
break; }
case HUD_ELEM_INVENTORY: {
InventoryList *inv = inventory->getList(e->text);
+ if (!inv)
+ warningstream << "HUD: Unknown inventory list. name=" << e->text << std::endl;
drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
inv, e->item, e->dir);
break; }
// Rectangles for 1/2 the "off state" texture
core::rect<s32> srchalfrect2, dsthalfrect2;
- if (count % 2 == 1) {
+ if (count % 2 == 1 || maxcount % 2 == 1) {
// Need to draw halves: Calculate rectangles
srchalfrect = calculate_clipping_rect(srcd, steppos);
dsthalfrect = calculate_clipping_rect(dstd, steppos);
}
}
- if (stat_texture_bg && maxcount > count / 2) {
+ if (stat_texture_bg && maxcount > count) {
// Draw "off state" textures
s32 start_offset;
if (count % 2 == 1)
if (maxcount % 2 == 1) {
draw2DImageFilterScaled(driver, stat_texture_bg,
- dsthalfrect + p, srchalfrect,
- NULL, colors, true);
+ dsthalfrect + p, srchalfrect, NULL, colors, true);
}
}
}
bool draw_overlay = false;
+ bool has_mesh = false;
+ ItemMesh *imesh;
+
+ core::rect<s32> viewrect = rect;
+ if (clip != nullptr)
+ viewrect.clipAgainst(*clip);
+
// Render as mesh if animated or no inventory image
if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) {
- ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
- if (!imesh || !imesh->mesh)
- return;
+ imesh = client->idef()->getWieldMesh(def.name, client);
+ has_mesh = imesh && imesh->mesh;
+ }
+ if (has_mesh) {
scene::IMesh *mesh = imesh->mesh;
driver->clearBuffers(video::ECBF_DEPTH);
s32 delta = 0;
core::rect<s32> oldViewPort = driver->getViewPort();
core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
- core::rect<s32> viewrect = rect;
- if (clip)
- viewrect.clipAgainst(*clip);
core::matrix4 ProjMatrix;
ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
draw_overlay = def.type == ITEM_NODE && def.inventory_image.empty();
} else { // Otherwise just draw as 2D
video::ITexture *texture = client->idef()->getInventoryTexture(def.name, client);
- if (!texture)
- return;
- video::SColor color =
- client->idef()->getItemstackColor(item, client);
+ video::SColor color;
+ if (texture) {
+ color = client->idef()->getItemstackColor(item, client);
+ } else {
+ color = video::SColor(255, 255, 255, 255);
+ ITextureSource *tsrc = client->getTextureSource();
+ texture = tsrc->getTexture("no_texture.png");
+ if (!texture)
+ return;
+ }
+
const video::SColor colors[] = { color, color, color, color };
draw2DImageFilterScaled(driver, texture, rect,
driver->draw2DRectangle(color, progressrect2, clip);
}
- if (font != NULL && item.count >= 2) {
+ const std::string &count_text = item.metadata.getString("count_meta");
+ if (font != nullptr && (item.count >= 2 || !count_text.empty())) {
// Get the item count as a string
- std::string text = itos(item.count);
- v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
+ std::string text = count_text.empty() ? itos(item.count) : count_text;
+ v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str());
v2s32 sdim(dim.X, dim.Y);
core::rect<s32> rect2(
- /*rect.UpperLeftCorner,
- core::dimension2d<u32>(rect.getWidth(), 15)*/
rect.LowerRightCorner - sdim,
- sdim
+ rect.LowerRightCorner
);
- video::SColor bgcolor(128, 0, 0, 0);
- driver->draw2DRectangle(bgcolor, rect2, clip);
+ // get the count alignment
+ s32 count_alignment = stoi(item.metadata.getString("count_alignment"));
+ if (count_alignment != 0) {
+ s32 a_x = count_alignment & 3;
+ s32 a_y = (count_alignment >> 2) & 3;
+
+ s32 x1, x2, y1, y2;
+ switch (a_x) {
+ case 1: // left
+ x1 = rect.UpperLeftCorner.X;
+ x2 = x1 + sdim.X;
+ break;
+ case 2: // middle
+ x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2;
+ x2 = x1 + sdim.X;
+ break;
+ case 3: // right
+ x2 = rect.LowerRightCorner.X;
+ x1 = x2 - sdim.X;
+ break;
+ default: // 0 = default
+ x1 = rect2.UpperLeftCorner.X;
+ x2 = rect2.LowerRightCorner.X;
+ break;
+ }
+
+ switch (a_y) {
+ case 1: // up
+ y1 = rect.UpperLeftCorner.Y;
+ y2 = y1 + sdim.Y;
+ break;
+ case 2: // middle
+ y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2;
+ y2 = y1 + sdim.Y;
+ break;
+ case 3: // down
+ y2 = rect.LowerRightCorner.Y;
+ y1 = y2 - sdim.Y;
+ break;
+ default: // 0 = default
+ y1 = rect2.UpperLeftCorner.Y;
+ y2 = rect2.LowerRightCorner.Y;
+ break;
+ }
+
+ rect2 = core::rect<s32>(x1, y1, x2, y2);
+ }
video::SColor color(255, 255, 255, 255);
- font->draw(text.c_str(), rect2, color, false, false, clip);
+ font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect);
}
}
#endif
class InputHandler;
+class TouchScreenGUI;
/****************************************************************************
Fast key cache for main game loop
// in the subsequent iteration of Game::processPlayerInteraction
bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; }
- void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); }
- void dontListenForKeys() { keysListenedFor.clear(); }
+ void listenForKey(const KeyPress &keyCode)
+ {
+ keysListenedFor.set(keyCode);
+ }
+ void dontListenForKeys()
+ {
+ keysListenedFor.clear();
+ }
s32 getMouseWheel()
{
#endif
}
- s32 mouse_wheel = 0;
-
JoystickController *joystick = nullptr;
#ifdef HAVE_TOUCHSCREENGUI
TouchScreenGUI *m_touchscreengui;
#endif
-private:
+ s32 mouse_wheel = 0;
+
// The current state of keys
KeyList keyIsDown;
}
virtual bool isKeyDown(GameKeyType k) = 0;
+ virtual void setKeypress(const KeyPress &keyCode) = 0;
+ virtual void unsetKeypress(const KeyPress &keyCode) = 0;
virtual bool wasKeyDown(GameKeyType k) = 0;
virtual bool wasKeyPressed(GameKeyType k) = 0;
virtual bool wasKeyReleased(GameKeyType k) = 0;
{
m_receiver->joystick = &joystick;
}
+
+ virtual ~RealInputHandler()
+ {
+ m_receiver->joystick = nullptr;
+ }
+
virtual bool isKeyDown(GameKeyType k)
{
return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k);
}
+ virtual void setKeypress(const KeyPress &keyCode)
+ {
+ m_receiver->keyIsDown.set(keyCode);
+ m_receiver->keyWasDown.set(keyCode);
+ }
+ virtual void unsetKeypress(const KeyPress &keyCode)
+ {
+ m_receiver->keyIsDown.unset(keyCode);
+ }
virtual bool wasKeyDown(GameKeyType k)
{
return m_receiver->WasKeyDown(keycache.key[k]) || joystick.wasKeyDown(k);
{
return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k);
}
+
virtual float getMovementSpeed()
{
bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]),
}
return joystick.getMovementSpeed();
}
+
virtual float getMovementDirection()
{
float x = 0, z = 0;
else
return joystick.getMovementDirection();
}
+
virtual bool cancelPressed()
{
return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey);
}
+
virtual void clearWasKeyPressed()
{
m_receiver->clearWasKeyPressed();
{
m_receiver->clearWasKeyReleased();
}
+
virtual void listenForKey(const KeyPress &keyCode)
{
m_receiver->listenForKey(keyCode);
}
- virtual void dontListenForKeys() { m_receiver->dontListenForKeys(); }
+ virtual void dontListenForKeys()
+ {
+ m_receiver->dontListenForKeys();
+ }
+
virtual v2s32 getMousePos()
{
- if (RenderingEngine::get_raw_device()->getCursorControl()) {
- return RenderingEngine::get_raw_device()
- ->getCursorControl()
- ->getPosition();
+ auto control = RenderingEngine::get_raw_device()->getCursorControl();
+ if (control) {
+ return control->getPosition();
}
return m_mousepos;
virtual void setMousePos(s32 x, s32 y)
{
- if (RenderingEngine::get_raw_device()->getCursorControl()) {
- RenderingEngine::get_raw_device()
- ->getCursorControl()
- ->setPosition(x, y);
+ auto control = RenderingEngine::get_raw_device()->getCursorControl();
+ if (control) {
+ control->setPosition(x, y);
} else {
m_mousepos = v2s32(x, y);
}
}
- virtual s32 getMouseWheel() { return m_receiver->getMouseWheel(); }
+ virtual s32 getMouseWheel()
+ {
+ return m_receiver->getMouseWheel();
+ }
void clear()
{
m_receiver->clearInput();
}
- private:
+ private:
MyEventReceiver *m_receiver = nullptr;
v2s32 m_mousepos;
};
}
virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; }
+ virtual void setKeypress(const KeyPress &keyCode)
+ {
+ keydown.set(keyCode);
+ }
+ virtual void unsetKeypress(const KeyPress &keyCode)
+ {
+ keydown.unset(keyCode);
+ }
virtual bool wasKeyDown(GameKeyType k) { return false; }
virtual bool wasKeyPressed(GameKeyType k) { return false; }
virtual bool wasKeyReleased(GameKeyType k) { return false; }
#include "map.h"
#include "client.h"
#include "content_cao.h"
+#include "util/pointedthing.h"
+#include "client/game.h"
/*
LocalPlayer
new_sneak_node_exists = false;
} else {
node = map->getNode(current_node, &is_valid_position);
- if (!is_valid_position || !nodemgr->get(node).walkable)
+ if (!is_valid_position || nodemgr->get(node).walkable)
new_sneak_node_exists = false;
}
// The node to be sneaked on has to be walkable
node = map->getNode(p, &is_valid_position);
- if (!is_valid_position || !nodemgr->get(node).walkable)
+ if (!is_valid_position || ! nodemgr->get(node).walkable)
continue;
// And the node(s) above have to be nonwalkable
bool ok = true;
} else {
// legacy behaviour: check just one node
node = map->getNode(p + v3s16(0, 1, 0), &is_valid_position);
- ok = is_valid_position && !nodemgr->get(node).walkable;
+ ok = is_valid_position && ! nodemgr->get(node).walkable;
}
if (!ok)
continue;
node = map->getNode(m_sneak_node + v3s16(0, 3, 0),
&is_valid_position);
m_sneak_ladder_detected = is_valid_position &&
- !nodemgr->get(node).walkable;
+ ! nodemgr->get(node).walkable;
}
}
return true;
void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
std::vector<CollisionInfo> *collision_info)
{
+ if (m_cao && m_cao->m_waiting_for_reattach > 0)
+ m_cao->m_waiting_for_reattach -= dtime;
+
// Node at feet position, update each ClientEnvironment::step()
if (!collision_info || collision_info->empty())
m_standing_node = floatToInt(m_position, BS);
PlayerSettings &player_settings = getPlayerSettings();
// Skip collision detection if noclip mode is used
- bool fly_allowed = m_client->checkLocalPrivilege("fly");
- bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip;
- bool free_move = player_settings.free_move && fly_allowed;
+ bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam");
+ bool noclip = (m_client->checkLocalPrivilege("noclip") && player_settings.noclip) || g_settings->getBool("freecam");
+ bool free_move = (player_settings.free_move && fly_allowed) || g_settings->getBool("freecam");
if (noclip && free_move) {
position += m_speed * dtime;
pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
pp = floatToInt(position + v3f(0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
+ in_liquid_stable = nodemgr->get(node.getContent()).liquid_move_physics;
} else {
in_liquid_stable = false;
}
nodemgr->get(node2.getContent()).climbable) && !free_move;
}
+ if (!is_climbing && !free_move && g_settings->getBool("spider")) {
+ v3s16 spider_positions[4] = {
+ floatToInt(position + v3f(+1.0f, +0.0f, 0.0f) * BS, BS),
+ floatToInt(position + v3f(-1.0f, +0.0f, 0.0f) * BS, BS),
+ floatToInt(position + v3f( 0.0f, +0.0f, +1.0f) * BS, BS),
+ floatToInt(position + v3f( 0.0f, +0.0f, -1.0f) * BS, BS),
+ };
+
+ for (v3s16 sp : spider_positions) {
+ bool is_valid;
+ MapNode node = map->getNode(sp, &is_valid);
+
+ if (is_valid && nodemgr->get(node.getContent()).walkable) {
+ is_climbing = true;
+ break;
+ }
+ }
+ }
+
/*
Collision uncertainty radius
Make it a bit larger than the maximum distance of movement
collisionMoveResult result = collisionMoveSimple(env, m_client,
pos_max_d, m_collisionbox, player_stepheight, dtime,
- &position, &m_speed, accel_f);
+ &position, &m_speed, accel_f, NULL, true, true);
bool could_sneak = control.sneak && !free_move && !in_liquid &&
!is_climbing && physics_override_sneak;
Player is allowed to jump when this is true.
*/
bool touching_ground_was = touching_ground;
- touching_ground = result.touching_ground;
+ touching_ground = result.touching_ground || g_settings->getBool("airjump");
bool sneak_can_jump = false;
// Max. distance (X, Z) over border for sneaking determined by collision box
bool fly_allowed = m_client->checkLocalPrivilege("fly");
bool fast_allowed = m_client->checkLocalPrivilege("fast");
- bool free_move = fly_allowed && player_settings.free_move;
- bool fast_move = fast_allowed && player_settings.fast_move;
+ bool free_move = (fly_allowed && player_settings.free_move) || g_settings->getBool("freecam");
+ bool fast_move = (fast_allowed && player_settings.fast_move) || g_settings->getBool("freecam");
bool pitch_move = (free_move || in_liquid) && player_settings.pitch_move;
// When aux1_descends is enabled the fast key is used to go down, so fast isn't possible
bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends;
else
speedV.Y = movement_speed_walk;
}
- } else if (m_can_jump) {
+ } else if (m_can_jump || g_settings->getBool("jetpack")) {
/*
NOTE: The d value in move() affects jump height by
raising the height at which the jump speed is kept
at its starting value
*/
v3f speedJ = getSpeed();
- if (speedJ.Y >= -0.5f * BS) {
+ if (speedJ.Y >= -0.5f * BS || g_settings->getBool("jetpack")) {
speedJ.Y = movement_speed_jump * physics_override_jump;
setSpeed(speedJ);
m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP));
if (superspeed || (is_climbing && fast_climb) ||
((in_liquid || in_liquid_stable) && fast_climb))
speedH = speedH.normalize() * movement_speed_fast;
- else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable)
+ else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable && !g_settings->getBool("no_slow"))
speedH = speedH.normalize() * movement_speed_crouch;
else
speedH = speedH.normalize() * movement_speed_walk;
v3s16 LocalPlayer::getFootstepNodePos()
{
+ v3f feet_pos = getPosition() + v3f(0.0f, m_collisionbox.MinEdge.Y, 0.0f);
+
// Emit swimming sound if the player is in liquid
if (in_liquid_stable)
- return floatToInt(getPosition(), BS);
+ return floatToInt(feet_pos, BS);
// BS * 0.05 below the player's feet ensures a 1/16th height
// nodebox is detected instead of the node below it.
if (touching_ground)
- return floatToInt(getPosition() - v3f(0.0f, BS * 0.05f, 0.0f), BS);
+ return floatToInt(feet_pos - v3f(0.0f, BS * 0.05f, 0.0f), BS);
// A larger distance below is necessary for a footstep sound
// when landing after a jump or fall. BS * 0.5 ensures water
// sounds when swimming in 1 node deep water.
- return floatToInt(getPosition() - v3f(0.0f, BS * 0.5f, 0.0f), BS);
+ return floatToInt(feet_pos - v3f(0.0f, BS * 0.5f, 0.0f), BS);
}
v3s16 LocalPlayer::getLightPosition() const
return floatToInt(m_position + v3f(0.0f, BS * 1.5f, 0.0f), BS);
}
+v3f LocalPlayer::getSendSpeed()
+{
+ v3f speed = getLegitSpeed();
+
+ if (m_client->modsLoaded())
+ speed = m_client->getScript()->get_send_speed(speed);
+
+ return speed;
+}
+
v3f LocalPlayer::getEyeOffset() const
{
float eye_height = camera_barely_in_ceiling ? m_eye_height - 0.125f : m_eye_height;
ClientActiveObject *LocalPlayer::getParent() const
{
- return m_cao ? m_cao->getParent() : nullptr;
+ return (m_cao && ! g_settings->getBool("entity_speed")) ? m_cao->getParent() : nullptr;
}
bool LocalPlayer::isDead() const
return !getCAO()->isImmortal() && hp == 0;
}
+void LocalPlayer::tryReattach(int id)
+{
+ PointedThing pointed(id, v3f(0, 0, 0), v3s16(0, 0, 0), 0);
+ m_client->interact(INTERACT_PLACE, pointed);
+ m_cao->m_waiting_for_reattach = 10;
+}
+
+bool LocalPlayer::isWaitingForReattach() const
+{
+ return g_settings->getBool("entity_speed") && m_cao && ! m_cao->getParent() && m_cao->m_waiting_for_reattach > 0;
+}
+
// 3D acceleration
void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H,
const f32 max_increase_V, const bool use_pitch)
PlayerSettings &player_settings = getPlayerSettings();
// Skip collision detection if noclip mode is used
- bool fly_allowed = m_client->checkLocalPrivilege("fly");
- bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip;
- bool free_move = noclip && fly_allowed && player_settings.free_move;
+ bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam");
+ bool noclip = (m_client->checkLocalPrivilege("noclip") && player_settings.noclip) || g_settings->getBool("freecam");
+ bool free_move = (noclip && fly_allowed && player_settings.free_move) || g_settings->getBool("freecam");
if (free_move) {
position += m_speed * dtime;
setPosition(position);
pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
- in_liquid = nodemgr->get(node.getContent()).isLiquid();
- liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+ const ContentFeatures &cf = nodemgr->get(node.getContent());
+ in_liquid = cf.liquid_move_physics;
+ move_resistance = cf.move_resistance;
} else {
in_liquid = false;
}
pp = floatToInt(position + v3f(0.0f), BS);
node = map->getNode(pp, &is_valid_position);
if (is_valid_position)
- in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
+ in_liquid_stable = nodemgr->get(node.getContent()).liquid_move_physics;
else
in_liquid_stable = false;
collisionMoveResult result = collisionMoveSimple(env, m_client,
pos_max_d, m_collisionbox, player_stepheight, dtime,
- &position, &m_speed, accel_f);
+ &position, &m_speed, accel_f, NULL, true, true);
// Positition was slightly changed; update standing node pos
if (touching_ground)
Map *map = &env->getMap();
const ContentFeatures &f = nodemgr->get(map->getNode(getStandingNodePos()));
int slippery = 0;
- if (f.walkable)
+ if (f.walkable && ! g_settings->getBool("antislip"))
slippery = itemgroup_get(f.groups, "slippery");
if (slippery >= 1) {
if (m_autojump)
return;
- bool control_forward = keyPressed & (1 << 0);
-
bool could_autojump =
- m_can_jump && !control.jump && !control.sneak && control_forward;
+ m_can_jump && !control.jump && !control.sneak && control.isMoving();
if (!could_autojump)
return;
// try at peak of jump, zero step height
collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d,
- m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f));
+ m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f), NULL, true, true);
// see if we can get a little bit farther horizontally if we had
// jumped
m_autojump_time = 0.1f;
}
}
+
#include "environment.h"
#include "constants.h"
#include "settings.h"
+ #include "lighting.h"
#include <list>
class Client;
class ClientActiveObject;
class ClientEnvironment;
class IGameDef;
+struct ContentFeatures;
struct collisionMoveResult;
enum LocalPlayerAnimations
bool in_liquid = false;
// This is more stable and defines the maximum speed of the player
bool in_liquid_stable = false;
- // Gets the viscosity of water to calculate friction
- u8 liquid_viscosity = 0;
+ // Slows down the player when moving through
+ u8 move_resistance = 0;
bool is_climbing = false;
bool swimming_vertical = false;
bool swimming_pitch = false;
v3f last_speed;
float last_pitch = 0.0f;
float last_yaw = 0.0f;
- unsigned int last_keyPressed = 0;
+ u32 last_keyPressed = 0;
u8 last_camera_fov = 0;
u8 last_wanted_range = 0;
inline void setPosition(const v3f &position)
{
m_position = position;
+ if (! m_freecam)
+ m_legit_position = position;
m_sneak_node_exists = false;
}
v3f getPosition() const { return m_position; }
+ v3f getLegitPosition() const { return m_legit_position; }
+
+ v3f getLegitSpeed() const { return m_freecam ? m_legit_speed : m_speed; }
+
+ v3f getSendSpeed();
+
+ inline void setLegitPosition(const v3f &position)
+ {
+ if (m_freecam)
+ m_legit_position = position;
+ else
+ setPosition(position);
+ }
+
+ inline void freecamEnable()
+ {
+ m_freecam = true;
+ }
+
+ inline void freecamDisable()
+ {
+ m_freecam = false;
+ setPosition(m_legit_position);
+ setSpeed(m_legit_speed);
+ }
+
// Non-transformed eye offset getters
// For accurate positions, use the Camera functions
v3f getEyePosition() const { return m_position + getEyeOffset(); }
added_velocity += vel;
}
+ inline Lighting& getLighting() { return m_lighting; }
+
+ void tryReattach(int id);
+
+ bool isWaitingForReattach() const;
+
+ bool canWalkOn(const ContentFeatures &f);
+
private:
void accelerate(const v3f &target_speed, const f32 max_increase_H,
const f32 max_increase_V, const bool use_pitch);
const v3f &position_before_move, const v3f &speed_before_move,
f32 pos_max_d);
+ bool m_freecam = false;
v3f m_position;
+ v3f m_legit_position;
+ v3f m_legit_speed;
v3s16 m_standing_node;
v3s16 m_sneak_node = v3s16(32767, 32767, 32767);
GenericCAO *m_cao = nullptr;
Client *m_client;
+ Lighting m_lighting;
};
#include "client/meshgen/collector.h"
#include "client/renderingengine.h"
#include <array>
+ #include <algorithm>
/*
MeshMakeData
void MeshMakeData::setSmoothLighting(bool smooth_lighting)
{
- m_smooth_lighting = smooth_lighting;
+ m_smooth_lighting = smooth_lighting && ! g_settings->getBool("fullbright");
}
/*
u8 light = n.getLight(bank, ndef);
if (light > 0)
light = rangelim(light + increment, 0, LIGHT_SUN);
+ if(g_settings->getBool("fullbright"))
+ return 255;
return decode_light(light);
}
ndef->get(n2).light_source);
if(light_source > light)
light = light_source;
-
+ if(g_settings->getBool("fullbright"))
+ return 255;
return decode_light(light);
}
u8 c1 = f1.solidness;
u8 c2 = f2.solidness;
+
if (c1 == c2)
return 0;
else if (c2 == 0)
c2 = f2.visual_solidness;
+
if (c1 == c2) {
*equivalent = true;
// If same solidness, liquid takes precense
tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
}
+std::set<content_t> splitToContentT(std::string str, const NodeDefManager *ndef)
+{
+ str += "\n";
+ std::set<content_t> dat;
+ std::string buf;
+ for (char c : str) {
+ if (c == ',' || c == '\n') {
+ if (! buf.empty()) {
+ dat.insert(ndef->getId(buf));
+ }
+ buf.clear();
+ } else if (c != ' ') {
+ buf += c;
+ }
+ }
+ return dat;
+}
+
static void getTileInfo(
// Input:
MeshMakeData *data,
v3s16 &face_dir_corrected,
u16 *lights,
u8 &waving,
- TileSpec &tile
- )
+ TileSpec &tile,
+ // lol more Input
+ bool xray,
+ std::set<content_t> xraySet)
{
VoxelManipulator &vmanip = data->m_vmanip;
const NodeDefManager *ndef = data->m_client->ndef();
const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
+ content_t c0 = n0.getContent();
+ if (xray && xraySet.find(c0) != xraySet.end())
+ c0 = CONTENT_AIR;
// Don't even try to get n1 if n0 is already CONTENT_IGNORE
- if (n0.getContent() == CONTENT_IGNORE) {
+ if (c0 == CONTENT_IGNORE) {
makes_face = false;
return;
}
const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
- if (n1.getContent() == CONTENT_IGNORE) {
+ content_t c1 = n1.getContent();
+ if (xray && xraySet.find(c1) != xraySet.end())
+ c1 = CONTENT_AIR;
+
+ if (c1 == CONTENT_IGNORE) {
makes_face = false;
return;
}
// This is hackish
bool equivalent = false;
- u8 mf = face_contents(n0.getContent(), n1.getContent(),
+ u8 mf = face_contents(c0, c1,
&equivalent, ndef);
if (mf == 0) {
v3s16 translate_dir,
const v3f &&translate_dir_f,
const v3s16 &&face_dir,
- std::vector<FastFace> &dest)
+ std::vector<FastFace> &dest,
+ bool xray,
+ std::set<content_t> xraySet)
{
static thread_local const bool waving_liquids =
g_settings->getBool("enable_shaders") &&
// Get info of first tile
getTileInfo(data, p, face_dir,
makes_face, p_corrected, face_dir_corrected,
- lights, waving, tile);
+ lights, waving, tile, xray, xraySet);
// Unroll this variable which has a significant build cost
TileSpec next_tile;
next_makes_face, next_p_corrected,
next_face_dir_corrected, next_lights,
waving,
- next_tile);
+ next_tile,
+ xray,
+ xraySet);
if (!force_not_tiling
&& next_makes_face == makes_face
}
static void updateAllFastFaceRows(MeshMakeData *data,
- std::vector<FastFace> &dest)
+ std::vector<FastFace> &dest, bool xray, std::set<content_t> xraySet)
{
/*
Go through every y,z and get top(y+) faces in rows of x+
v3s16(1, 0, 0), //dir
v3f (1, 0, 0),
v3s16(0, 1, 0), //face dir
- dest);
+ dest,
+ xray,
+ xraySet);
/*
Go through every x,y and get right(x+) faces in rows of z+
v3s16(0, 0, 1), //dir
v3f (0, 0, 1),
v3s16(1, 0, 0), //face dir
- dest);
+ dest,
+ xray,
+ xraySet);
/*
Go through every y,z and get back(z+) faces in rows of x+
v3s16(1, 0, 0), //dir
v3f (1, 0, 0),
v3s16(0, 0, 1), //face dir
- dest);
+ dest,
+ xray,
+ xraySet);
}
static void applyTileColor(PreMeshBuffer &pmb)
}
}
+ /*
+ MapBlockBspTree
+ */
+
+ void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles)
+ {
+ this->triangles = triangles;
+
+ nodes.clear();
+
+ // assert that triangle index can fit into s32
+ assert(triangles->size() <= 0x7FFFFFFFL);
+ std::vector<s32> indexes;
+ indexes.reserve(triangles->size());
+ for (u32 i = 0; i < triangles->size(); i++)
+ indexes.push_back(i);
+
+ root = buildTree(v3f(1, 0, 0), v3f(85, 85, 85), 40, indexes, 0);
+ }
+
+ /**
+ * @brief Find a candidate plane to split a set of triangles in two
+ *
+ * The candidate plane is represented by one of the triangles from the set.
+ *
+ * @param list Vector of indexes of the triangles in the set
+ * @param triangles Vector of all triangles in the BSP tree
+ * @return Address of the triangle that represents the proposed split plane
+ */
+ static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
+ {
+ // find the center of the cluster.
+ v3f center(0, 0, 0);
+ size_t n = list.size();
+ for (s32 i : list) {
+ center += triangles[i].centroid / n;
+ }
+
+ // find the triangle with the largest area and closest to the center
+ const MeshTriangle *candidate_triangle = &triangles[list[0]];
+ const MeshTriangle *ith_triangle;
+ for (s32 i : list) {
+ ith_triangle = &triangles[i];
+ if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
+ (ith_triangle->areaSQ == candidate_triangle->areaSQ &&
+ ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
+ candidate_triangle = ith_triangle;
+ }
+ }
+ return candidate_triangle;
+ }
+
+ s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
+ {
+ // if the list is empty, don't bother
+ if (list.empty())
+ return -1;
+
+ // if there is only one triangle, or the delta is insanely small, this is a leaf node
+ if (list.size() == 1 || delta < 0.01) {
+ nodes.emplace_back(normal, origin, list, -1, -1);
+ return nodes.size() - 1;
+ }
+
+ std::vector<s32> front_list;
+ std::vector<s32> back_list;
+ std::vector<s32> node_list;
+
+ // split the list
+ for (s32 i : list) {
+ const MeshTriangle &triangle = (*triangles)[i];
+ float factor = normal.dotProduct(triangle.centroid - origin);
+ if (factor == 0)
+ node_list.push_back(i);
+ else if (factor > 0)
+ front_list.push_back(i);
+ else
+ back_list.push_back(i);
+ }
+
+ // define the new split-plane
+ v3f candidate_normal(normal.Z, normal.X, normal.Y);
+ float candidate_delta = delta;
+ if (depth % 3 == 2)
+ candidate_delta /= 2;
+
+ s32 front_index = -1;
+ s32 back_index = -1;
+
+ if (!front_list.empty()) {
+ v3f next_normal = candidate_normal;
+ v3f next_origin = origin + delta * normal;
+ float next_delta = candidate_delta;
+ if (next_delta < 10) {
+ const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
+ next_normal = candidate->getNormal();
+ next_origin = candidate->centroid;
+ }
+ front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
+
+ // if there are no other triangles, don't create a new node
+ if (back_list.empty() && node_list.empty())
+ return front_index;
+ }
+
+ if (!back_list.empty()) {
+ v3f next_normal = candidate_normal;
+ v3f next_origin = origin - delta * normal;
+ float next_delta = candidate_delta;
+ if (next_delta < 10) {
+ const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
+ next_normal = candidate->getNormal();
+ next_origin = candidate->centroid;
+ }
+
+ back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
+
+ // if there are no other triangles, don't create a new node
+ if (front_list.empty() && node_list.empty())
+ return back_index;
+ }
+
+ nodes.emplace_back(normal, origin, node_list, front_index, back_index);
+
+ return nodes.size() - 1;
+ }
+
+ void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
+ {
+ if (node < 0) return; // recursion break;
+
+ const TreeNode &n = nodes[node];
+ float factor = n.normal.dotProduct(viewpoint - n.origin);
+
+ if (factor > 0)
+ traverse(n.back_ref, viewpoint, output);
+ else
+ traverse(n.front_ref, viewpoint, output);
+
+ if (factor != 0)
+ for (s32 i : n.triangle_refs)
+ output.push_back(i);
+
+ if (factor > 0)
+ traverse(n.front_ref, viewpoint, output);
+ else
+ traverse(n.back_ref, viewpoint, output);
+ }
+
+
+
+ /*
+ PartialMeshBuffer
+ */
+
+ void PartialMeshBuffer::beforeDraw() const
+ {
+ // Patch the indexes in the mesh buffer before draw
+
+ m_buffer->Indices.clear();
+ if (!m_vertex_indexes.empty()) {
+ for (auto index : m_vertex_indexes)
+ m_buffer->Indices.push_back(index);
+ }
+ m_buffer->setDirty(scene::EBT_INDEX);
+ }
+
/*
MapBlockMesh
*/
std::vector<FastFace> fastfaces_new;
fastfaces_new.reserve(512);
+ /*
+ X-Ray
+ */
+ bool xray = g_settings->getBool("xray");
+ std::set<content_t> xraySet, nodeESPSet;
+ if (xray)
+ xraySet = splitToContentT(g_settings->get("xray_nodes"), data->m_client->ndef());
+
+ nodeESPSet = splitToContentT(g_settings->get("node_esp_nodes"), data->m_client->ndef());
/*
We are including the faces of the trailing edges of the block.
{
// 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated)
//TimeTaker timer2("updateAllFastFaceRows()");
- updateAllFastFaceRows(data, fastfaces_new);
+ updateAllFastFaceRows(data, fastfaces_new, xray, xraySet);
}
// End of slow part
+ /*
+ NodeESP
+ */
+ {
+ v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
+ for (s16 x = 0; x < MAP_BLOCKSIZE; x++) {
+ for (s16 y = 0; y < MAP_BLOCKSIZE; y++) {
+ for (s16 z = 0; z < MAP_BLOCKSIZE; z++) {
+ v3s16 pos = v3s16(x, y, z) + blockpos_nodes;
+ const MapNode &node = data->m_vmanip.getNodeRefUnsafeCheckFlags(pos);
+ if (nodeESPSet.find(node.getContent()) != nodeESPSet.end())
+ esp_nodes.insert(pos);
+ }
+ }
+ }
+ }
+
/*
Convert FastFaces to MeshCollector
*/
Convert MeshCollector to SMesh
*/
+ const bool desync_animations = g_settings->getBool(
+ "desynchronize_mapblock_texture_animation");
+
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
{
// - Texture animation
if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
// Add to MapBlockMesh in order to animate these tiles
- m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer;
- m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
- if (g_settings->getBool(
- "desynchronize_mapblock_texture_animation")) {
+ auto &info = m_animation_info[{layer, i}];
+ info.tile = p.layer;
+ info.frame = 0;
+ if (desync_animations) {
// Get starting position from noise
- m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] =
+ info.frame_offset =
100000 * (2.0 + noise3d(
data->m_blockpos.X, data->m_blockpos.Y,
data->m_blockpos.Z, 0));
} else {
// Play all synchronized
- m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0;
+ info.frame_offset = 0;
}
// Replace tile texture with the first animation frame
p.layer.texture = (*p.layer.frames)[0].texture;
// Dummy sunlight to handle non-sunlit areas
video::SColorf sunlight;
get_sunlight_color(&sunlight, 0);
- u32 vertex_count = p.vertices.size();
+
+ std::map<u32, video::SColor> colors;
+ const u32 vertex_count = p.vertices.size();
for (u32 j = 0; j < vertex_count; j++) {
video::SColor *vc = &p.vertices[j].Color;
video::SColor copy = *vc;
if (vc->getAlpha() == 0) // No sunlight - no need to animate
final_color_blend(vc, copy, sunlight); // Finalize color
else // Record color to animate
- m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy;
+ colors[j] = copy;
// The sunlight ratio has been stored,
// delete alpha (for the final rendering).
vc->setAlpha(255);
}
+ if (!colors.empty())
+ m_daynight_diffs[{layer, i}] = std::move(colors);
}
// Create material
scene::SMeshBuffer *buf = new scene::SMeshBuffer();
buf->Material = material;
- buf->append(&p.vertices[0], p.vertices.size(),
- &p.indices[0], p.indices.size());
+ switch (p.layer.material_type) {
+ // list of transparent materials taken from tile.h
+ case TILE_MATERIAL_ALPHA:
+ case TILE_MATERIAL_LIQUID_TRANSPARENT:
+ case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
+ {
+ buf->append(&p.vertices[0], p.vertices.size(),
+ &p.indices[0], 0);
+
+ MeshTriangle t;
+ t.buffer = buf;
+ for (u32 i = 0; i < p.indices.size(); i += 3) {
+ t.p1 = p.indices[i];
+ t.p2 = p.indices[i + 1];
+ t.p3 = p.indices[i + 2];
+ t.updateAttributes();
+ m_transparent_triangles.push_back(t);
+ }
+ }
+ break;
+ default:
+ buf->append(&p.vertices[0], p.vertices.size(),
+ &p.indices[0], p.indices.size());
+ break;
+ }
mesh->addMeshBuffer(buf);
buf->drop();
}
}
//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
+ m_bsp_tree.buildTree(&m_transparent_triangles);
// Check if animation is required for this mesh
m_has_animation =
!m_crack_materials.empty() ||
!m_daynight_diffs.empty() ||
- !m_animation_tiles.empty();
+ !m_animation_info.empty();
}
MapBlockMesh::~MapBlockMesh()
{
for (scene::IMesh *m : m_mesh) {
+ #if IRRLICHT_VERSION_MT_REVISION < 5
if (m_enable_vbo) {
for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
scene::IMeshBuffer *buf = m->getMeshBuffer(i);
RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
}
}
+ #endif
m->drop();
}
delete m_minimap_mapblock;
for (auto &crack_material : m_crack_materials) {
scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
getMeshBuffer(crack_material.first.second);
- std::string basename = crack_material.second;
// Create new texture name from original
- std::ostringstream os;
- os << basename << crack;
+ std::string s = crack_material.second + itos(crack);
u32 new_texture_id = 0;
video::ITexture *new_texture =
- m_tsrc->getTextureForMesh(os.str(), &new_texture_id);
+ m_tsrc->getTextureForMesh(s, &new_texture_id);
buf->getMaterial().setTexture(0, new_texture);
- // If the current material is also animated,
- // update animation info
- auto anim_iter = m_animation_tiles.find(crack_material.first);
- if (anim_iter != m_animation_tiles.end()) {
- TileLayer &tile = anim_iter->second;
+ // If the current material is also animated, update animation info
+ auto anim_it = m_animation_info.find(crack_material.first);
+ if (anim_it != m_animation_info.end()) {
+ TileLayer &tile = anim_it->second.tile;
tile.texture = new_texture;
tile.texture_id = new_texture_id;
// force animation update
- m_animation_frames[crack_material.first] = -1;
+ anim_it->second.frame = -1;
}
}
}
// Texture animation
- for (auto &animation_tile : m_animation_tiles) {
- const TileLayer &tile = animation_tile.second;
+ for (auto &it : m_animation_info) {
+ const TileLayer &tile = it.second.tile;
// Figure out current frame
- int frameoffset = m_animation_frame_offsets[animation_tile.first];
- int frame = (int)(time * 1000 / tile.animation_frame_length_ms
- + frameoffset) % tile.animation_frame_count;
+ int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
+ + it.second.frame_offset) % tile.animation_frame_count;
// If frame doesn't change, skip
- if (frame == m_animation_frames[animation_tile.first])
+ if (frameno == it.second.frame)
continue;
- m_animation_frames[animation_tile.first] = frame;
+ it.second.frame = frameno;
- scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]->
- getMeshBuffer(animation_tile.first.second);
+ scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
- const FrameSpec &animation_frame = (*tile.frames)[frame];
- buf->getMaterial().setTexture(0, animation_frame.texture);
+ const FrameSpec &frame = (*tile.frames)[frameno];
+ buf->getMaterial().setTexture(0, frame.texture);
if (m_enable_shaders) {
- if (animation_frame.normal_texture)
- buf->getMaterial().setTexture(1,
- animation_frame.normal_texture);
- buf->getMaterial().setTexture(2, animation_frame.flags_texture);
+ if (frame.normal_texture)
+ buf->getMaterial().setTexture(1, frame.normal_texture);
+ buf->getMaterial().setTexture(2, frame.flags_texture);
}
}
return true;
}
+ void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
+ {
+ // nothing to do if the entire block is opaque
+ if (m_transparent_triangles.empty())
+ return;
+
+ v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
+ v3f rel_camera_pos = camera_pos - block_posf;
+
+ std::vector<s32> triangle_refs;
+ m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
+
+ // arrange index sequences into partial buffers
+ m_transparent_buffers.clear();
+
+ scene::SMeshBuffer *current_buffer = nullptr;
+ std::vector<u16> current_strain;
+ for (auto i : triangle_refs) {
+ const auto &t = m_transparent_triangles[i];
+ if (current_buffer != t.buffer) {
+ if (current_buffer) {
+ m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ current_strain.clear();
+ }
+ current_buffer = t.buffer;
+ }
+ current_strain.push_back(t.p1);
+ current_strain.push_back(t.p2);
+ current_strain.push_back(t.p3);
+ }
+
+ if (!current_strain.empty())
+ m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ }
+
+ void MapBlockMesh::consolidateTransparentBuffers()
+ {
+ m_transparent_buffers.clear();
+
+ scene::SMeshBuffer *current_buffer = nullptr;
+ std::vector<u16> current_strain;
+
+ // use the fact that m_transparent_triangles is already arranged by buffer
+ for (const auto &t : m_transparent_triangles) {
+ if (current_buffer != t.buffer) {
+ if (current_buffer != nullptr) {
+ this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ current_strain.clear();
+ }
+ current_buffer = t.buffer;
+ }
+ current_strain.push_back(t.p1);
+ current_strain.push_back(t.p2);
+ current_strain.push_back(t.p3);
+ }
+
+ if (!current_strain.empty()) {
+ this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
+ }
+ }
+
video::SColor encode_light(u16 light, u8 emissive_light)
{
// Get components
void setSmoothLighting(bool smooth_lighting);
};
+ // represents a triangle as indexes into the vertex buffer in SMeshBuffer
+ class MeshTriangle
+ {
+ public:
+ scene::SMeshBuffer *buffer;
+ u16 p1, p2, p3;
+ v3f centroid;
+ float areaSQ;
+
+ void updateAttributes()
+ {
+ v3f v1 = buffer->getPosition(p1);
+ v3f v2 = buffer->getPosition(p2);
+ v3f v3 = buffer->getPosition(p3);
+
+ centroid = (v1 + v2 + v3) / 3;
+ areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4;
+ }
+
+ v3f getNormal() const {
+ v3f v1 = buffer->getPosition(p1);
+ v3f v2 = buffer->getPosition(p2);
+ v3f v3 = buffer->getPosition(p3);
+
+ return (v2-v1).crossProduct(v3-v1);
+ }
+ };
+
+ /**
+ * Implements a binary space partitioning tree
+ * See also: https://en.wikipedia.org/wiki/Binary_space_partitioning
+ */
+ class MapBlockBspTree
+ {
+ public:
+ MapBlockBspTree() {}
+
+ void buildTree(const std::vector<MeshTriangle> *triangles);
+
+ void traverse(v3f viewpoint, std::vector<s32> &output) const
+ {
+ traverse(root, viewpoint, output);
+ }
+
+ private:
+ // Tree node definition;
+ struct TreeNode
+ {
+ v3f normal;
+ v3f origin;
+ std::vector<s32> triangle_refs;
+ s32 front_ref;
+ s32 back_ref;
+
+ TreeNode() = default;
+ TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) :
+ normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref)
+ {}
+ };
+
+
+ s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth);
+ void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const;
+
+ const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally
+ std::vector<TreeNode> nodes; // list of nodes
+ s32 root = -1; // index of the root node
+ };
+
+ class PartialMeshBuffer
+ {
+ public:
+ PartialMeshBuffer(scene::SMeshBuffer *buffer, const std::vector<u16> &vertex_indexes) :
+ m_buffer(buffer), m_vertex_indexes(vertex_indexes)
+ {}
+
+ scene::IMeshBuffer *getBuffer() const { return m_buffer; }
+ const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; }
+
+ void beforeDraw() const;
+ private:
+ scene::SMeshBuffer *m_buffer;
+ std::vector<u16> m_vertex_indexes;
+ };
+
/*
Holds a mesh for a mapblock.
m_animation_force_timer--;
}
+ /// update transparent buffers to render towards the camera
+ void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
+ void consolidateTransparentBuffers();
+
+ /// get the list of transparent buffers
+ const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
+ {
+ return this->m_transparent_buffers;
+ }
+
+ std::set<v3s16> esp_nodes;
+
private:
+ struct AnimationInfo {
+ int frame; // last animation frame
+ int frame_offset;
+ TileLayer tile;
+ };
+
scene::IMesh *m_mesh[MAX_TILE_LAYERS];
MinimapMapblock *m_minimap_mapblock;
ITextureSource *m_tsrc;
// Maps mesh and mesh buffer (i.e. material) indices to base texture names
std::map<std::pair<u8, u32>, std::string> m_crack_materials;
- // Animation info: texture animationi
+ // Animation info: texture animation
// Maps mesh and mesh buffer indices to TileSpecs
// Keys are pairs of (mesh index, buffer index in the mesh)
- std::map<std::pair<u8, u32>, TileLayer> m_animation_tiles;
- std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame
- std::map<std::pair<u8, u32>, int> m_animation_frame_offsets;
+ std::map<std::pair<u8, u32>, AnimationInfo> m_animation_info;
// Animation info: day/night transitions
// Last daynight_ratio value passed to animate()
// of sunlit vertices
// Keys are pairs of (mesh index, buffer index in the mesh)
std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs;
+
+ // list of all semitransparent triangles in the mapblock
+ std::vector<MeshTriangle> m_transparent_triangles;
+ // Binary Space Partitioning tree for the block
+ MapBlockBspTree m_bsp_tree;
+ // Ordered list of references to parts of transparent buffers to draw
+ std::vector<PartialMeshBuffer> m_transparent_buffers;
};
/*!
// Caches the block at p and its neighbors (if needed) and queues a mesh
// update for the block at p
- void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
+ bool addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
// Returned pointer must be deleted
// Returns NULL if queue is empty
// Caches the block at p and its neighbors (if needed) and queues a mesh
// update for the block at p
- void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
+ void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent,
+ bool update_neighbors = false);
v3s16 m_camera_offset;
MutexedQueue<MeshUpdateResult> m_queue_out;
// TODO: Add callback to update these when g_settings changes
int m_generation_interval;
-protected:
+public:
virtual void doUpdate();
};
data->mode = m_modes[index];
m_current_mode_index = index;
} else {
- data->mode = MinimapModeDef{MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, ""};
+ data->mode = {MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, "", 0};
m_current_mode_index = 0;
}
const u32 size = 0.25 * screensize.Y;
drawMinimap(core::rect<s32>(
- screensize.X - size - 10, 10,
- screensize.X - 10, size + 10));
+ screensize.X - size * 2 - 10, 10,
+ screensize.X - size - 10, size + 10));
}
void Minimap::drawMinimap(core::rect<s32> rect) {
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <iostream>
#include "core.h"
#include "client/camera.h"
#include "client/client.h"
#include "client/clientmap.h"
#include "client/hud.h"
#include "client/minimap.h"
+#include "client/content_cao.h"
+#include "mapblock.h"
+#include "mapsector.h"
#include "client/shadows/dynamicshadowsrender.h"
RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud)
show_minimap = _show_minimap;
draw_wield_tool = _draw_wield_tool;
draw_crosshair = _draw_crosshair;
+ draw_entity_esp = g_settings->getBool("enable_entity_esp");
+ draw_entity_tracers = g_settings->getBool("enable_entity_tracers");
+ draw_player_esp = g_settings->getBool("enable_player_esp");
+ draw_player_tracers = g_settings->getBool("enable_player_tracers");
+ draw_node_esp = g_settings->getBool("enable_node_esp");
+ draw_node_tracers = g_settings->getBool("enable_node_tracers");
+ v3f entity_color = g_settings->getV3F("entity_esp_color");
+ v3f player_color = g_settings->getV3F("player_esp_color");
+ entity_esp_color = video::SColor(255, entity_color.X, entity_color.Y, entity_color.Z);
+ player_esp_color = video::SColor(255, player_color.X, player_color.Y, player_color.Z);
- if (shadow_renderer)
+ if (shadow_renderer) {
+ // This is necessary to render shadows for animations correctly
+ smgr->getRootSceneNode()->OnAnimate(device->getTimer()->getTime());
shadow_renderer->update();
+ }
beforeDraw();
drawAll();
}
+void RenderingCore::drawTracersAndESP()
+{
+ ClientEnvironment &env = client->getEnv();
+ Camera *camera = client->getCamera();
+
+ v3f camera_offset = intToFloat(camera->getOffset(), BS);
+
+ v3f eye_pos = (camera->getPosition() + camera->getDirection() - camera_offset);
+
+ video::SMaterial material, oldmaterial;
+ oldmaterial = driver->getMaterial2D();
+ material.setFlag(video::EMF_LIGHTING, false);
+ material.setFlag(video::EMF_BILINEAR_FILTER, false);
+ material.setFlag(video::EMF_ZBUFFER, false);
+ material.setFlag(video::EMF_ZWRITE_ENABLE, false);
+ driver->setMaterial(material);
+
+ if (draw_entity_esp || draw_entity_tracers || draw_player_esp || draw_player_tracers) {
+ auto allObjects = env.getAllActiveObjects();
+ for (auto &it : allObjects) {
+ ClientActiveObject *cao = it.second;
+ if (cao->isLocalPlayer() || cao->getParent())
+ continue;
+ GenericCAO *obj = dynamic_cast<GenericCAO *>(cao);
+ if (! obj)
+ continue;
+ bool is_player = obj->isPlayer();
+ bool draw_esp = is_player ? draw_player_esp : draw_entity_esp;
+ bool draw_tracers = is_player ? draw_player_tracers : draw_entity_tracers;
+ video::SColor color = is_player ? player_esp_color : entity_esp_color;
+ if (! (draw_esp || draw_tracers))
+ continue;
+ aabb3f box;
+ if (! obj->getSelectionBox(&box))
+ continue;
+ v3f pos = obj->getPosition() - camera_offset;
+ box.MinEdge += pos;
+ box.MaxEdge += pos;
+ if (draw_esp)
+ driver->draw3DBox(box, color);
+ if (draw_tracers)
+ driver->draw3DLine(eye_pos, box.getCenter(), color);
+ }
+ }
+ if (draw_node_esp || draw_node_tracers) {
+ Map &map = env.getMap();
+ std::vector<v3s16> positions;
+ map.listAllLoadedBlocks(positions);
+ for (v3s16 blockp : positions) {
+ MapBlock *block = map.getBlockNoCreate(blockp);
+ if (! block->mesh)
+ continue;
+ for (v3s16 p : block->mesh->esp_nodes) {
+ v3f pos = intToFloat(p, BS) - camera_offset;
+ MapNode node = map.getNode(p);
+ std::vector<aabb3f> boxes;
+ node.getSelectionBoxes(client->getNodeDefManager(), &boxes, node.getNeighbors(p, &map));
+ video::SColor color = client->getNodeDefManager()->get(node).minimap_color;
+ for (aabb3f box : boxes) {
+ box.MinEdge += pos;
+ box.MaxEdge += pos;
+ if (draw_node_esp)
+ driver->draw3DBox(box, color);
+ if (draw_node_tracers)
+ driver->draw3DLine(eye_pos, box.getCenter(), color);
+ }
+ }
+ }
+ }
+
+ driver->setMaterial(oldmaterial);
+}
+
void RenderingCore::draw3D()
{
smgr->drawAll();
return;
hud->drawBlockBounds();
hud->drawSelectionMesh();
+ if (draw_entity_esp || draw_entity_tracers || draw_player_esp || draw_player_tracers || draw_node_esp || draw_node_tracers)
+ drawTracersAndESP();
if (draw_wield_tool)
camera->drawWieldedTool();
}
}
SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
- if (g_logger.getTraceEnabled())
+ if (tracestream)
params.LoggingLevel = irr::ELL_DEBUG;
params.DriverType = driverType;
params.WindowSize = core::dimension2d<u32>(screen_w, screen_h);
#if defined(XORG_USED)
#if RUN_IN_PLACE
return setXorgWindowIconFromPath(
- porting::path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png");
+ porting::path_share + "/misc/dragonfire-xorg-icon-128.png");
#else
// We have semi-support for reading in-place data if we are
// compiled with RUN_IN_PLACE. Don't break with this and
// also try the path_share location.
return setXorgWindowIconFromPath(
- ICON_DIR "/hicolor/128x128/apps/" PROJECT_NAME ".png") ||
- setXorgWindowIconFromPath(porting::path_share + "/misc/" PROJECT_NAME
- "-xorg-icon-128.png");
+ ICON_DIR "/hicolor/128x128/apps/dragonfire.png") ||
+ setXorgWindowIconFromPath(porting::path_share + "/misc/dragonfire-xorg-icon-128.png");
#endif
#elif defined(_WIN32)
HWND hWnd; // Window handle
float RenderingEngine::getDisplayDensity()
{
static float cached_display_density = calcDisplayDensity();
- return cached_display_density;
+ return cached_display_density * g_settings->getFloat("display_density_factor");
}
#elif defined(_WIN32)
display_density = calcDisplayDensity(get_video_driver());
cached = true;
}
- return display_density;
+ return display_density * g_settings->getFloat("display_density_factor");
}
#else
float RenderingEngine::getDisplayDensity()
{
- return g_settings->getFloat("screen_dpi") / 96.0;
+ return (g_settings->getFloat("screen_dpi") / 96.0) * g_settings->getFloat("display_density_factor");
}
#endif
(std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z)
- std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z)
- relbox.MinEdge.Z < 0)
- )
+ )
return COLLISION_AXIS_X;
}
} else {
(std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y)
- std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y)
- relbox.MinEdge.Y < 0)
- )
+ )
return COLLISION_AXIS_Z;
}
}
f32 stepheight, f32 dtime,
v3f *pos_f, v3f *speed_f,
v3f accel_f, ActiveObject *self,
- bool collideWithObjects)
+ bool collideWithObjects, bool jesus)
{
static bool time_notification_done = false;
Map *map = &env->getMap();
v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1);
bool any_position_valid = false;
+ jesus = jesus && g_settings->getBool("jesus");
v3s16 p;
for (p.X = min.X; p.X <= max.X; p.X++)
const NodeDefManager *nodedef = gamedef->getNodeDefManager();
const ContentFeatures &f = nodedef->get(n);
- if (!f.walkable)
+ if (!(f.walkable || (jesus && f.isLiquid())))
continue;
- int n_bouncy_value = itemgroup_get(f.groups, "bouncy");
+ // Negative bouncy may have a meaning, but we need +value here.
+ int n_bouncy_value = abs(itemgroup_get(f.groups, "bouncy"));
int neighbors = 0;
if (f.drawtype == NDT_NODEBOX &&
settings->setDefault("screenshot_quality", "0");
settings->setDefault("client_unload_unused_data_timeout", "600");
settings->setDefault("client_mapblock_limit", "7500");
- settings->setDefault("enable_build_where_you_stand", "false");
+ settings->setDefault("enable_build_where_you_stand", "true");
settings->setDefault("curl_timeout", "20000");
settings->setDefault("curl_parallel_limit", "8");
settings->setDefault("curl_file_download_timeout", "300000");
settings->setDefault("curl_verify_cert", "true");
settings->setDefault("enable_remote_media_server", "true");
- settings->setDefault("enable_client_modding", "false");
+ settings->setDefault("enable_client_modding", "true");
settings->setDefault("max_out_chat_queue_size", "20");
settings->setDefault("pause_on_lost_focus", "false");
settings->setDefault("enable_register_confirmation", "true");
- settings->setDefault("clickable_chat_weblinks", "false");
settings->setDefault("chat_weblink_color", "#8888FF");
+ // Cheat Menu
+ settings->setDefault("cheat_menu_font", "FM_Standard");
+ settings->setDefault("cheat_menu_bg_color", "(45, 45, 68)");
+ settings->setDefault("cheat_menu_bg_color_alpha", "173");
+ settings->setDefault("cheat_menu_active_bg_color", "(0, 0, 0)");
+ settings->setDefault("cheat_menu_active_bg_color_alpha", "210");
+ settings->setDefault("cheat_menu_font_color", "(255, 255, 255)");
+ settings->setDefault("cheat_menu_font_color_alpha", "195");
+ settings->setDefault("cheat_menu_selected_font_color", "(255, 255, 255)");
+ settings->setDefault("cheat_menu_selected_font_color_alpha", "235");
+ settings->setDefault("cheat_menu_head_height", "50");
+ settings->setDefault("cheat_menu_entry_height", "35");
+ settings->setDefault("cheat_menu_entry_width", "200");
+
+ // Cheats
+ settings->setDefault("xray", "false");
+ settings->setDefault("xray_nodes", "default:stone,mcl_core:stone");
+ settings->setDefault("fullbright", "false");
+ settings->setDefault("priv_bypass", "true");
+ settings->setDefault("freecam", "false");
+ settings->setDefault("prevent_natural_damage", "true");
+ settings->setDefault("freecam", "false");
+ settings->setDefault("no_hurt_cam", "false");
+ settings->setDefault("reach", "true");
+ settings->setDefault("hud_flags_bypass", "true");
+ settings->setDefault("antiknockback", "false");
+ settings->setDefault("entity_speed", "false");
+ settings->setDefault("autodig", "false");
+ settings->setDefault("fastdig", "false");
+ settings->setDefault("jesus", "false");
+ settings->setDefault("fastplace", "false");
+ settings->setDefault("autoplace", "false");
+ settings->setDefault("instant_break", "false");
+ settings->setDefault("no_night", "false");
+ settings->setDefault("coords", "false");
+ settings->setDefault("point_liquids", "false");
+ settings->setDefault("spamclick", "false");
+ settings->setDefault("no_force_rotate", "false");
+ settings->setDefault("no_slow", "false");
+ settings->setDefault("float_above_parent", "false");
+ settings->setDefault("dont_point_nodes", "false");
+ settings->setDefault("cheat_hud", "true");
+ settings->setDefault("node_esp_nodes", "");
+ settings->setDefault("jetpack", "false");
+ settings->setDefault("autohit", "false");
+ settings->setDefault("antislip", "false");
+ settings->setDefault("enable_entity_esp", "false");
+ settings->setDefault("enable_entity_tracers", "false");
+ settings->setDefault("enable_player_esp", "false");
+ settings->setDefault("enable_player_tracers", "false");
+ settings->setDefault("enable_node_esp", "false");
+ settings->setDefault("enable_node_tracers", "false");
+ settings->setDefault("entity_esp_color", "(255, 255, 255)");
+ settings->setDefault("player_esp_color", "(0, 255, 0)");
+ settings->setDefault("tool_range", "2");
+ settings->setDefault("scaffold", "false");
+ settings->setDefault("killaura", "false");
+ settings->setDefault("airjump", "false");
+ settings->setDefault("spider", "false");
+
// Keymap
settings->setDefault("remote_port", "30000");
settings->setDefault("keymap_forward", "KEY_KEY_W");
settings->setDefault("keymap_drop", "KEY_KEY_Q");
settings->setDefault("keymap_zoom", "KEY_KEY_Z");
settings->setDefault("keymap_inventory", "KEY_KEY_I");
+ settings->setDefault("keymap_enderchest", "KEY_KEY_O");
settings->setDefault("keymap_aux1", "KEY_KEY_E");
settings->setDefault("keymap_chat", "KEY_KEY_T");
settings->setDefault("keymap_cmd", "/");
settings->setDefault("keymap_toggle_hud", "KEY_F1");
settings->setDefault("keymap_toggle_chat", "KEY_F2");
settings->setDefault("keymap_toggle_fog", "KEY_F3");
+ settings->setDefault("keymap_toggle_cheat_menu", "KEY_F8");
#if DEBUG
settings->setDefault("keymap_toggle_update_camera", "KEY_F4");
#else
settings->setDefault("keymap_screenshot", "KEY_F12");
settings->setDefault("keymap_increase_viewing_range_min", "+");
settings->setDefault("keymap_decrease_viewing_range_min", "-");
+ settings->setDefault("keymap_toggle_killaura", "KEY_KEY_X");
+ settings->setDefault("keymap_toggle_freecam", "KEY_KEY_G");
+ settings->setDefault("keymap_toggle_scaffold", "KEY_KEY_Y");
+ settings->setDefault("keymap_select_up", "KEY_UP");
+ settings->setDefault("keymap_select_down", "KEY_DOWN");
+ settings->setDefault("keymap_select_left", "KEY_LEFT");
+ settings->setDefault("keymap_select_right", "KEY_RIGHT");
+ settings->setDefault("keymap_select_confirm", "KEY_RETURN");
settings->setDefault("keymap_slot1", "KEY_KEY_1");
settings->setDefault("keymap_slot2", "KEY_KEY_2");
settings->setDefault("keymap_slot3", "KEY_KEY_3");
settings->setDefault("leaves_style", "fancy");
settings->setDefault("connected_glass", "false");
settings->setDefault("smooth_lighting", "true");
+ settings->setDefault("performance_tradeoffs", "false");
settings->setDefault("lighting_alpha", "0.0");
settings->setDefault("lighting_beta", "1.5");
settings->setDefault("display_gamma", "1.0");
settings->setDefault("opaque_water", "false");
settings->setDefault("console_height", "0.6");
settings->setDefault("console_color", "(0,0,0)");
- settings->setDefault("console_alpha", "200");
+ settings->setDefault("console_alpha", "150");
settings->setDefault("formspec_fullscreen_bg_color", "(0,0,0)");
settings->setDefault("formspec_fullscreen_bg_opacity", "140");
settings->setDefault("formspec_default_bg_color", "(0,0,0)");
settings->setDefault("enable_particles", "true");
settings->setDefault("arm_inertia", "true");
settings->setDefault("show_nametag_backgrounds", "true");
+ settings->setDefault("transparency_sorting_distance", "16");
settings->setDefault("enable_minimap", "true");
- settings->setDefault("minimap_shape_round", "true");
+ settings->setDefault("minimap_shape_round", "false");
settings->setDefault("minimap_double_scan_height", "true");
// Effects
// Effects Shadows
settings->setDefault("enable_dynamic_shadows", "false");
- settings->setDefault("shadow_strength", "0.2");
+ settings->setDefault("shadow_strength_gamma", "1.0");
settings->setDefault("shadow_map_max_distance", "200.0");
settings->setDefault("shadow_map_texture_size", "2048");
settings->setDefault("shadow_map_texture_32bit", "true");
settings->setDefault("aux1_descends", "false");
settings->setDefault("doubletap_jump", "false");
settings->setDefault("always_fly_fast", "true");
- #ifdef __ANDROID__
+ #ifdef HAVE_TOUCHSCREENGUI
settings->setDefault("autojump", "true");
#else
settings->setDefault("autojump", "false");
settings->setDefault("main_menu_path", "");
settings->setDefault("serverlist_file", "favoriteservers.json");
- #if USE_FREETYPE
- settings->setDefault("freetype", "true");
+ // General font settings
settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "Arimo-Regular.ttf"));
settings->setDefault("font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-Italic.ttf"));
settings->setDefault("font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Arimo-Bold.ttf"));
settings->setDefault("font_italic", "false");
settings->setDefault("font_shadow", "1");
settings->setDefault("font_shadow_alpha", "127");
+ settings->setDefault("font_size_divisible_by", "1");
settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "Cousine-Regular.ttf"));
settings->setDefault("mono_font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-Italic.ttf"));
settings->setDefault("mono_font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Cousine-Bold.ttf"));
settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf"));
+ settings->setDefault("mono_font_size_divisible_by", "1");
settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf"));
std::string font_size_str = std::to_string(TTF_DEFAULT_FONT_SIZE);
- #else
- settings->setDefault("freetype", "false");
- settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans"));
- settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans"));
-
- std::string font_size_str = std::to_string(DEFAULT_FONT_SIZE);
- #endif
- // General font settings
settings->setDefault("font_size", font_size_str);
settings->setDefault("mono_font_size", font_size_str);
settings->setDefault("chat_font_size", "0"); // Default "font_size"
// ContentDB
- settings->setDefault("contentdb_url", "https://content.minetest.net");
+ settings->setDefault("contentdb_url", "http://cheatdb.elidragon.com");
settings->setDefault("contentdb_max_concurrent_downloads", "3");
#ifdef __ANDROID__
- settings->setDefault("contentdb_flag_blacklist", "nonfree, android_default");
+ settings->setDefault("contentdb_flag_blacklist", "android_default");
#else
- settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default");
+ settings->setDefault("contentdb_flag_blacklist", "desktop_default");
#endif
settings->setDefault("max_simultaneous_block_sends_per_client", "40");
settings->setDefault("time_send_interval", "5");
- settings->setDefault("default_game", "minetest");
+ settings->setDefault("default_game", "MineClone2");
settings->setDefault("motd", "");
settings->setDefault("max_users", "15");
settings->setDefault("creative_mode", "false");
settings->setDefault("time_speed", "72");
settings->setDefault("world_start_time", "6125");
settings->setDefault("server_unload_unused_data_timeout", "29");
- settings->setDefault("max_objects_per_block", "64");
+ settings->setDefault("max_objects_per_block", "256");
settings->setDefault("server_map_save_interval", "5.3");
settings->setDefault("chat_message_max_size", "500");
settings->setDefault("chat_message_limit_per_10sec", "8.0");
// Mapgen
settings->setDefault("mg_name", "v7");
settings->setDefault("water_level", "1");
- settings->setDefault("mapgen_limit", "31000");
+ settings->setDefault("mapgen_limit", "31007");
settings->setDefault("chunksize", "5");
settings->setDefault("fixed_map_seed", "");
settings->setDefault("max_block_generate_distance", "10");
settings->setDefault("enable_console", "false");
settings->setDefault("screen_dpi", "72");
+ settings->setDefault("display_density_factor", "1");
// Altered settings for macOS
#if defined(__MACH__) && defined(__APPLE__)
settings->setDefault("fps_max", "0");
#endif
+ #ifdef HAVE_TOUCHSCREENGUI
+ settings->setDefault("touchtarget", "true");
+ settings->setDefault("touchscreen_threshold","20");
+ settings->setDefault("fixed_virtual_joystick", "false");
+ settings->setDefault("virtual_joystick_triggers_aux1", "false");
+ settings->setDefault("clickable_chat_weblinks", "false");
+ #else
+ settings->setDefault("clickable_chat_weblinks", "true");
+ #endif
// Altered settings for Android
#ifdef __ANDROID__
settings->setDefault("screen_w", "0");
settings->setDefault("screen_h", "0");
settings->setDefault("fullscreen", "true");
- settings->setDefault("touchtarget", "true");
- settings->setDefault("touchscreen_threshold","20");
- settings->setDefault("fixed_virtual_joystick", "false");
- settings->setDefault("virtual_joystick_triggers_aux1", "false");
settings->setDefault("smooth_lighting", "false");
+ settings->setDefault("performance_tradeoffs", "true");
settings->setDefault("max_simultaneous_block_sends_per_client", "10");
settings->setDefault("emergequeue_limit_diskonly", "16");
settings->setDefault("emergequeue_limit_generate", "16");
settings->setDefault("enable_3d_clouds", "false");
settings->setDefault("fps_max", "30");
settings->setDefault("fps_max_unfocused", "10");
- settings->setDefault("max_objects_per_block", "20");
settings->setDefault("sqlite_synchronous", "1");
settings->setDefault("map_compression_level_disk", "-1");
settings->setDefault("map_compression_level_net", "-1");
u32 Environment::getDayNightRatio()
{
MutexAutoLock lock(this->m_time_lock);
+ if (g_settings->getBool("no_night"))
+ return time_to_daynight_ratio(12000, m_cache_enable_shaders);
if (m_enable_day_night_ratio_override)
return m_day_night_ratio_override;
return time_to_daynight_ratio(m_time_of_day_f * 24000, m_cache_enable_shaders);
Check if a node is pointable
*/
inline static bool isPointableNode(const MapNode &n,
- const NodeDefManager *nodedef , bool liquids_pointable)
+ const NodeDefManager *nodedef , bool liquids_pointable, bool nodes_pointable)
{
+ if (! nodes_pointable)
+ return false;
const ContentFeatures &features = nodedef->get(n);
return features.pointable ||
- (liquids_pointable && features.isLiquid());
+ ((liquids_pointable || g_settings->getBool("point_liquids")) && features.isLiquid());
}
void Environment::continueRaycast(RaycastState *state, PointedThing *result)
new_nodes.MaxEdge.Z = new_nodes.MinEdge.Z;
}
+ if (new_nodes.MaxEdge.X == S16_MAX ||
+ new_nodes.MaxEdge.Y == S16_MAX ||
+ new_nodes.MaxEdge.Z == S16_MAX) {
+ break; // About to go out of bounds
+ }
+
// For each untested node
for (s16 x = new_nodes.MinEdge.X; x <= new_nodes.MaxEdge.X; x++)
for (s16 y = new_nodes.MinEdge.Y; y <= new_nodes.MaxEdge.Y; y++)
n = map.getNode(np, &is_valid_position);
if (!(is_valid_position && isPointableNode(n, nodedef,
- state->m_liquids_pointable))) {
+ state->m_liquids_pointable, state->m_nodes_pointable))) {
continue;
}
#include "irrlichttypes.h"
class IItemDefManager;
+class IWritableItemDefManager;
class NodeDefManager;
class ICraftDefManager;
class ITextureSource;
class Camera;
class ModChannel;
class ModMetadata;
+ class ModMetadataDatabase;
namespace irr { namespace scene {
class IAnimatedMesh;
// These are thread-safe IF they are not edited while running threads.
// Thus, first they are set up and then they are only read.
virtual IItemDefManager* getItemDefManager()=0;
+ virtual IWritableItemDefManager* getWritableItemDefManager()=0;
virtual const NodeDefManager* getNodeDefManager()=0;
+ virtual NodeDefManager* getWritableNodeDefManager()=0;
virtual ICraftDefManager* getCraftDefManager()=0;
// Used for keeping track of names/ids of unknown nodes
virtual IRollbackManager* getRollbackManager() { return NULL; }
// Shorthands
+ // TODO: these should be made const-safe so that a const IGameDef* is
+ // actually usable
IItemDefManager *idef() { return getItemDefManager(); }
const NodeDefManager *ndef() { return getNodeDefManager(); }
ICraftDefManager *cdef() { return getCraftDefManager(); }
virtual const std::vector<ModSpec> &getMods() const = 0;
virtual const ModSpec* getModSpec(const std::string &modname) const = 0;
virtual std::string getWorldPath() const { return ""; }
- virtual std::string getModStoragePath() const = 0;
virtual bool registerModStorage(ModMetadata *storage) = 0;
virtual void unregisterModStorage(const std::string &name) = 0;
+ virtual ModMetadataDatabase *getModStorageDatabase() = 0;
virtual bool joinModChannel(const std::string &channel) = 0;
virtual bool leaveModChannel(const std::string &channel) = 0;
+ set(extra_gui_SRCS "")
+ if(ENABLE_TOUCH)
+ set(extra_gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/touchscreengui.cpp)
+ endif()
+
set(gui_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/cheatMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiAnimatedImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiBackgroundImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/profilergraph.cpp
+ ${extra_gui_SRCS}
PARENT_SCOPE
)
--- /dev/null
- else if (str == "FM_Simple")
- return FM_Simple;
- else if (str == "FM_SimpleMono")
- return FM_SimpleMono;
+/*
+Dragonfire
+Copyright (C) 2020 Elias Fleckenstein <eliasfleckenstein@web.de>
+
+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 "script/scripting_client.h"
+#include "client/client.h"
+#include "client/fontengine.h"
+#include "cheatMenu.h"
+#include <cstddef>
+
+FontMode CheatMenu::fontStringToEnum(std::string str)
+{
+ if (str == "FM_Standard")
+ return FM_Standard;
+ else if (str == "FM_Mono")
+ return FM_Mono;
+ else if (str == "FM_Fallback")
+ return _FM_Fallback;
+ else if (str == "FM_MaxMode")
+ return FM_MaxMode;
+ else if (str == "FM_Unspecified")
+ return FM_Unspecified;
+ else
+ return FM_Standard;
+}
+
+CheatMenu::CheatMenu(Client *client) : m_client(client)
+{
+ FontMode fontMode = fontStringToEnum(g_settings->get("cheat_menu_font"));
+ v3f bg_color, active_bg_color, font_color, selected_font_color;
+
+ bg_color = g_settings->getV3F("cheat_menu_bg_color");
+ active_bg_color = g_settings->getV3F("cheat_menu_active_bg_color");
+ font_color = g_settings->getV3F("cheat_menu_font_color");
+ selected_font_color = g_settings->getV3F("cheat_menu_selected_font_color");
+
+ m_bg_color = video::SColor(g_settings->getU32("cheat_menu_bg_color_alpha"),
+ bg_color.X, bg_color.Y, bg_color.Z);
+
+ m_active_bg_color = video::SColor(
+ g_settings->getU32("cheat_menu_active_bg_color_alpha"),
+ active_bg_color.X, active_bg_color.Y, active_bg_color.Z);
+
+ m_font_color = video::SColor(g_settings->getU32("cheat_menu_font_color_alpha"),
+ font_color.X, font_color.Y, font_color.Z);
+
+ m_selected_font_color = video::SColor(
+ g_settings->getU32("cheat_menu_selected_font_color_alpha"),
+ selected_font_color.X, selected_font_color.Y,
+ selected_font_color.Z);
+
+ m_head_height = g_settings->getU32("cheat_menu_head_height");
+ m_entry_height = g_settings->getU32("cheat_menu_entry_height");
+ m_entry_width = g_settings->getU32("cheat_menu_entry_width");
+
+ m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, fontMode);
+
+ if (!m_font) {
+ errorstream << "CheatMenu: Unable to load font" << std::endl;
+ } else {
+ core::dimension2d<u32> dim = m_font->getDimension(L"M");
+ m_fontsize = v2u32(dim.Width, dim.Height);
+ m_font->grab();
+ }
+ m_fontsize.X = MYMAX(m_fontsize.X, 1);
+ m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
+}
+
+void CheatMenu::drawEntry(video::IVideoDriver *driver, std::string name, int number,
+ bool selected, bool active, CheatMenuEntryType entry_type)
+{
+ int x = m_gap, y = m_gap, width = m_entry_width, height = m_entry_height;
+ video::SColor *bgcolor = &m_bg_color, *fontcolor = &m_font_color;
+ if (entry_type == CHEAT_MENU_ENTRY_TYPE_HEAD) {
+ bgcolor = &m_active_bg_color;
+ height = m_head_height;
+ } else {
+ bool is_category = entry_type == CHEAT_MENU_ENTRY_TYPE_CATEGORY;
+ y += m_gap + m_head_height +
+ (number + (is_category ? 0 : m_selected_category)) *
+ (m_entry_height + m_gap);
+ x += (is_category ? 0 : m_gap + m_entry_width);
+ if (active)
+ bgcolor = &m_active_bg_color;
+ if (selected)
+ fontcolor = &m_selected_font_color;
+ }
+ driver->draw2DRectangle(*bgcolor, core::rect<s32>(x, y, x + width, y + height));
+ if (selected)
+ driver->draw2DRectangleOutline(
+ core::rect<s32>(x - 1, y - 1, x + width, y + height),
+ *fontcolor);
+ int fx = x + 5, fy = y + (height - m_fontsize.Y) / 2;
+ core::rect<s32> fontbounds(
+ fx, fy, fx + m_fontsize.X * name.size(), fy + m_fontsize.Y);
+ m_font->draw(name.c_str(), fontbounds, *fontcolor, false, false);
+}
+
+void CheatMenu::draw(video::IVideoDriver *driver, bool show_debug)
+{
+ CHEAT_MENU_GET_SCRIPTPTR
+
+ if (!show_debug)
+ drawEntry(driver, "Dragonfireclient", 0, false, false,
+ CHEAT_MENU_ENTRY_TYPE_HEAD);
+ int category_count = 0;
+ for (auto category = script->m_cheat_categories.begin();
+ category != script->m_cheat_categories.end(); category++) {
+ bool is_selected = category_count == m_selected_category;
+ drawEntry(driver, (*category)->m_name, category_count, is_selected, false,
+ CHEAT_MENU_ENTRY_TYPE_CATEGORY);
+ if (is_selected && m_cheat_layer) {
+ int cheat_count = 0;
+ for (auto cheat = (*category)->m_cheats.begin();
+ cheat != (*category)->m_cheats.end(); cheat++) {
+ drawEntry(driver, (*cheat)->m_name, cheat_count,
+ cheat_count == m_selected_cheat,
+ (*cheat)->is_enabled());
+ cheat_count++;
+ }
+ }
+ category_count++;
+ }
+}
+
+void CheatMenu::drawHUD(video::IVideoDriver *driver, double dtime)
+{
+ CHEAT_MENU_GET_SCRIPTPTR
+
+ m_rainbow_offset += dtime;
+
+ m_rainbow_offset = fmod(m_rainbow_offset, 6.0f);
+
+ std::vector<std::string> enabled_cheats;
+
+ int cheat_count = 0;
+
+ for (auto category = script->m_cheat_categories.begin();
+ category != script->m_cheat_categories.end(); category++) {
+ for (auto cheat = (*category)->m_cheats.begin();
+ cheat != (*category)->m_cheats.end(); cheat++) {
+ if ((*cheat)->is_enabled()) {
+ enabled_cheats.push_back((*cheat)->m_name);
+ cheat_count++;
+ }
+ }
+ }
+
+ if (enabled_cheats.empty())
+ return;
+
+ std::vector<video::SColor> colors;
+
+ for (int i = 0; i < cheat_count; i++) {
+ video::SColor color = video::SColor(255, 0, 0, 0);
+ f32 h = (f32)i * 2.0f / (f32)cheat_count - m_rainbow_offset;
+ if (h < 0)
+ h = 6.0f + h;
+ f32 x = (1 - fabs(fmod(h, 2.0f) - 1.0f)) * 255.0f;
+ switch ((int)h) {
+ case 0:
+ color = video::SColor(255, 255, x, 0);
+ break;
+ case 1:
+ color = video::SColor(255, x, 255, 0);
+ break;
+ case 2:
+ color = video::SColor(255, 0, 255, x);
+ break;
+ case 3:
+ color = video::SColor(255, 0, x, 255);
+ break;
+ case 4:
+ color = video::SColor(255, x, 0, 255);
+ break;
+ case 5:
+ color = video::SColor(255, 255, 0, x);
+ break;
+ }
+ colors.push_back(color);
+ }
+
+ core::dimension2d<u32> screensize = driver->getScreenSize();
+
+ u32 y = 5;
+
+ int i = 0;
+ for (std::string cheat : enabled_cheats) {
+ core::dimension2d<u32> dim =
+ m_font->getDimension(utf8_to_wide(cheat).c_str());
+ u32 x = screensize.Width - 5 - dim.Width;
+
+ core::rect<s32> fontbounds(x, y, x + dim.Width, y + dim.Height);
+ m_font->draw(cheat.c_str(), fontbounds, colors[i], false, false);
+
+ y += dim.Height;
+ i++;
+ }
+}
+
+void CheatMenu::selectUp()
+{
+ CHEAT_MENU_GET_SCRIPTPTR
+
+ int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category]
+ ->m_cheats.size()
+ : script->m_cheat_categories.size()) -
+ 1;
+ int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category;
+ --*selected;
+ if (*selected < 0)
+ *selected = max;
+}
+
+void CheatMenu::selectDown()
+{
+ CHEAT_MENU_GET_SCRIPTPTR
+
+ int max = (m_cheat_layer ? script->m_cheat_categories[m_selected_category]
+ ->m_cheats.size()
+ : script->m_cheat_categories.size()) -
+ 1;
+ int *selected = m_cheat_layer ? &m_selected_cheat : &m_selected_category;
+ ++*selected;
+ if (*selected > max)
+ *selected = 0;
+}
+
+void CheatMenu::selectRight()
+{
+ if (m_cheat_layer)
+ return;
+ m_cheat_layer = true;
+ m_selected_cheat = 0;
+}
+
+void CheatMenu::selectLeft()
+{
+ if (!m_cheat_layer)
+ return;
+ m_cheat_layer = false;
+}
+
+void CheatMenu::selectConfirm()
+{
+ CHEAT_MENU_GET_SCRIPTPTR
+
+ if (m_cheat_layer)
+ script->toggle_cheat(script->m_cheat_categories[m_selected_category]
+ ->m_cheats[m_selected_cheat]);
+}
return block;
}
- bool Map::isNodeUnderground(v3s16 p)
- {
- v3s16 blockpos = getNodeBlockPos(p);
- MapBlock *block = getBlockNoCreateNoEx(blockpos);
- return block && block->getIsUnderground();
- }
-
+void Map::listAllLoadedBlocks(std::vector<v3s16> &dst)
+{
+ for (auto §or_it : m_sectors) {
+ MapSector *sector = sector_it.second;
+
+ MapBlockVect blocks;
+ sector->getBlocks(blocks);
+
+ for (MapBlock *block : blocks) {
+ v3s16 p = block->getPos();
+ dst.push_back(p);
+ }
+ }
+}
+
bool Map::isValidPosition(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
return node;
}
- // throws InvalidPositionException if not found
- void Map::setNode(v3s16 p, MapNode & n)
+ static void set_node_in_block(MapBlock *block, v3s16 relpos, MapNode n)
{
- v3s16 blockpos = getNodeBlockPos(p);
- MapBlock *block = getBlockNoCreate(blockpos);
- v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
// Never allow placing CONTENT_IGNORE, it causes problems
if(n.getContent() == CONTENT_IGNORE){
+ const NodeDefManager *nodedef = block->getParent()->getNodeDefManager();
+ v3s16 blockpos = block->getPos();
+ v3s16 p = blockpos * MAP_BLOCKSIZE + relpos;
bool temp_bool;
- errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE"
+ errorstream<<"Not allowing to place CONTENT_IGNORE"
<<" while trying to replace \""
- <<m_nodedef->get(block->getNodeNoCheck(relpos, &temp_bool)).name
+ <<nodedef->get(block->getNodeNoCheck(relpos, &temp_bool)).name
<<"\" at "<<PP(p)<<" (block "<<PP(blockpos)<<")"<<std::endl;
return;
}
block->setNodeNoCheck(relpos, n);
}
+ // throws InvalidPositionException if not found
+ void Map::setNode(v3s16 p, MapNode & n)
+ {
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock *block = getBlockNoCreate(blockpos);
+ v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
+ set_node_in_block(block, relpos, n);
+ }
+
void Map::addNodeAndUpdate(v3s16 p, MapNode n,
std::map<v3s16, MapBlock*> &modified_blocks,
bool remove_metadata)
// Collect old node for rollback
RollbackNode rollback_oldnode(this, p, m_gamedef);
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock *block = getBlockNoCreate(blockpos);
+ if (block->isDummy())
+ throw InvalidPositionException();
+ v3s16 relpos = p - blockpos * MAP_BLOCKSIZE;
+
// This is needed for updating the lighting
- MapNode oldnode = getNode(p);
+ MapNode oldnode = block->getNodeUnsafe(relpos);
// Remove node metadata
if (remove_metadata) {
}
// Set the node on the map
- // Ignore light (because calling voxalgo::update_lighting_nodes)
- n.setLight(LIGHTBANK_DAY, 0, m_nodedef);
- n.setLight(LIGHTBANK_NIGHT, 0, m_nodedef);
- setNode(p, n);
+ const ContentFeatures &cf = m_nodedef->get(n);
+ const ContentFeatures &oldcf = m_nodedef->get(oldnode);
+ if (cf.lightingEquivalent(oldcf)) {
+ // No light update needed, just copy over the old light.
+ n.setLight(LIGHTBANK_DAY, oldnode.getLightRaw(LIGHTBANK_DAY, oldcf), cf);
+ n.setLight(LIGHTBANK_NIGHT, oldnode.getLightRaw(LIGHTBANK_NIGHT, oldcf), cf);
+ set_node_in_block(block, relpos, n);
+
+ modified_blocks[blockpos] = block;
+ } else {
+ // Ignore light (because calling voxalgo::update_lighting_nodes)
+ n.setLight(LIGHTBANK_DAY, 0, cf);
+ n.setLight(LIGHTBANK_NIGHT, 0, cf);
+ set_node_in_block(block, relpos, n);
- // Update lighting
- std::vector<std::pair<v3s16, MapNode> > oldnodes;
- oldnodes.emplace_back(p, oldnode);
- voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks);
+ // Update lighting
+ std::vector<std::pair<v3s16, MapNode> > oldnodes;
+ oldnodes.emplace_back(p, oldnode);
+ voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks);
- for (auto &modified_block : modified_blocks) {
- modified_block.second->expireDayNightDiff();
+ for (auto &modified_block : modified_blocks) {
+ modified_block.second->expireDayNightDiff();
+ }
}
// Report for rollback
action.setSetNode(p, rollback_oldnode, rollback_newnode);
m_gamedef->rollback()->reportAction(action);
}
-
- /*
- Add neighboring liquid nodes and this node to transform queue.
- (it's vital for the node itself to get updated last, if it was removed.)
- */
-
- for (const v3s16 &dir : g_7dirs) {
- v3s16 p2 = p + dir;
-
- bool is_valid_position;
- MapNode n2 = getNode(p2, &is_valid_position);
- if(is_valid_position &&
- (m_nodedef->get(n2).isLiquid() ||
- n2.getContent() == CONTENT_AIR))
- m_transforming_liquid.push_back(p2);
- }
}
void Map::removeNodeAndUpdate(v3s16 p,
void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks,
std::vector<v3s16> *unloaded_blocks)
{
- bool save_before_unloading = (mapType() == MAPTYPE_SERVER);
+ bool save_before_unloading = maySaveBlocks();
// Profile modified reasons
Profiler modprofiler;
u32 saved_blocks_count = 0;
u32 block_count_all = 0;
+ const auto start_time = porting::getTimeUs();
beginSave();
// If there is no practical limit, we spare creation of mapblock_queue
}
}
+ // Delete sector if we emptied it
if (all_blocks_deleted) {
sector_deletion_queue.push_back(sector_it.first);
}
}
}
block_count_all = mapblock_queue.size();
+
// Delete old blocks, and blocks over the limit from the memory
while (!mapblock_queue.empty() && (mapblock_queue.size() > max_loaded_blocks
|| mapblock_queue.top().block->getUsageTimer() > unload_timeout)) {
deleted_blocks_count++;
block_count_all--;
}
+
// Delete empty sectors
for (auto §or_it : m_sectors) {
if (sector_it.second->empty()) {
}
}
}
+
endSave();
+ const auto end_time = porting::getTimeUs();
+
+ reportMetrics(end_time - start_time, saved_blocks_count, block_count_all);
// Finally delete the empty sectors
deleteSectors(sector_deletion_queue);
{ }
};
- void Map::transforming_liquid_add(v3s16 p) {
+ void ServerMap::transforming_liquid_add(v3s16 p) {
m_transforming_liquid.push_back(p);
}
- void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
+ void ServerMap::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
ServerEnvironment *env)
{
u32 loopcount = 0;
m_savedir = savedir;
m_map_saving_enabled = false;
- m_save_time_counter = mb->addCounter("minetest_core_map_save_time", "Map save time (in nanoseconds)");
+ m_save_time_counter = mb->addCounter(
+ "minetest_map_save_time", "Time spent saving blocks (in microseconds)");
+ m_save_count_counter = mb->addCounter(
+ "minetest_map_saved_blocks", "Number of blocks saved");
+ m_loaded_blocks_gauge = mb->addGauge(
+ "minetest_map_loaded_blocks", "Number of loaded blocks");
m_map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9);
/*
Do not create over max mapgen limit
*/
- const s16 max_limit_bp = MAX_MAP_GENERATION_LIMIT / MAP_BLOCKSIZE;
- if (p2d.X < -max_limit_bp ||
- p2d.X > max_limit_bp ||
- p2d.Y < -max_limit_bp ||
- p2d.Y > max_limit_bp)
+ if (blockpos_over_max_limit(v3s16(p2d.X, 0, p2d.Y)))
throw InvalidPositionException("createSector(): pos. over max mapgen limit");
/*
sector = new MapSector(this, p2d, m_gamedef);
- // Sector position on map in nodes
- //v2s16 nodepos2d = p2d * MAP_BLOCKSIZE;
-
/*
Insert to container
*/
return m_emerge && m_emerge->isBlockInQueue(pos);
}
+ void ServerMap::addNodeAndUpdate(v3s16 p, MapNode n,
+ std::map<v3s16, MapBlock*> &modified_blocks,
+ bool remove_metadata)
+ {
+ Map::addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
+
+ /*
+ Add neighboring liquid nodes and this node to transform queue.
+ (it's vital for the node itself to get updated last, if it was removed.)
+ */
+
+ for (const v3s16 &dir : g_7dirs) {
+ v3s16 p2 = p + dir;
+
+ bool is_valid_position;
+ MapNode n2 = getNode(p2, &is_valid_position);
+ if(is_valid_position &&
+ (m_nodedef->get(n2).isLiquid() ||
+ n2.getContent() == CONTENT_AIR))
+ m_transforming_liquid.push_back(p2);
+ }
+ }
+
// N.B. This requires no synchronization, since data will not be modified unless
// the VoxelManipulator being updated belongs to the same thread.
void ServerMap::updateVManip(v3s16 pos)
vm->m_is_dirty = true;
}
+ void ServerMap::reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks)
+ {
+ m_loaded_blocks_gauge->set(all_blocks);
+ m_save_time_counter->increment(save_time_us);
+ m_save_count_counter->increment(saved_blocks);
+ }
+
void ServerMap::save(ModifiedState save_level)
{
if (!m_map_saving_enabled) {
return;
}
- u64 start_time = porting::getTimeNs();
+ const auto start_time = porting::getTimeUs();
if(save_level == MOD_STATE_CLEAN)
infostream<<"ServerMap: Saving whole map, this can take time."
modprofiler.print(infostream);
}
- auto end_time = porting::getTimeNs();
- m_save_time_counter->increment(end_time - start_time);
+ const auto end_time = porting::getTimeUs();
+ reportMetrics(end_time - start_time, block_count, block_count_all);
}
void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst)
dbase_ro->listAllLoadableBlocks(dst);
}
-void ServerMap::listAllLoadedBlocks(std::vector<v3s16> &dst)
-{
- for (auto §or_it : m_sectors) {
- MapSector *sector = sector_it.second;
-
- MapBlockVect blocks;
- sector->getBlocks(blocks);
-
- for (MapBlock *block : blocks) {
- v3s16 p = block->getPos();
- dst.push_back(p);
- }
- }
-}
-
MapDatabase *ServerMap::createDatabase(
const std::string &name,
const std::string &savedir,
VoxelManipulator(),
m_map(map)
{
+ assert(map);
}
void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
{
TimeTaker timer1("initialEmerge", &emerge_time);
+ assert(m_map);
+
// Units of these are MapBlocks
v3s16 p_min = blockpos_min;
v3s16 p_max = blockpos_max;
{
if(m_area.getExtent() == v3s16(0,0,0))
return;
+ assert(m_map);
/*
Copy data of all blocks
}
}
+ MMVManip *MMVManip::clone() const
+ {
+ MMVManip *ret = new MMVManip();
+
+ const s32 size = m_area.getVolume();
+ ret->m_area = m_area;
+ if (m_data) {
+ ret->m_data = new MapNode[size];
+ memcpy(ret->m_data, m_data, size * sizeof(MapNode));
+ }
+ if (m_flags) {
+ ret->m_flags = new u8[size];
+ memcpy(ret->m_flags, m_flags, size * sizeof(u8));
+ }
+
+ ret->m_is_dirty = m_is_dirty;
+ // Even if the copy is disconnected from a map object keep the information
+ // needed to write it back to one
+ ret->m_loaded_blocks = m_loaded_blocks;
+
+ return ret;
+ }
+
+ void MMVManip::reparent(Map *map)
+ {
+ assert(map && !m_map);
+ m_map = map;
+ }
+
//END
MapEditEvent
*/
- #define MAPTYPE_BASE 0
- #define MAPTYPE_SERVER 1
- #define MAPTYPE_CLIENT 2
-
enum MapEditEventType{
// Node added (changed from air or something else to something)
MEET_ADDNODE,
virtual ~Map();
DISABLE_CLASS_COPY(Map);
- virtual s32 mapType() const
- {
- return MAPTYPE_BASE;
- }
-
/*
Drop (client) or delete (server) the map.
*/
MapBlock * getBlockNoCreate(v3s16 p);
// Returns NULL if not found
MapBlock * getBlockNoCreateNoEx(v3s16 p);
-
+
+ void listAllLoadedBlocks(std::vector<v3s16> &dst);
+
/* Server overrides */
virtual MapBlock * emergeBlock(v3s16 p, bool create_blank=true)
{ return getBlockNoCreateNoEx(p); }
inline const NodeDefManager * getNodeDefManager() { return m_nodedef; }
- // Returns InvalidPositionException if not found
- bool isNodeUnderground(v3s16 p);
-
bool isValidPosition(v3s16 p);
// throws InvalidPositionException if not found
/*
These handle lighting but not faces.
*/
- void addNodeAndUpdate(v3s16 p, MapNode n,
+ virtual void addNodeAndUpdate(v3s16 p, MapNode n,
std::map<v3s16, MapBlock*> &modified_blocks,
bool remove_metadata = true);
void removeNodeAndUpdate(v3s16 p,
virtual void save(ModifiedState save_level) { FATAL_ERROR("FIXME"); }
+ /*
+ Return true unless the map definitely cannot save blocks.
+ */
+ virtual bool maySaveBlocks() { return true; }
+
// Server implements these.
// Client leaves them as no-op.
virtual bool saveBlock(MapBlock *block) { return false; }
/*
Updates usage timers and unloads unused blocks and sectors.
- Saves modified blocks before unloading on MAPTYPE_SERVER.
+ Saves modified blocks before unloading if possible.
*/
void timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks,
std::vector<v3s16> *unloaded_blocks=NULL);
/*
Unloads all blocks with a zero refCount().
- Saves modified blocks before unloading on MAPTYPE_SERVER.
+ Saves modified blocks before unloading if possible.
*/
void unloadUnreferencedBlocks(std::vector<v3s16> *unloaded_blocks=NULL);
// For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: "
virtual void PrintInfo(std::ostream &out);
- void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks,
- ServerEnvironment *env);
-
/*
Node metadata
These are basically coordinate wrappers to MapBlock
Variables
*/
- void transforming_liquid_add(v3s16 p);
-
bool isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes);
protected:
- friend class LuaVoxelManip;
-
IGameDef *m_gamedef;
std::set<MapEventReceiver*> m_event_receivers;
MapSector *m_sector_cache = nullptr;
v2s16 m_sector_cache_p;
- // Queued transforming water nodes
- UniqueQueue<v3s16> m_transforming_liquid;
-
// This stores the properties of the nodes on the map.
const NodeDefManager *m_nodedef;
+ // Can be implemented by child class
+ virtual void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) {}
+
bool determineAdditionalOcclusionCheck(const v3s16 &pos_camera,
const core::aabbox3d<s16> &block_bounds, v3s16 &check);
bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target,
float step, float stepfac, float start_offset, float end_offset,
u32 needed_count);
-
- private:
- f32 m_transforming_liquid_loop_count_multiplier = 1.0f;
- u32 m_unprocessed_count = 0;
- u64 m_inc_trending_up_start_time = 0; // milliseconds
- bool m_queue_size_timer_started = false;
};
/*
ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb);
~ServerMap();
- s32 mapType() const
- {
- return MAPTYPE_SERVER;
- }
-
/*
Get a sector from somewhere.
- Check memory
- Create blank filled with CONTENT_IGNORE
*/
- MapBlock *emergeBlock(v3s16 p, bool create_blank=true);
+ MapBlock *emergeBlock(v3s16 p, bool create_blank=true) override;
/*
Try to get a block.
bool isBlockInQueue(v3s16 pos);
+ void addNodeAndUpdate(v3s16 p, MapNode n,
+ std::map<v3s16, MapBlock*> &modified_blocks,
+ bool remove_metadata) override;
+
/*
Database functions
*/
static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf);
// Call these before and after saving of blocks
- void beginSave();
- void endSave();
+ void beginSave() override;
+ void endSave() override;
- void save(ModifiedState save_level);
+ void save(ModifiedState save_level) override;
void listAllLoadableBlocks(std::vector<v3s16> &dst);
- void listAllLoadedBlocks(std::vector<v3s16> &dst);
MapgenParams *getMapgenParams();
- bool saveBlock(MapBlock *block);
+ bool saveBlock(MapBlock *block) override;
static bool saveBlock(MapBlock *block, MapDatabase *db, int compression_level = -1);
MapBlock* loadBlock(v3s16 p);
// Database version
void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false);
- bool deleteBlock(v3s16 blockpos);
+ bool deleteBlock(v3s16 blockpos) override;
void updateVManip(v3s16 pos);
// For debug printing
- virtual void PrintInfo(std::ostream &out);
+ void PrintInfo(std::ostream &out) override;
bool isSavingEnabled(){ return m_map_saving_enabled; }
bool repairBlockLight(v3s16 blockpos,
std::map<v3s16, MapBlock *> *modified_blocks);
+ void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks,
+ ServerEnvironment *env);
+
+ void transforming_liquid_add(v3s16 p);
+
MapSettingsManager settings_mgr;
+ protected:
+
+ void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) override;
+
private:
+ friend class LuaVoxelManip;
+
// Emerge manager
EmergeManager *m_emerge;
std::set<v3s16> m_chunks_in_progress;
+ // Queued transforming water nodes
+ UniqueQueue<v3s16> m_transforming_liquid;
+ f32 m_transforming_liquid_loop_count_multiplier = 1.0f;
+ u32 m_unprocessed_count = 0;
+ u64 m_inc_trending_up_start_time = 0; // milliseconds
+ bool m_queue_size_timer_started = false;
+
/*
Metadata is re-written on disk only if this is true.
This is reset to false when written on disk.
MapDatabase *dbase = nullptr;
MapDatabase *dbase_ro = nullptr;
+ // Map metrics
+ MetricGaugePtr m_loaded_blocks_gauge;
MetricCounterPtr m_save_time_counter;
+ MetricCounterPtr m_save_count_counter;
};
void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
bool overwrite_generated = true);
+ /*
+ Creates a copy of this VManip including contents, the copy will not be
+ associated with a Map.
+ */
+ MMVManip *clone() const;
+
+ // Reassociates a copied VManip to a map
+ void reparent(Map *map);
+
+ // Is it impossible to call initialEmerge / blitBackAll?
+ inline bool isOrphan() const { return !m_map; }
+
bool m_is_dirty = false;
protected:
- Map *m_map;
+ MMVManip() {};
+
+ // may be null
+ Map *m_map = nullptr;
/*
key = blockpos
value = flags describing the block
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <iostream>
#include "client/client.h"
#include "util/base64.h"
#include "server.h"
#include "util/strfnd.h"
#include "client/clientevent.h"
+#include "client/content_cao.h"
#include "client/sound.h"
#include "network/clientopcodes.h"
#include "network/connection.h"
#include "tileanimation.h"
#include "gettext.h"
#include "skyparams.h"
+ #include <memory>
void Client::handleCommand_Deprecated(NetworkPacket* pkt)
{
m_access_denied_reason = "Unknown";
if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) {
- // 13/03/15 Legacy code from 0.4.12 and lesser but is still used
+ // Legacy code from 0.4.12 and older but is still used
// in some places of the server code
if (pkt->getSize() >= 2) {
std::wstring wide_reason;
if (pkt->getSize() < 1)
return;
- u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA;
+ u8 denyCode;
*pkt >> denyCode;
+
if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN ||
denyCode == SERVER_ACCESSDENIED_CRASH) {
*pkt >> m_access_denied_reason;
- if (m_access_denied_reason.empty()) {
+ if (m_access_denied_reason.empty())
m_access_denied_reason = accessDeniedStrings[denyCode];
- }
u8 reconnect;
*pkt >> reconnect;
m_access_denied_reconnect = reconnect & 1;
// Until then (which may be never), this is outside
// of the defined protocol.
*pkt >> m_access_denied_reason;
- if (m_access_denied_reason.empty()) {
+ if (m_access_denied_reason.empty())
m_access_denied_reason = "Unknown";
- }
}
}
}
*/
+ LocalPlayer *player = m_env.getLocalPlayer();
+ bool try_reattach = player && player->isWaitingForReattach();
+
try {
u8 type;
u16 removed_count, added_count, id;
for (u16 i = 0; i < added_count; i++) {
*pkt >> id >> type;
m_env.addActiveObject(id, type, pkt->readLongString());
+ if (try_reattach)
+ player->tryReattach(id);
}
} catch (PacketError &e) {
infostream << "handleCommand_ActiveObjectRemoveAdd: " << e.what()
LocalPlayer *player = m_env.getLocalPlayer();
assert(player != NULL);
+ if ((player->getCAO() && player->getCAO()->getParentId()) || player->isWaitingForReattach())
+ return;
+
v3f pos;
f32 pitch, yaw;
*pkt >> pos >> pitch >> yaw;
- player->setPosition(pos);
+ player->setLegitPosition(pos);
infostream << "Client got TOCLIENT_MOVE_PLAYER"
<< " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")"
it would just force the pitch and yaw values to whatever
the camera points to.
*/
+
+ if (g_settings->getBool("no_force_rotate"))
+ return;
+
ClientEvent *event = new ClientEvent();
event->type = CE_PLAYER_FORCE_MOVE;
event->player_force_move.pitch = pitch;
*pkt >> ephemeral;
} catch (PacketError &e) {};
+ SimpleSoundSpec sound_spec(name, gain, fade, pitch);
+
+ if (m_mods_loaded && m_script->on_play_sound(sound_spec))
+ return;
+
// Start playing
int client_id = -1;
switch(type) {
m_privileges.insert(priv);
infostream << priv << " ";
}
-
- // Enable basic_debug on server versions before it was added
- if (m_proto_ver < 40)
- m_privileges.insert("basic_debug");
-
infostream << std::endl;
}
event->type = CE_SPAWN_PARTICLE;
event->spawn_particle = new ParticleParameters(p);
+ if (m_mods_loaded && m_script->on_spawn_particle(*event->spawn_particle))
+ return;
+
m_client_event_queue.push(event);
}
player->hud_flags &= ~mask;
player->hud_flags |= flags;
+ if (g_settings->getBool("hud_flags_bypass"))
+ player->hud_flags = HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE |
+ HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE |
+ HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE |
+ HUD_FLAG_MINIMAP_RADAR_VISIBLE;
+
m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE);
bool m_minimap_radar_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE);
} catch (...) {}
// Use default skybox settings:
- SkyboxDefaults sky_defaults;
- SunParams sun = sky_defaults.getSunDefaults();
- MoonParams moon = sky_defaults.getMoonDefaults();
- StarParams stars = sky_defaults.getStarDefaults();
+ SunParams sun = SkyboxDefaults::getSunDefaults();
+ MoonParams moon = SkyboxDefaults::getMoonDefaults();
+ StarParams stars = SkyboxDefaults::getStarDefaults();
// Fix for "regular" skies, as color isn't kept:
if (skybox.type == "regular") {
- skybox.sky_color = sky_defaults.getSkyColorDefaults();
+ skybox.sky_color = SkyboxDefaults::getSkyColorDefaults();
skybox.fog_tint_type = "default";
skybox.fog_moon_tint = video::SColor(255, 255, 255, 255);
skybox.fog_sun_tint = video::SColor(255, 255, 255, 255);
- }
- else {
+ } else {
sun.visible = false;
sun.sunrise_visible = false;
moon.visible = false;
*pkt >> player->local_animations[2];
*pkt >> player->local_animations[3];
*pkt >> player->local_animation_speed;
+
+ player->last_animation = -1;
}
void Client::handleCommand_EyeOffset(NetworkPacket* pkt)
void Client::handleCommand_PlayerSpeed(NetworkPacket *pkt)
{
+ if (g_settings->getBool("antiknockback"))
+ return;
v3f added_vel;
*pkt >> added_vel;
m_media_pushed_files.insert(filename);
// create a downloader for this file
- auto downloader = new SingleMediaDownloader(cached);
+ auto downloader(std::make_shared<SingleMediaDownloader>(cached));
m_pending_media_downloads.emplace_back(token, downloader);
downloader->addFile(filename, raw_hash);
for (const auto &baseurl : m_remote_media_servers)
if (m_minimap)
m_minimap->setModeIndex(mode);
}
+
+ void Client::handleCommand_SetLighting(NetworkPacket *pkt)
+ {
+ Lighting& lighting = m_env.getLocalPlayer()->getLighting();
+
+ if (pkt->getRemainingBytes() >= 4)
+ *pkt >> lighting.shadow_intensity;
+ }
#include "nameidmapping.h"
#include "util/numeric.h"
#include "util/serialize.h"
+ #include "util/string.h"
#include "exceptions.h"
#include "debug.h"
#include "gamedef.h"
u8 version = 6;
writeU8(os, version);
- os << serializeString16(name);
+ if (protocol_version > 39) {
+ os << serializeString16(name);
+ } else {
+ // Before f018737, TextureSource::getTextureAverageColor did not handle
+ // missing textures. "[png" can be used as base texture, but is not known
+ // on older clients. Hence use "blank.png" to avoid this problem.
+ // To be forward-compatible with future base textures/modifiers,
+ // we apply the same prefix to any texture beginning with [,
+ // except for the ones that are supported on older clients.
+ bool pass_through = true;
+
+ if (!name.empty() && name[0] == '[') {
+ pass_through = str_starts_with(name, "[combine:") ||
+ str_starts_with(name, "[inventorycube{") ||
+ str_starts_with(name, "[lowpart:");
+ }
+
+ if (pass_through)
+ os << serializeString16(name);
+ else
+ os << serializeString16("blank.png^" + name);
+ }
animation.serialize(os, version);
bool has_scale = scale > 0;
u16 flags = 0;
Cached stuff
*/
#ifndef SERVER
- solidness = 2;
+ solidness = 0;
visual_solidness = 0;
backface_culling = true;
palette_name = "";
palette = NULL;
node_dig_prediction = "air";
+ move_resistance = 0;
+ liquid_move_physics = false;
}
void ContentFeatures::setAlphaFromLegacy(u8 legacy_alpha)
writeU16(os, groups.size());
for (const auto &group : groups) {
os << serializeString16(group.first);
- writeS16(os, group.second);
+ if (protocol_version < 41 && group.first.compare("bouncy") == 0) {
+ // Old clients may choke on negative bouncy value
+ writeS16(os, abs(group.second));
+ } else {
+ writeS16(os, group.second);
+ }
}
writeU8(os, param_type);
writeU8(os, param_type_2);
writeU32(os, damage_per_second);
// liquid
- writeU8(os, liquid_type);
+ LiquidType liquid_type_bc = liquid_type;
+ if (protocol_version <= 39) {
+ // Since commit 7f25823, liquid drawtypes can be used even with LIQUID_NONE
+ // solution: force liquid type accordingly to accepted values
+ if (drawtype == NDT_LIQUID)
+ liquid_type_bc = LIQUID_SOURCE;
+ else if (drawtype == NDT_FLOWINGLIQUID)
+ liquid_type_bc = LIQUID_FLOWING;
+ }
+ writeU8(os, liquid_type_bc);
os << serializeString16(liquid_alternative_flowing);
os << serializeString16(liquid_alternative_source);
writeU8(os, liquid_viscosity);
writeU8(os, legacy_facedir_simple);
writeU8(os, legacy_wallmounted);
+ // new attributes
os << serializeString16(node_dig_prediction);
writeU8(os, leveled_max);
writeU8(os, alpha);
+ writeU8(os, move_resistance);
+ writeU8(os, liquid_move_physics);
}
void ContentFeatures::deSerialize(std::istream &is)
// liquid
liquid_type = (enum LiquidType) readU8(is);
+ liquid_move_physics = liquid_type != LIQUID_NONE;
liquid_alternative_flowing = deSerializeString16(is);
liquid_alternative_source = deSerializeString16(is);
liquid_viscosity = readU8(is);
+ move_resistance = liquid_viscosity; // set default move_resistance
liquid_renewable = readU8(is);
liquid_range = readU8(is);
drowning = readU8(is);
if (is.eof())
throw SerializationError("");
alpha = static_cast<enum AlphaMode>(tmp);
+
+ tmp = readU8(is);
+ if (is.eof())
+ throw SerializationError("");
+ move_resistance = tmp;
+
+ tmp = readU8(is);
+ if (is.eof())
+ throw SerializationError("");
+ liquid_move_physics = tmp;
} catch(SerializationError &e) {};
}
bool has_scale = tiledef.scale > 0;
bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE ||
(tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale);
- if (use_autoscale) {
+ if (use_autoscale && layer->texture) {
auto texture_size = layer->texture->getOriginalSize();
float base_size = tsettings.node_texture_size;
float size = std::fmin(texture_size.Width, texture_size.Height);
// Animation parameters
int frame_count = 1;
if (layer->material_flags & MATERIAL_FLAG_ANIMATION) {
+ assert(layer->texture);
int frame_length_ms;
tiledef.animation.determineParams(layer->texture->getOriginalSize(),
&frame_count, &frame_length_ms, NULL);
if (frame_count == 1) {
layer->material_flags &= ~MATERIAL_FLAG_ANIMATION;
} else {
- std::ostringstream os(std::ios::binary);
- if (!layer->frames) {
+ assert(layer->texture);
+ if (!layer->frames)
layer->frames = new std::vector<FrameSpec>();
- }
layer->frames->resize(frame_count);
+ std::ostringstream os(std::ios::binary);
for (int i = 0; i < frame_count; i++) {
-
FrameSpec frame;
os.str("");
TileDef tdef[6];
for (u32 j = 0; j < 6; j++) {
tdef[j] = tiledef[j];
- if (tdef[j].name.empty())
- tdef[j].name = "unknown_node.png";
+ if (tdef[j].name.empty()) {
+ tdef[j].name = "no_texture.png";
+ tdef[j].backface_culling = false;
+ }
}
// also the overlay tiles
TileDef tdef_overlay[6];
tdef_overlay[j] = tiledef_overlay[j];
// also the special tiles
TileDef tdef_spec[6];
- for (u32 j = 0; j < CF_SPECIAL_COUNT; j++)
+ for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) {
tdef_spec[j] = tiledef_special[j];
+ }
bool is_liquid = false;
{
ContentFeatures f;
f.name = "unknown";
+ TileDef unknownTile;
+ unknownTile.name = "unknown_node.png";
+ for (int t = 0; t < 6; t++)
+ f.tiledef[t] = unknownTile;
// Insert directly into containers
content_t c = CONTENT_UNKNOWN;
m_content_features[c] = f;
// IWritableNodeDefManager
-content_t NodeDefManager::set(const std::string &name, const ContentFeatures &def)
+content_t NodeDefManager::set(const std::string &name, const ContentFeatures &d)
{
+ ContentFeatures def = d;
+
// Pre-conditions
assert(name != "");
assert(name != "ignore");
assert(name == def.name);
content_t id = CONTENT_IGNORE;
- if (!m_name_id_mapping.getId(name, id)) { // ignore aliases
+
+ if (m_name_id_mapping.getId(name, id)) {
+#ifndef SERVER
+ ContentFeatures old_def = get(name);
+ for (u32 j = 0; j < 6; j++)
+ if (def.tiledef[j].name.empty())
+ def.tiledef[j] = old_def.tiledef[j];
+ for (u32 j = 0; j < 6; j++)
+ if (def.tiledef_overlay[j].name.empty())
+ def.tiledef_overlay[j] = old_def.tiledef_overlay[j];
+ for (u32 j = 0; j < CF_SPECIAL_COUNT; j++)
+ if (def.tiledef_special[j].name.empty())
+ def.tiledef_special[j] = old_def.tiledef_special[j];
+#endif
+ } else {
// Get new id
id = allocateId();
if (id == CONTENT_IGNORE) {
#include "player.h"
+ #include <cmath>
#include "threading/mutex_auto_lock.h"
#include "util/numeric.h"
#include "hud.h"
HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE |
HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE |
HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE |
- HUD_FLAG_MINIMAP_RADAR_VISIBLE;
+ HUD_FLAG_MINIMAP_RADAR_VISIBLE | HUD_FLAG_BASIC_DEBUG;
hud_hotbar_itemcount = HUD_HOTBAR_ITEMCOUNT_DEFAULT;
}
}
+ #ifndef SERVER
+
+ u32 PlayerControl::getKeysPressed() const
+ {
+ u32 keypress_bits =
+ ( (u32)(jump & 1) << 4) |
+ ( (u32)(aux1 & 1) << 5) |
+ ( (u32)(sneak & 1) << 6) |
+ ( (u32)(dig & 1) << 7) |
+ ( (u32)(place & 1) << 8) |
+ ( (u32)(zoom & 1) << 9)
+ ;
+
+ // If any direction keys are pressed pass those through
+ if (direction_keys != 0)
+ {
+ keypress_bits |= direction_keys;
+ }
+ // Otherwise set direction keys based on joystick movement (for mod compatibility)
+ else if (isMoving())
+ {
+ float abs_d;
+
+ // (absolute value indicates forward / backward)
+ abs_d = abs(movement_direction);
+ if (abs_d < 3.0f / 8.0f * M_PI)
+ keypress_bits |= (u32)1; // Forward
+ if (abs_d > 5.0f / 8.0f * M_PI)
+ keypress_bits |= (u32)1 << 1; // Backward
+
+ // rotate entire coordinate system by 90 degree
+ abs_d = movement_direction + M_PI_2;
+ if (abs_d >= M_PI)
+ abs_d -= 2 * M_PI;
+ abs_d = abs(abs_d);
+ // (value now indicates left / right)
+ if (abs_d < 3.0f / 8.0f * M_PI)
+ keypress_bits |= (u32)1 << 2; // Left
+ if (abs_d > 5.0f / 8.0f * M_PI)
+ keypress_bits |= (u32)1 << 3; // Right
+ }
+
+ return keypress_bits;
+ }
+
+ #endif
+
+ void PlayerControl::unpackKeysPressed(u32 keypress_bits)
+ {
+ direction_keys = keypress_bits & 0xf;
+ jump = keypress_bits & (1 << 4);
+ aux1 = keypress_bits & (1 << 5);
+ sneak = keypress_bits & (1 << 6);
+ dig = keypress_bits & (1 << 7);
+ place = keypress_bits & (1 << 8);
+ zoom = keypress_bits & (1 << 9);
+ }
+
void PlayerSettings::readGlobalSettings()
{
- free_move = g_settings->getBool("free_move");
+ freecam = g_settings->getBool("freecam");
+ free_move = g_settings->getBool("free_move") || freecam;
pitch_move = g_settings->getBool("pitch_move");
- fast_move = g_settings->getBool("fast_move");
+ fast_move = g_settings->getBool("fast_move") || freecam;
continuous_forward = g_settings->getBool("continuous_forward");
- always_fly_fast = g_settings->getBool("always_fly_fast");
+ always_fly_fast = g_settings->getBool("always_fly_fast") || freecam;
aux1_descends = g_settings->getBool("aux1_descends");
- noclip = g_settings->getBool("noclip");
+ noclip = g_settings->getBool("noclip") || freecam;
autojump = g_settings->getBool("autojump");
}
PlayerControl() = default;
PlayerControl(
- bool a_jump,
- bool a_aux1,
- bool a_sneak,
+ bool a_up, bool a_down, bool a_left, bool a_right,
+ bool a_jump, bool a_aux1, bool a_sneak,
bool a_zoom,
- bool a_dig,
- bool a_place,
- float a_pitch,
- float a_yaw,
- float a_movement_speed,
- float a_movement_direction
+ bool a_dig, bool a_place,
+ float a_pitch, float a_yaw,
+ float a_movement_speed, float a_movement_direction
)
{
+ // Encode direction keys into a single value so nobody uses it accidentally
+ // as movement_{speed,direction} is supposed to be the source of truth.
+ direction_keys = (a_up&1) | ((a_down&1) << 1) |
+ ((a_left&1) << 2) | ((a_right&1) << 3);
jump = a_jump;
aux1 = a_aux1;
sneak = a_sneak;
movement_speed = a_movement_speed;
movement_direction = a_movement_direction;
}
+
+ #ifndef SERVER
+ // For client use
+ u32 getKeysPressed() const;
+ inline bool isMoving() const { return movement_speed > 0.001f; }
+ #endif
+
+ // For server use
+ void unpackKeysPressed(u32 keypress_bits);
+
+ u8 direction_keys = 0;
bool jump = false;
bool aux1 = false;
bool sneak = false;
bool zoom = false;
bool dig = false;
bool place = false;
+ // Note: These four are NOT available on the server
float pitch = 0.0f;
float yaw = 0.0f;
- // Note: These two are NOT available on the server
float movement_speed = 0.0f;
float movement_direction = 0.0f;
};
bool free_move = false;
bool pitch_move = false;
bool fast_move = false;
+ bool freecam = false;
bool continuous_forward = false;
bool always_fly_fast = false;
bool aux1_descends = false;
bool noclip = false;
bool autojump = false;
- const std::string setting_names[8] = {
- "free_move", "pitch_move", "fast_move", "continuous_forward", "always_fly_fast",
+ const std::string setting_names[9] = {
+ "free_move", "pitch_move", "fast_move", "freecam", "continuous_forward", "always_fly_fast",
"aux1_descends", "noclip", "autojump"
};
void readGlobalSettings();
return m_fov_override_spec;
}
- u32 keyPressed = 0;
-
HudElement* getHud(u32 id);
u32 addHud(HudElement* hud);
HudElement* removeHud(u32 id);
#include "object_properties.h"
#include "collision.h"
#include "cpp_api/s_node.h"
+#include "lua_api/l_clientobject.h"
#include "lua_api/l_object.h"
#include "lua_api/l_item.h"
#include "common/c_internal.h"
}
push_groups(L, i.groups);
lua_setfield(L, -2, "groups");
+ lua_newtable(L);
push_soundspec(L, i.sound_place);
- lua_setfield(L, -2, "sound_place");
+ lua_setfield(L, -2, "place");
push_soundspec(L, i.sound_place_failed);
- lua_setfield(L, -2, "sound_place_failed");
+ lua_setfield(L, -2, "place_failed");
+ lua_setfield(L, -2, "sounds");
lua_pushstring(L, i.node_placement_prediction.c_str());
lua_setfield(L, -2, "node_placement_prediction");
}
if (getintfield(L, -1, "hp_max", hp_max)) {
prop->hp_max = (u16)rangelim(hp_max, 0, U16_MAX);
- if (prop->hp_max < sao->getHP()) {
+ if (sao && prop->hp_max < sao->getHP()) {
PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP);
sao->setHP(prop->hp_max, reason);
}
}
if (getintfield(L, -1, "breath_max", prop->breath_max)) {
- if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
+ if (sao && sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
PlayerSAO *player = (PlayerSAO *)sao;
if (prop->breath_max < player->getBreath())
player->setBreath(prop->breath_max);
return tiledef;
}
+/******************************************************************************/
+void push_tiledef(lua_State *L, TileDef tiledef)
+{
+ lua_newtable(L);
+ setstringfield(L, -1, "name", tiledef.name);
+ setboolfield(L, -1, "backface_culling", tiledef.backface_culling);
+ setboolfield(L, -1, "tileable_horizontal", tiledef.tileable_horizontal);
+ setboolfield(L, -1, "tileable_vertical", tiledef.tileable_vertical);
+ std::string align_style;
+ switch (tiledef.align_style) {
+ case ALIGN_STYLE_USER_DEFINED:
+ align_style = "user";
+ break;
+ case ALIGN_STYLE_WORLD:
+ align_style = "world";
+ break;
+ default:
+ align_style = "node";
+ }
+ setstringfield(L, -1, "align_style", align_style);
+ setintfield(L, -1, "scale", tiledef.scale);
+ if (tiledef.has_color) {
+ push_ARGB8(L, tiledef.color);
+ lua_setfield(L, -2, "color");
+ }
+ push_animation_definition(L, tiledef.animation);
+ lua_setfield(L, -2, "animation");
+}
+
/******************************************************************************/
void read_content_features(lua_State *L, ContentFeatures &f, int index)
{
// the slowest possible
f.liquid_viscosity = getintfield_default(L, index,
"liquid_viscosity", f.liquid_viscosity);
+ // If move_resistance is not set explicitly,
+ // move_resistance is equal to liquid_viscosity
+ f.move_resistance = f.liquid_viscosity;
f.liquid_range = getintfield_default(L, index,
"liquid_range", f.liquid_range);
f.leveled = getintfield_default(L, index, "leveled", f.leveled);
getstringfield(L, index, "node_dig_prediction",
f.node_dig_prediction);
+ // How much the node slows down players, ranging from 1 to 7,
+ // the higher, the slower.
+ f.move_resistance = getintfield_default(L, index,
+ "move_resistance", f.move_resistance);
+
+ // Whether e.g. players in this node will have liquid movement physics
+ lua_getfield(L, index, "liquid_move_physics");
+ if(lua_isboolean(L, -1)) {
+ f.liquid_move_physics = lua_toboolean(L, -1);
+ } else if(lua_isnil(L, -1)) {
+ f.liquid_move_physics = f.liquid_type != LIQUID_NONE;
+ } else {
+ errorstream << "Field \"liquid_move_physics\": Invalid type!" << std::endl;
+ }
+ lua_pop(L, 1);
}
void push_content_features(lua_State *L, const ContentFeatures &c)
std::string drawtype(ScriptApiNode::es_DrawType[(int)c.drawtype].str);
std::string liquid_type(ScriptApiNode::es_LiquidType[(int)c.liquid_type].str);
- /* Missing "tiles" because I don't see a usecase (at least not yet). */
+ lua_newtable(L);
+
+ // tiles
+ lua_newtable(L);
+ for (int i = 0; i < 6; i++) {
+ push_tiledef(L, c.tiledef[i]);
+ lua_rawseti(L, -2, i + 1);
+ }
+ lua_setfield(L, -2, "tiles");
+ // overlay_tiles
lua_newtable(L);
+ for (int i = 0; i < 6; i++) {
+ push_tiledef(L, c.tiledef_overlay[i]);
+ lua_rawseti(L, -2, i + 1);
+ }
+ lua_setfield(L, -2, "overlay_tiles");
+
+ // special_tiles
+ lua_newtable(L);
+ for (int i = 0; i < CF_SPECIAL_COUNT; i++) {
+ push_tiledef(L, c.tiledef_special[i]);
+ lua_rawseti(L, -2, i + 1);
+ }
+ lua_setfield(L, -2, "special_tiles");
+
lua_pushboolean(L, c.has_on_construct);
lua_setfield(L, -2, "has_on_construct");
lua_pushboolean(L, c.has_on_destruct);
lua_setfield(L, -2, "collision_box");
lua_newtable(L);
push_soundspec(L, c.sound_footstep);
- lua_setfield(L, -2, "sound_footstep");
+ lua_setfield(L, -2, "footstep");
push_soundspec(L, c.sound_dig);
- lua_setfield(L, -2, "sound_dig");
+ lua_setfield(L, -2, "dig");
push_soundspec(L, c.sound_dug);
- lua_setfield(L, -2, "sound_dug");
+ lua_setfield(L, -2, "dug");
lua_setfield(L, -2, "sounds");
lua_pushboolean(L, c.legacy_facedir_simple);
lua_setfield(L, -2, "legacy_facedir_simple");
lua_setfield(L, -2, "legacy_wallmounted");
lua_pushstring(L, c.node_dig_prediction.c_str());
lua_setfield(L, -2, "node_dig_prediction");
+ lua_pushnumber(L, c.move_resistance);
+ lua_setfield(L, -2, "move_resistance");
+ lua_pushboolean(L, c.liquid_move_physics);
+ lua_setfield(L, -2, "liquid_move_physics");
}
/******************************************************************************/
}
/******************************************************************************/
- void push_inventory(lua_State *L, Inventory *inventory)
+ void push_inventory_list(lua_State *L, const InventoryList &invlist)
{
- if (! inventory)
- throw SerializationError("Attempt to push nonexistant inventory");
- std::vector<const InventoryList*> lists = inventory->getLists();
- std::vector<const InventoryList*>::iterator iter = lists.begin();
+ push_items(L, invlist.getItems());
+ }
+
+ /******************************************************************************/
+ void push_inventory_lists(lua_State *L, const Inventory &inv)
+ {
+ const auto &lists = inv.getLists();
lua_createtable(L, 0, lists.size());
- for (; iter != lists.end(); iter++) {
- const char* name = (*iter)->getName().c_str();
- lua_pushstring(L, name);
- push_inventory_list(L, inventory, name);
+ for(const InventoryList *list : lists) {
+ const std::string &name = list->getName();
+ lua_pushlstring(L, name.c_str(), name.size());
+ push_inventory_list(L, *list);
lua_rawset(L, -3);
}
}
/******************************************************************************/
void read_inventory_list(lua_State *L, int tableindex,
- Inventory *inv, const char *name, Server* srv, int forcesize)
+ Inventory *inv, const char *name, IGameDef *gdef, int forcesize)
{
if(tableindex < 0)
tableindex = lua_gettop(L) + 1 + tableindex;
}
// Get Lua-specified items to insert into the list
- std::vector<ItemStack> items = read_items(L, tableindex,srv);
+ std::vector<ItemStack> items = read_items(L, tableindex, gdef);
size_t listsize = (forcesize >= 0) ? forcesize : items.size();
// Create or resize/clear list
}
}
- void push_inventory_list(lua_State *L, Inventory *inv, const char *name)
- {
- InventoryList *invlist = inv->getList(name);
- if(invlist == NULL){
- lua_pushnil(L);
- return;
- }
- std::vector<ItemStack> items;
- for(u32 i=0; i<invlist->getSize(); i++)
- items.push_back(invlist->getItem(i));
- push_items(L, items);
- }
-
/******************************************************************************/
struct TileAnimationParams read_animation_definition(lua_State *L, int index)
{
return anim;
}
+void push_animation_definition(lua_State *L, struct TileAnimationParams anim)
+{
+ switch (anim.type) {
+ case TAT_NONE:
+ lua_pushnil(L);
+ break;
+ case TAT_VERTICAL_FRAMES:
+ lua_newtable(L);
+ setstringfield(L, -1, "type", "vertical_frames");
+ setfloatfield(L, -1, "aspect_w", anim.vertical_frames.aspect_w);
+ setfloatfield(L, -1, "aspect_h", anim.vertical_frames.aspect_h);
+ setfloatfield(L, -1, "length", anim.vertical_frames.length);
+ break;
+ case TAT_SHEET_2D:
+ lua_newtable(L);
+ setstringfield(L, -1, "type", "sheet_2d");
+ setintfield(L, -1, "frames_w", anim.sheet_2d.frames_w);
+ setintfield(L, -1, "frames_h", anim.sheet_2d.frames_h);
+ setintfield(L, -1, "frame_length", anim.sheet_2d.frame_length);
+ break;
+ }
+}
+
/******************************************************************************/
ToolCapabilities read_tool_capabilities(
lua_State *L, int table)
}
/******************************************************************************/
- std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv)
+ std::vector<ItemStack> read_items(lua_State *L, int index, IGameDef *gdef)
{
if(index < 0)
index = lua_gettop(L) + 1 + index;
if (items.size() < (u32) key) {
items.resize(key);
}
- items[key - 1] = read_item(L, -1, srv->idef());
+ items[key - 1] = read_item(L, -1, gdef->idef());
lua_pop(L, 1);
}
return items;
void push_noiseparams(lua_State *L, NoiseParams *np)
{
lua_newtable(L);
- push_float_string(L, np->offset);
- lua_setfield(L, -2, "offset");
- push_float_string(L, np->scale);
- lua_setfield(L, -2, "scale");
- push_float_string(L, np->persist);
- lua_setfield(L, -2, "persistence");
- push_float_string(L, np->lacunarity);
- lua_setfield(L, -2, "lacunarity");
- lua_pushnumber(L, np->seed);
- lua_setfield(L, -2, "seed");
- lua_pushnumber(L, np->octaves);
- lua_setfield(L, -2, "octaves");
+ setfloatfield(L, -1, "offset", np->offset);
+ setfloatfield(L, -1, "scale", np->scale);
+ setfloatfield(L, -1, "persist", np->persist);
+ setfloatfield(L, -1, "persistence", np->persist);
+ setfloatfield(L, -1, "lacunarity", np->lacunarity);
+ setintfield( L, -1, "seed", np->seed);
+ setintfield( L, -1, "octaves", np->octaves);
push_flags_string(L, flagdesc_noiseparams, np->flags,
np->flags);
lua_setfield(L, -2, "flags");
- push_v3_float_string(L, np->spread);
+ push_v3f(L, np->spread);
lua_setfield(L, -2, "spread");
}
} else if (pointed.type == POINTEDTHING_OBJECT) {
lua_pushstring(L, "object");
lua_setfield(L, -2, "type");
-
- if (csm) {
- lua_pushinteger(L, pointed.object_id);
- lua_setfield(L, -2, "id");
- } else {
- push_objectRef(L, pointed.object_id);
- lua_setfield(L, -2, "ref");
- }
+ push_objectRef(L, pointed.object_id);
+ lua_setfield(L, -2, "ref");
} else {
lua_pushstring(L, "nothing");
lua_setfield(L, -2, "type");
lua_pushnumber(L, elem->number);
lua_setfield(L, -2, "number");
+ if (elem->type == HUD_ELEM_WAYPOINT) {
+ // waypoints reuse the item field to store precision, precision = item - 1
+ lua_pushnumber(L, elem->item - 1);
+ lua_setfield(L, -2, "precision");
+ }
+ // push the item field for waypoints as well for backwards compatibility
lua_pushnumber(L, elem->item);
lua_setfield(L, -2, "item");
lua_setfield(L, -2, "collisions");
/**/
}
+
+/******************************************************************************/
+void push_physics_override(lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move)
+{
+ lua_createtable(L, 0, 6);
+
+ lua_pushnumber(L, speed);
+ lua_setfield(L, -2, "speed");
+
+ lua_pushnumber(L, jump);
+ lua_setfield(L, -2, "jump");
+
+ lua_pushnumber(L, gravity);
+ lua_setfield(L, -2, "gravity");
+
+ lua_pushboolean(L, sneak);
+ lua_setfield(L, -2, "sneak");
+
+ lua_pushboolean(L, sneak_glitch);
+ lua_setfield(L, -2, "sneak_glitch");
+
+ lua_pushboolean(L, new_move);
+ lua_setfield(L, -2, "new_move");
+}
struct SimpleSoundSpec;
struct ServerSoundParams;
class Inventory;
+ class InventoryList;
struct NodeBox;
struct ContentFeatures;
struct TileDef;
- class Server;
+ class IGameDef;
struct DigParams;
struct HitParams;
struct EnumString;
ItemStack read_item (lua_State *L, int index, IItemDefManager *idef);
struct TileAnimationParams read_animation_definition(lua_State *L, int index);
+void push_animation_definition(lua_State *L, struct TileAnimationParams anim);
ToolCapabilities read_tool_capabilities (lua_State *L, int table);
void push_tool_capabilities (lua_State *L,
void push_object_properties (lua_State *L,
ObjectProperties *prop);
+void push_inventory (lua_State *L,
+ Inventory *inventory);
+
void push_inventory_list (lua_State *L,
- Inventory *inv,
- const char *name);
+ const InventoryList &invlist);
+ void push_inventory_lists (lua_State *L,
+ const Inventory &inv);
void read_inventory_list (lua_State *L, int tableindex,
Inventory *inv, const char *name,
- Server *srv, int forcesize=-1);
+ IGameDef *gdef, int forcesize=-1);
MapNode readnode (lua_State *L, int index,
const NodeDefManager *ndef);
std::vector<ItemStack> read_items (lua_State *L,
int index,
- Server* srv);
+ IGameDef* gdef);
void push_soundspec (lua_State *L,
const SimpleSoundSpec &spec);
bool read_hud_change (lua_State *L, HudElementStat &stat, HudElement *elem, void **value);
void push_collision_move_result(lua_State *L, const collisionMoveResult &res);
+
+void push_physics_override (lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move);
#include "cpp_api/s_internal.h"
#include "cpp_api/s_security.h"
#include "lua_api/l_object.h"
+#include "lua_api/l_clientobject.h"
#include "common/c_converter.h"
#include "server/player_sao.h"
#include "filesys.h"
#include "lualib.h"
#if USE_LUAJIT
#include "luajit.h"
+ #else
+ #include "bit.h"
#endif
}
lua_atpanic(m_luastack, &luaPanic);
- if (m_type == ScriptingType::Client)
+ /*if (m_type == ScriptingType::Client)
clientOpenLibs(m_luastack);
- else
+ else*/
luaL_openlibs(m_luastack);
+ // Load bit library
+ lua_pushcfunction(m_luastack, luaopen_bit);
+ lua_pushstring(m_luastack, LUA_BITLIBNAME);
+ lua_call(m_luastack, 1, 0);
+
// Make the ScriptApiBase* accessible to ModApiBase
#if INDIRECT_SCRIPTAPI_RIDX
*(void **)(lua_newuserdata(m_luastack, sizeof(void *))) = this;
lua_newtable(m_luastack);
lua_setglobal(m_luastack, "core");
+ // vector.metatable is stored in the registry for quick access from C++.
+ lua_newtable(m_luastack);
+ lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE);
+ lua_newtable(m_luastack);
+ lua_rawgeti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE);
+ lua_setfield(m_luastack, -2, "metatable");
+ lua_setglobal(m_luastack, "vector");
+
if (m_type == ScriptingType::Client)
lua_pushstring(m_luastack, "/");
else
* since we lose control over the ref and the contained pointer.
*/
-void ScriptApiBase::addObjectReference(ServerActiveObject *cobj)
+void ScriptApiBase::addObjectReference(ActiveObject *cobj)
{
SCRIPTAPI_PRECHECKHEADER
//infostream<<"scriptapi_add_object_reference: id="<<cobj->getId()<<std::endl;
// Create object on stack
- ObjectRef::create(L, cobj); // Puts ObjectRef (as userdata) on stack
+#ifndef SERVER
+ if (m_type == ScriptingType::Client)
+ ClientObjectRef::create(L, dynamic_cast<ClientActiveObject *>(cobj));
+ else
+#endif
+ ObjectRef::create(L, dynamic_cast<ServerActiveObject *>(cobj)); // Puts ObjectRef (as userdata) on stack
int object = lua_gettop(L);
// Get core.object_refs table
lua_settable(L, objectstable);
}
-void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj)
+void ScriptApiBase::removeObjectReference(ActiveObject *cobj)
{
SCRIPTAPI_PRECHECKHEADER
//infostream<<"scriptapi_rm_object_reference: id="<<cobj->getId()<<std::endl;
lua_pushnumber(L, cobj->getId()); // Push id
lua_gettable(L, objectstable);
// Set object reference to NULL
- ObjectRef::set_null(L);
+#ifndef SERVER
+ if (m_type == ScriptingType::Client)
+ ClientObjectRef::set_null(L);
+ else
+#endif
+ ObjectRef::set_null(L);
lua_pop(L, 1); // pop object
// Set object_refs[id] = nil
<< ", this is probably a bug." << std::endl;
}
}
-
void ScriptApiBase::pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason &reason)
{
if (reason.hasLuaReference())
class Server;
#ifndef SERVER
class Client;
+class Game;
#endif
class IGameDef;
class Environment;
class GUIEngine;
+class ActiveObject;
class ServerActiveObject;
struct PlayerHPChangeReason;
RunCallbacksMode mode, const char *fxn);
/* object */
- void addObjectReference(ServerActiveObject *cobj);
- void removeObjectReference(ServerActiveObject *cobj);
+ void addObjectReference(ActiveObject *cobj);
+ void removeObjectReference(ActiveObject *cobj);
IGameDef *getGameDef() { return m_gamedef; }
Server* getServer();
ScriptingType getType() { return m_type; }
#ifndef SERVER
Client* getClient();
+ Game *getGame() { return m_game; }
#endif
// IMPORTANT: these cannot be used for any security-related uses, they exist
friend class ModApiEnvMod;
friend class LuaVoxelManip;
+ /*
+ Subtle edge case with coroutines: If for whatever reason you have a
+ method in a subclass that's called from existing lua_CFunction
+ (any of the l_*.cpp files) then make it static and take the lua_State*
+ as an argument. This is REQUIRED because getStack() will not return the
+ correct state if called inside coroutines.
+
+ Also note that src/script/common/ is the better place for such helpers.
+ */
lua_State* getStack()
{ return m_luastack; }
void stackDump(std::ostream &o);
void setGameDef(IGameDef* gamedef) { m_gamedef = gamedef; }
+#ifndef SERVER
+ void setGame(Game *game) { m_game = game; }
+#endif
Environment* getEnv() { return m_environment; }
void setEnv(Environment* env) { m_environment = env; }
lua_State *m_luastack = nullptr;
IGameDef *m_gamedef = nullptr;
+#ifndef SERVER
+ Game *m_game = nullptr;
+#endif
Environment *m_environment = nullptr;
#ifndef SERVER
GUIEngine *m_guiengine = nullptr;
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "nodedef.h"
+#include "itemdef.h"
#include "s_client.h"
#include "s_internal.h"
#include "client/client.h"
#include "common/c_converter.h"
#include "common/c_content.h"
+#include "lua_api/l_clientobject.h"
#include "s_item.h"
void ScriptApiClient::on_mods_loaded()
const NodeDefManager *ndef = getClient()->ndef();
- // Get core.registered_on_punchgnode
+ // Get core.registered_on_punchnode
lua_getglobal(L, "core");
lua_getfield(L, -1, "registered_on_punchnode");
return readParam<bool>(L, -1);
}
+bool ScriptApiClient::on_recieve_physics_override(float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_recieve_physics_override
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_recieve_physics_override");
+
+ // Push data
+ push_physics_override(L, speed, jump, gravity, sneak, sneak_glitch, new_move);
+
+ // Call functions
+ runCallbacks(1, RUN_CALLBACKS_MODE_OR);
+ return readParam<bool>(L, -1);
+}
+
+bool ScriptApiClient::on_play_sound(SimpleSoundSpec spec)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_play_sound
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_play_sound");
+
+ // Push data
+ push_soundspec(L, spec);
+
+ // Call functions
+ runCallbacks(1, RUN_CALLBACKS_MODE_OR);
+ return readParam<bool>(L, -1);
+}
+
+bool ScriptApiClient::on_spawn_particle(struct ParticleParameters param)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_play_sound
+
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_spawn_particle");
+
+ // Push data
+ lua_newtable(L);
+ push_v3f(L, param.pos);
+ lua_setfield(L, -2, "pos");
+ push_v3f(L, param.vel);
+ lua_setfield(L, -2, "velocity");
+ push_v3f(L, param.acc);
+ lua_setfield(L, -2, "acceleration");
+ setfloatfield(L, -1, "expirationtime", param.expirationtime);
+ setboolfield(L, -1, "collisiondetection", param.collisiondetection);
+ setboolfield(L, -1, "collision_removal", param.collision_removal);
+ setboolfield(L, -1, "object_collision", param.object_collision);
+ setboolfield(L, -1, "vertical", param.vertical);
+ push_animation_definition(L, param.animation);
+ lua_setfield(L, -2, "animation");
+ setstringfield(L, -1, "texture", param.texture);
+ setintfield(L, -1, "glow", param.glow);
+ if (param.node.getContent() != CONTENT_IGNORE) {
+ pushnode(L, param.node, getGameDef()->ndef());
+ lua_setfield(L, -2, "node");
+ }
+ setintfield(L, -1, "node_tile", param.node_tile);
+
+ // Call functions
+ runCallbacks(1, RUN_CALLBACKS_MODE_OR);
+ return readParam<bool>(L, -1);
+}
+
+void ScriptApiClient::on_object_properties_change(s16 id)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_object_properties_change
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_object_properties_change");
+
+ // Push data
+ push_objectRef(L, id);
+
+ // Call functions
+ runCallbacks(1, RUN_CALLBACKS_MODE_FIRST);
+}
+
+void ScriptApiClient::on_object_hp_change(s16 id)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_object_hp_change
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_object_hp_change");
+
+ // Push data
+ push_objectRef(L, id);
+
+ // Call functions
+ runCallbacks(1, RUN_CALLBACKS_MODE_FIRST);
+}
+
+bool ScriptApiClient::on_object_add(s16 id)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_object_add
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_object_add");
+
+ // Push data
+ push_objectRef(L, id);
+
+ // Call functions
+ runCallbacks(1, RUN_CALLBACKS_MODE_OR);
+ return readParam<bool>(L, -1);
+}
+
bool ScriptApiClient::on_inventory_open(Inventory *inventory)
{
SCRIPTAPI_PRECHECKHEADER
lua_getglobal(L, "core");
lua_getfield(L, -1, "registered_on_inventory_open");
- push_inventory(L, inventory);
+ push_inventory_lists(L, *inventory);
try {
runCallbacks(1, RUN_CALLBACKS_MODE_OR);
return readParam<bool>(L, -1);
}
+void ScriptApiClient::open_enderchest()
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ PUSH_ERROR_HANDLER(L);
+ int error_handler = lua_gettop(L) - 1;
+ lua_insert(L, error_handler);
+
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "open_enderchest");
+ if (lua_isfunction(L, -1))
+ lua_pcall(L, 0, 0, error_handler);
+}
+
+v3f ScriptApiClient::get_send_speed(v3f speed)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ PUSH_ERROR_HANDLER(L);
+ int error_handler = lua_gettop(L) - 1;
+ lua_insert(L, error_handler);
+
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "get_send_speed");
+ if (lua_isfunction(L, -1)) {
+ speed /= BS;
+ push_v3f(L, speed);
+ lua_pcall(L, 1, 1, error_handler);
+ speed = read_v3f(L, -1);
+ speed *= BS;
+ }
+
+ return speed;
+}
+
+void ScriptApiClient::set_node_def(const ContentFeatures &f)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_nodes");
+
+ push_content_features(L, f);
+ lua_setfield(L, -2, f.name.c_str());
+}
+
+void ScriptApiClient::set_item_def(const ItemDefinition &i)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_items");
+
+ push_item_definition(L, i);
+ lua_setfield(L, -2, i.name.c_str());
+}
+
void ScriptApiClient::setEnv(ClientEnvironment *env)
{
ScriptApiBase::setEnv(env);
#include <cerrno>
#include <string>
+ #include <algorithm>
#include <iostream>
"type",
"unpack",
"_VERSION",
+ "vector",
"xpcall",
};
static const char *whitelist_tables[] = {
"string",
"table",
"math",
+ "bit"
};
static const char *io_whitelist[] = {
+ "open",
"close",
"flush",
"read",
"date",
"difftime",
"getenv",
- "setlocale",
"time",
- "tmpname",
};
static const char *debug_whitelist[] = {
"gethook",
"traceback",
"getinfo",
"getmetatable",
- "setupvalue",
"setmetatable",
"upvalueid",
"sethook",
"debug",
- "setlocal",
};
static const char *package_whitelist[] = {
"config",
copy_safe(L, io_whitelist, sizeof(io_whitelist));
// And replace unsafe ones
- SECURE_API(io, open);
+ //SECURE_API(io, open);
SECURE_API(io, input);
SECURE_API(io, output);
SECURE_API(io, lines);
// And replace unsafe ones
SECURE_API(os, remove);
SECURE_API(os, rename);
+ SECURE_API(os, setlocale);
lua_setglobal(L, "os");
lua_pop(L, 1); // Pop old OS
lua_pop(L, 1); // Pop old jit
#endif
+ // Get rid of 'core' in the old globals, we don't want anyone thinking it's
+ // safe or even usable.
+ lua_pushnil(L);
+ lua_setfield(L, old_globals, "core");
+
+ // 'vector' as well.
+ lua_pushnil(L);
+ lua_setfield(L, old_globals, "vector");
+
lua_pop(L, 1); // Pop globals_backup
"rawset",
"select",
"setfenv",
- // getmetatable can be used to escape the sandbox
+ // getmetatable can be used to escape the sandbox <- ???
"setmetatable",
"tonumber",
"tostring",
"type",
"unpack",
"_VERSION",
+ "vector",
"xpcall",
// Completely safe libraries
"coroutine",
"string",
"table",
"math",
+ "bit",
};
static const char *os_whitelist[] = {
"clock",
"time"
};
static const char *debug_whitelist[] = {
- "getinfo",
+ "getinfo", // used by builtin and unset before mods load
"traceback"
};
-
#if USE_LUAJIT
static const char *jit_whitelist[] = {
"arch",
lua_State *L = getStack();
int thread = getThread(L);
+ // Backup globals to the registry
+ lua_getglobal(L, "_G");
+ lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
+
// create an empty environment
createEmptyEnv(L);
SECURE_API(g, require);
lua_pop(L, 2);
-
-
// Copy safe OS functions
lua_getglobal(L, "os");
lua_newtable(L);
copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
lua_setfield(L, -3, "debug");
lua_pop(L, 1); // Pop old debug
+
#if USE_LUAJIT
// Copy safe jit functions, if they exist
return false;
}
+ bool ScriptApiSecurity::checkWhitelisted(lua_State *L, const std::string &setting)
+ {
+ assert(str_starts_with(setting, "secure."));
+
+ // We have to make sure that this function is being called directly by
+ // a mod, otherwise a malicious mod could override this function and
+ // steal its return value.
+ lua_Debug info;
+
+ // Make sure there's only one item below this function on the stack...
+ if (lua_getstack(L, 2, &info))
+ return false;
+ FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed");
+ FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed");
+
+ // ...and that that item is the main file scope.
+ if (strcmp(info.what, "main") != 0)
+ return false;
+
+ // Mod must be listed in secure.http_mods or secure.trusted_mods
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
+ if (!lua_isstring(L, -1))
+ return false;
+ std::string mod_name = readParam<std::string>(L, -1);
+
+ std::string value = g_settings->get(setting);
+ value.erase(std::remove(value.begin(), value.end(), ' '), value.end());
+ auto mod_list = str_split(value, ',');
+
+ return CONTAINS(mod_list, mod_name);
+ }
+
int ScriptApiSecurity::sl_g_dofile(lua_State *L)
{
return 2;
}
+
+ int ScriptApiSecurity::sl_os_setlocale(lua_State *L)
+ {
+ const bool cat = lua_gettop(L) > 1;
+ // Don't allow changes
+ if (!lua_isnoneornil(L, 1)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ push_original(L, "os", "setlocale");
+ lua_pushnil(L);
+ if (cat)
+ lua_pushvalue(L, 2);
+ lua_call(L, cat ? 2 : 1, 1);
+ return 1;
+ }
#include "client/clientevent.h"
#include "client/sound.h"
#include "client/clientenvironment.h"
+#include "client/game.h"
#include "common/c_content.h"
#include "common/c_converter.h"
#include "cpp_api/s_base.h"
#include "gettext.h"
#include "l_internal.h"
+#include "l_clientobject.h"
#include "lua_api/l_nodemeta.h"
#include "gui/mainmenumanager.h"
#include "map.h"
#include "util/string.h"
#include "nodedef.h"
+#include "client/keycode.h"
#define checkCSMRestrictionFlag(flag) \
( getClient(L)->checkCSMRestrictionFlag(CSMRestrictionFlags::flag) )
return 1;
}
- push_inventory(L, inventory);
+// send_damage(damage)
+int ModApiClient::l_send_damage(lua_State *L)
+{
+ u16 damage = luaL_checknumber(L, 1);
+ getClient(L)->sendDamage(damage);
+ return 0;
+}
+
+// place_node(pos)
+int ModApiClient::l_place_node(lua_State *L)
+{
+ Client *client = getClient(L);
+ ClientMap &map = client->getEnv().getClientMap();
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ ItemStack selected_item, hand_item;
+ player->getWieldedItem(&selected_item, &hand_item);
+ const ItemDefinition &selected_def = selected_item.getDefinition(getGameDef(L)->idef());
+ v3s16 pos = read_v3s16(L, 1);
+ PointedThing pointed;
+ pointed.type = POINTEDTHING_NODE;
+ pointed.node_abovesurface = pos;
+ pointed.node_undersurface = pos;
+ NodeMetadata *meta = map.getNodeMetadata(pos);
+ g_game->nodePlacement(selected_def, selected_item, pos, pos, pointed, meta, true);
+ return 0;
+}
+
+// dig_node(pos)
+int ModApiClient::l_dig_node(lua_State *L)
+{
+ Client *client = getClient(L);
+ v3s16 pos = read_v3s16(L, 1);
+ PointedThing pointed;
+ pointed.type = POINTEDTHING_NODE;
+ pointed.node_abovesurface = pos;
+ pointed.node_undersurface = pos;
+ client->interact(INTERACT_START_DIGGING, pointed);
+ client->interact(INTERACT_DIGGING_COMPLETED, pointed);
+ client->removeNode(pos);
+ return 0;
+}
+
+// get_inventory(location)
+int ModApiClient::l_get_inventory(lua_State *L)
+{
+ Client *client = getClient(L);
+ InventoryLocation inventory_location;
+ Inventory *inventory;
+ std::string location;
+
+ location = readParam<std::string>(L, 1);
+
+ try {
+ inventory_location.deSerialize(location);
+ inventory = client->getInventory(inventory_location);
+ if (! inventory)
+ throw SerializationError(std::string("Attempt to access nonexistant inventory (") + location + ")");
++ push_inventory_lists(L, *inventory);
+ } catch (SerializationError &) {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
+// set_keypress(key_setting, pressed) -> returns true on success
+int ModApiClient::l_set_keypress(lua_State *L)
+{
+ std::string setting_name = "keymap_" + readParam<std::string>(L, 1);
+ bool pressed = lua_isboolean(L, 2) && readParam<bool>(L, 2);
+ try {
+ KeyPress keyCode = getKeySetting(setting_name.c_str());
+ if (pressed)
+ g_game->input->setKeypress(keyCode);
+ else
+ g_game->input->unsetKeypress(keyCode);
+ lua_pushboolean(L, true);
+ } catch (SettingNotFoundException &) {
+ lua_pushboolean(L, false);
+ }
+ return 1;
+}
+
+// drop_selected_item()
+int ModApiClient::l_drop_selected_item(lua_State *L)
+{
+ g_game->dropSelectedItem();
+ return 0;
+}
+
+// get_objects_inside_radius(pos, radius)
+int ModApiClient::l_get_objects_inside_radius(lua_State *L)
+{
+ ClientEnvironment &env = getClient(L)->getEnv();
+
+ v3f pos = checkFloatPos(L, 1);
+ float radius = readParam<float>(L, 2) * BS;
+
+ std::vector<DistanceSortedActiveObject> objs;
+ env.getActiveObjects(pos, radius, objs);
+
+ int i = 0;
+ lua_createtable(L, objs.size(), 0);
+ for (const auto obj : objs) {
+ push_objectRef(L, obj.obj->getId());
+ lua_rawseti(L, -2, ++i);
+ }
+ return 1;
+}
+
+// make_screenshot()
+int ModApiClient::l_make_screenshot(lua_State *L)
+{
+ getClient(L)->makeScreenshot();
+ return 0;
+}
+
+/*
+`pointed_thing`
+---------------
+
+* `{type="nothing"}`
+* `{type="node", under=pos, above=pos}`
+ * Indicates a pointed node selection box.
+ * `under` refers to the node position behind the pointed face.
+ * `above` refers to the node position in front of the pointed face.
+* `{type="object", ref=ObjectRef}`
+
+Exact pointing location (currently only `Raycast` supports these fields):
+
+* `pointed_thing.intersection_point`: The absolute world coordinates of the
+ point on the selection box which is pointed at. May be in the selection box
+ if the pointer is in the box too.
+* `pointed_thing.box_id`: The ID of the pointed selection box (counting starts
+ from 1).
+* `pointed_thing.intersection_normal`: Unit vector, points outwards of the
+ selected selection box. This specifies which face is pointed at.
+ Is a null vector `{x = 0, y = 0, z = 0}` when the pointer is inside the
+ selection box.
+*/
+
+// interact(action, pointed_thing)
+int ModApiClient::l_interact(lua_State *L)
+{
+ std::string action_str = readParam<std::string>(L, 1);
+ InteractAction action;
+
+ if (action_str == "start_digging")
+ action = INTERACT_START_DIGGING;
+ else if (action_str == "stop_digging")
+ action = INTERACT_STOP_DIGGING;
+ else if (action_str == "digging_completed")
+ action = INTERACT_DIGGING_COMPLETED;
+ else if (action_str == "place")
+ action = INTERACT_PLACE;
+ else if (action_str == "use")
+ action = INTERACT_USE;
+ else if (action_str == "activate")
+ action = INTERACT_ACTIVATE;
+ else
+ return 0;
+
+ lua_getfield(L, 2, "type");
+ if (! lua_isstring(L, -1))
+ return 0;
+ std::string type_str = lua_tostring(L, -1);
+ lua_pop(L, 1);
+
+ PointedThingType type;
+
+ if (type_str == "nothing")
+ type = POINTEDTHING_NOTHING;
+ else if (type_str == "node")
+ type = POINTEDTHING_NODE;
+ else if (type_str == "object")
+ type = POINTEDTHING_OBJECT;
+ else
+ return 0;
+
+ PointedThing pointed;
+ pointed.type = type;
+ ClientObjectRef *obj;
+
+ switch (type) {
+ case POINTEDTHING_NODE:
+ lua_getfield(L, 2, "under");
+ pointed.node_undersurface = check_v3s16(L, -1);
+
+ lua_getfield(L, 2, "above");
+ pointed.node_abovesurface = check_v3s16(L, -1);
+ break;
+ case POINTEDTHING_OBJECT:
+ lua_getfield(L, 2, "ref");
+ obj = ClientObjectRef::checkobject(L, -1);
+ pointed.object_id = obj->getClientActiveObject()->getId();
+ break;
+ default:
+ break;
+ }
+
+ getClient(L)->interact(action, pointed);
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+StringMap *table_to_stringmap(lua_State *L, int index)
+{
+ StringMap *m = new StringMap;
+
+ lua_pushvalue(L, index);
+ lua_pushnil(L);
+
+ while (lua_next(L, -2)) {
+ lua_pushvalue(L, -2);
+ std::basic_string<char> key = lua_tostring(L, -1);
+ std::basic_string<char> value = lua_tostring(L, -2);
+ (*m)[key] = value;
+ lua_pop(L, 2);
+ }
+
+ lua_pop(L, 1);
+
+ return m;
+}
+
+// send_inventory_fields(formname, fields)
+// Only works if the inventory form was opened beforehand.
+int ModApiClient::l_send_inventory_fields(lua_State *L)
+{
+ std::string formname = luaL_checkstring(L, 1);
+ StringMap *fields = table_to_stringmap(L, 2);
+
+ getClient(L)->sendInventoryFields(formname, *fields);
+ return 0;
+}
+
+// send_nodemeta_fields(position, formname, fields)
+int ModApiClient::l_send_nodemeta_fields(lua_State *L)
+{
+ v3s16 pos = check_v3s16(L, 1);
+ std::string formname = luaL_checkstring(L, 2);
+ StringMap *m = table_to_stringmap(L, 3);
+
+ getClient(L)->sendNodemetaFields(pos, formname, *m);
+ return 0;
+}
+
void ModApiClient::Initialize(lua_State *L, int top)
{
API_FCT(get_current_modname);
API_FCT(get_builtin_path);
API_FCT(get_language);
API_FCT(get_csm_restrictions);
+ API_FCT(send_damage);
+ API_FCT(place_node);
+ API_FCT(dig_node);
+ API_FCT(get_inventory);
+ API_FCT(set_keypress);
+ API_FCT(drop_selected_item);
+ API_FCT(get_objects_inside_radius);
+ API_FCT(make_screenshot);
+ API_FCT(interact);
+ API_FCT(send_inventory_fields);
+ API_FCT(send_nodemeta_fields);
}
return 1;
}
// Create item to place
- ItemStack item(ndef->get(n).name, 1, 0, idef);
+ Optional<ItemStack> item = ItemStack(ndef->get(n).name, 1, 0, idef);
// Make pointed position
PointedThing pointed;
pointed.type = POINTEDTHING_NODE;
int radius = luaL_checkinteger(L, 2);
std::vector<content_t> filter;
collectNodeIds(L, 3, ndef, filter);
-
int start_radius = (lua_isboolean(L, 4) && readParam<bool>(L, 4)) ? 0 : 1;
#ifndef SERVER
return 0;
}
+// find_nodes_near(pos, radius, nodenames, [search_center])
+// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
+int ModApiEnvMod::l_find_nodes_near(lua_State *L)
+{
+ GET_PLAIN_ENV_PTR;
+
+ const NodeDefManager *ndef = env->getGameDef()->ndef();
+ Map &map = env->getMap();
+
+ v3s16 pos = read_v3s16(L, 1);
+ int radius = luaL_checkinteger(L, 2);
+ std::vector<content_t> filter;
+ collectNodeIds(L, 3, ndef, filter);
+
+ int start_radius = (lua_isboolean(L, 4) && readParam<bool>(L, 4)) ? 0 : 1;
+
+#ifndef SERVER
+ // Client API limitations
+ if (Client *client = getClient(L))
+ radius = client->CSMClampRadius(pos, radius);
+#endif
+
+ std::vector<u32> individual_count;
+ individual_count.resize(filter.size());
+
+ lua_newtable(L);
+ u32 i = 0;
+
+ for (int d = start_radius; d <= radius; d++) {
+ const std::vector<v3s16> &list = FacePositionCache::getFacePositions(d);
+ for (const v3s16 &posi : list) {
+ v3s16 p = pos + posi;
+ content_t c = map.getNode(p).getContent();
+ auto it = std::find(filter.begin(), filter.end(), c);
+ if (it != filter.end()) {
+ push_v3s16(L, p);
+ lua_rawseti(L, -2, ++i);
+
+ u32 filt_index = it - filter.begin();
+ individual_count[filt_index]++;
+ }
+ }
+ }
+ lua_createtable(L, 0, filter.size());
+ for (u32 i = 0; i < filter.size(); i++) {
+ lua_pushinteger(L, individual_count[i]);
+ lua_setfield(L, -2, ndef->get(filter[i]).name.c_str());
+ }
+ return 2;
+}
+
+// find_nodes_near_under_air(pos, radius, nodenames, [search_center])
+// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
+int ModApiEnvMod::l_find_nodes_near_under_air(lua_State *L)
+{
+ GET_PLAIN_ENV_PTR;
+
+ const NodeDefManager *ndef = env->getGameDef()->ndef();
+ Map &map = env->getMap();
+
+ v3s16 pos = read_v3s16(L, 1);
+ int radius = luaL_checkinteger(L, 2);
+ std::vector<content_t> filter;
+ collectNodeIds(L, 3, ndef, filter);
+ int start_radius = (lua_isboolean(L, 4) && readParam<bool>(L, 4)) ? 0 : 1;
+
+#ifndef SERVER
+ // Client API limitations
+ if (Client *client = getClient(L))
+ radius = client->CSMClampRadius(pos, radius);
+#endif
+
+ std::vector<u32> individual_count;
+ individual_count.resize(filter.size());
+
+ lua_newtable(L);
+ u32 i = 0;
+
+ for (int d = start_radius; d <= radius; d++) {
+ const std::vector<v3s16> &list = FacePositionCache::getFacePositions(d);
+ for (const v3s16 &posi : list) {
+ v3s16 p = pos + posi;
+ content_t c = map.getNode(p).getContent();
+ v3s16 psurf(p.X, p.Y + 1, p.Z);
+ content_t csurf = map.getNode(psurf).getContent();
+ if (c == CONTENT_AIR || csurf != CONTENT_AIR)
+ continue;
+ auto it = std::find(filter.begin(), filter.end(), c);
+ if (it != filter.end()) {
+ push_v3s16(L, p);
+ lua_rawseti(L, -2, ++i);
+
+ u32 filt_index = it - filter.begin();
+ individual_count[filt_index]++;
+ }
+ }
+ }
+ lua_createtable(L, 0, filter.size());
+ for (u32 i = 0; i < filter.size(); i++) {
+ lua_pushinteger(L, individual_count[i]);
+ lua_setfield(L, -2, ndef->get(filter[i]).name.c_str());
+ }
+ return 2;
+}
+
+// find_nodes_near_under_air_except(pos, radius, nodenames, [search_center])
+// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
+int ModApiEnvMod::l_find_nodes_near_under_air_except(lua_State *L)
+{
+ GET_PLAIN_ENV_PTR;
+
+ const NodeDefManager *ndef = env->getGameDef()->ndef();
+ Map &map = env->getMap();
+
+ v3s16 pos = read_v3s16(L, 1);
+ int radius = luaL_checkinteger(L, 2);
+ std::vector<content_t> filter;
+ collectNodeIds(L, 3, ndef, filter);
+ int start_radius = (lua_isboolean(L, 4) && readParam<bool>(L, 4)) ? 0 : 1;
+
+#ifndef SERVER
+ // Client API limitations
+ if (Client *client = getClient(L))
+ radius = client->CSMClampRadius(pos, radius);
+#endif
+
+ std::vector<u32> individual_count;
+ individual_count.resize(filter.size());
+
+ lua_newtable(L);
+ u32 i = 0;
+
+ for (int d = start_radius; d <= radius; d++) {
+ const std::vector<v3s16> &list = FacePositionCache::getFacePositions(d);
+ for (const v3s16 &posi : list) {
+ v3s16 p = pos + posi;
+ content_t c = map.getNode(p).getContent();
+ v3s16 psurf(p.X, p.Y + 1, p.Z);
+ content_t csurf = map.getNode(psurf).getContent();
+ if (c == CONTENT_AIR || csurf != CONTENT_AIR)
+ continue;
+ auto it = std::find(filter.begin(), filter.end(), c);
+ if (it == filter.end()) {
+ push_v3s16(L, p);
+ lua_rawseti(L, -2, ++i);
+
+ u32 filt_index = it - filter.begin();
+ individual_count[filt_index]++;
+ }
+ }
+ }
+ lua_createtable(L, 0, filter.size());
+ for (u32 i = 0; i < filter.size(); i++) {
+ lua_pushinteger(L, individual_count[i]);
+ lua_setfield(L, -2, ndef->get(filter[i]).name.c_str());
+ }
+ return 2;
+}
+
+ static void checkArea(v3s16 &minp, v3s16 &maxp)
+ {
+ auto volume = VoxelArea(minp, maxp).getVolume();
+ // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000
+ if (volume > 4096000) {
+ throw LuaError("Area volume exceeds allowed value of 4096000");
+ }
+
+ // Clamp to map range to avoid problems
+ #define CLAMP(arg) core::clamp(arg, (s16)-MAX_MAP_GENERATION_LIMIT, (s16)MAX_MAP_GENERATION_LIMIT)
+ minp = v3s16(CLAMP(minp.X), CLAMP(minp.Y), CLAMP(minp.Z));
+ maxp = v3s16(CLAMP(maxp.X), CLAMP(maxp.Y), CLAMP(maxp.Z));
+ #undef CLAMP
+ }
+
// find_nodes_in_area(minp, maxp, nodenames, [grouped])
int ModApiEnvMod::l_find_nodes_in_area(lua_State *L)
{
}
#endif
- v3s16 cube = maxp - minp + 1;
- // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000
- if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) {
- luaL_error(L, "find_nodes_in_area(): area volume"
- " exceeds allowed value of 4096000");
- return 0;
- }
+ checkArea(minp, maxp);
std::vector<content_t> filter;
collectNodeIds(L, 3, ndef, filter);
}
#endif
- v3s16 cube = maxp - minp + 1;
- // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000
- if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) {
- luaL_error(L, "find_nodes_in_area_under_air(): area volume"
- " exceeds allowed value of 4096000");
- return 0;
- }
+ checkArea(minp, maxp);
std::vector<content_t> filter;
collectNodeIds(L, 3, ndef, filter);
// max_jump, max_drop, algorithm) -> table containing path
int ModApiEnvMod::l_find_path(lua_State *L)
{
- GET_ENV_PTR;
+ Environment *env = getEnv(L);
v3s16 pos1 = read_v3s16(L, 1);
v3s16 pos2 = read_v3s16(L, 2);
algo = PA_DIJKSTRA;
}
- std::vector<v3s16> path = get_path(&env->getServerMap(), env->getGameDef()->ndef(), pos1, pos2,
+ std::vector<v3s16> path = get_path(&env->getMap(), env->getGameDef()->ndef(), pos1, pos2,
searchdistance, max_jump, max_drop, algo);
if (!path.empty()) {
GET_ENV_PTR;
v3s16 p0 = read_v3s16(L, 1);
- env->getMap().transforming_liquid_add(p0);
+ env->getServerMap().transforming_liquid_add(p0);
return 1;
}
API_FCT(get_node_level);
API_FCT(find_nodes_with_meta);
API_FCT(find_node_near);
+ API_FCT(find_nodes_near);
+ API_FCT(find_nodes_near_under_air);
+ API_FCT(find_nodes_near_under_air_except);
API_FCT(find_nodes_in_area);
API_FCT(find_nodes_in_area_under_air);
+ API_FCT(get_voxel_manip);
+ API_FCT(find_path);
API_FCT(line_of_sight);
API_FCT(raycast);
}
// get_objects_inside_radius(pos, radius)
static int l_get_objects_inside_radius(lua_State *L);
-
+
// get_objects_in_area(pos, minp, maxp)
static int l_get_objects_in_area(lua_State *L);
// find_node_near(pos, radius, nodenames, search_center) -> pos or nil
// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
static int l_find_node_near(lua_State *L);
+
+ // find_nodes_near(pos, radius, nodenames, search_center) -> list of positions
+ // nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
+ static int l_find_nodes_near(lua_State *L);
+
+ // find_nodes_near_under_air(pos, radius, nodenames, search_center) -> list of positions
+ // nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
+ static int l_find_nodes_near_under_air(lua_State *L);
+
+ // find_nodes_near_under_air(pos, radius, nodenames, search_center) -> list of positions
+ // nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
+ static int l_find_nodes_near_under_air_except(lua_State *L);
// find_nodes_in_area(minp, maxp, nodenames) -> list of positions
// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
#include "lua_api/l_internal.h"
#include "common/c_converter.h"
#include "common/c_content.h"
+ #include "common/c_packer.h"
#include "itemdef.h"
#include "nodedef.h"
#include "server.h"
#include "inventory.h"
#include "log.h"
-
+#include "script/cpp_api/s_base.h"
+#ifndef SERVER
+#include "client/client.h"
+#include "client/renderingengine.h"
+#include "client/shader.h"
+#endif
// garbage collector
int LuaItemStack::gc_object(lua_State *L)
lua_setmetatable(L, -2);
return 1;
}
+
// Not callable from Lua
int LuaItemStack::create(lua_State *L, const ItemStack &item)
{
return *(LuaItemStack **)luaL_checkudata(L, narg, className);
}
+ void *LuaItemStack::packIn(lua_State *L, int idx)
+ {
+ LuaItemStack *o = checkobject(L, idx);
+ return new ItemStack(o->getItem());
+ }
+
+ void LuaItemStack::packOut(lua_State *L, void *ptr)
+ {
+ ItemStack *stack = reinterpret_cast<ItemStack*>(ptr);
+ if (L)
+ create(L, *stack);
+ delete stack;
+ }
+
void LuaItemStack::Register(lua_State *L)
{
lua_newtable(L);
// Can be created from Lua (ItemStack(itemstack or itemstring or table or nil))
lua_register(L, className, create_object);
+
+ script_register_packer(L, className, packIn, packOut);
}
const char LuaItemStack::className[] = "ItemStack";
// Get the writable item and node definition managers from the server
IWritableItemDefManager *idef =
- getServer(L)->getWritableItemDefManager();
+ getGameDef(L)->getWritableItemDefManager();
NodeDefManager *ndef =
- getServer(L)->getWritableNodeDefManager();
+ getGameDef(L)->getWritableNodeDefManager();
// Check if name is defined
std::string name;
+ itos(MAX_REGISTERED_CONTENT+1)
+ ") exceeded (" + name + ")");
}
+
}
-
+
return 0; /* number of results */
}
std::string name = luaL_checkstring(L, 1);
IWritableItemDefManager *idef =
- getServer(L)->getWritableItemDefManager();
+ getGameDef(L)->getWritableItemDefManager();
// Unregister the node
if (idef->get(name).type == ITEM_NODE) {
NodeDefManager *ndef =
- getServer(L)->getWritableNodeDefManager();
+ getGameDef(L)->getWritableNodeDefManager();
ndef->removeNode(name);
}
// Get the writable item definition manager from the server
IWritableItemDefManager *idef =
- getServer(L)->getWritableItemDefManager();
+ getGameDef(L)->getWritableItemDefManager();
idef->registerAlias(name, convert_to);
NO_MAP_LOCK_REQUIRED;
std::string name = luaL_checkstring(L, 1);
- const IItemDefManager *idef = getGameDef(L)->getItemDefManager();
- const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager();
+ const IItemDefManager *idef = getGameDef(L)->idef();
+ const NodeDefManager *ndef = getGameDef(L)->ndef();
// If this is called at mod load time, NodeDefManager isn't aware of
// aliases yet, so we need to handle them manually
NO_MAP_LOCK_REQUIRED;
content_t c = luaL_checkint(L, 1);
- const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager();
+ const NodeDefManager *ndef = getGameDef(L)->ndef();
const char *name = ndef->get(c).name.c_str();
lua_pushstring(L, name);
API_FCT(get_content_id);
API_FCT(get_name_from_content_id);
}
+
+ void ModApiItemMod::InitializeAsync(lua_State *L, int top)
+ {
+ // all read-only functions
+ API_FCT(get_content_id);
+ API_FCT(get_name_from_content_id);
+ }
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "l_clientobject.h"
#include "l_localplayer.h"
#include "l_internal.h"
#include "lua_api/l_item.h"
#include "client/localplayer.h"
#include "hud.h"
#include "common/c_content.h"
+#include "client/client.h"
#include "client/content_cao.h"
+#include "client/game.h"
LuaLocalPlayer::LuaLocalPlayer(LocalPlayer *m) : m_localplayer(m)
{
return 1;
}
+int LuaLocalPlayer::l_set_velocity(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+
+ v3f pos = checkFloatPos(L, 2);
+ player->setSpeed(pos);
+
+ return 0;
+}
+
+int LuaLocalPlayer::l_get_yaw(lua_State *L)
+{
+ lua_pushnumber(L, wrapDegrees_0_360(g_game->cam_view.camera_yaw));
+ return 1;
+}
+
+int LuaLocalPlayer::l_set_yaw(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+
+ if (lua_isnumber(L, 2)) {
+ double yaw = lua_tonumber(L, 2);
+ player->setYaw(yaw);
+ g_game->cam_view.camera_yaw = yaw;
+ g_game->cam_view_target.camera_yaw = yaw;
+ }
+
+ return 0;
+}
+
+int LuaLocalPlayer::l_get_pitch(lua_State *L)
+{
+ lua_pushnumber(L, -wrapDegrees_180(g_game->cam_view.camera_pitch) );
+ return 1;
+}
+
+int LuaLocalPlayer::l_set_pitch(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+
+ if (lua_isnumber(L, 2)) {
+ double pitch = lua_tonumber(L, 2);
+ player->setPitch(pitch);
+ g_game->cam_view.camera_pitch = pitch;
+ g_game->cam_view_target.camera_pitch = pitch;
+ }
+
+ return 0;
+}
+
+
int LuaLocalPlayer::l_get_hp(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
{
LocalPlayer *player = getobject(L, 1);
- lua_pushinteger(L, player->getWieldIndex());
+ lua_pushinteger(L, player->getWieldIndex() + 1);
return 1;
}
+// set_wield_index(self)
+int LuaLocalPlayer::l_set_wield_index(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+ u32 index = luaL_checkinteger(L, 2) - 1;
+
+ player->setWieldIndex(index);
+ g_game->processItemSelection(&g_game->runData.new_playeritem);
+ ItemStack selected_item, hand_item;
+ ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
+ g_game->camera->wield(tool_item);
+ return 0;
+}
+
// get_wielded_item(self)
int LuaLocalPlayer::l_get_wielded_item(lua_State *L)
{
return 1;
}
- int LuaLocalPlayer::l_get_liquid_viscosity(lua_State *L)
+ int LuaLocalPlayer::l_get_move_resistance(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
- lua_pushinteger(L, player->liquid_viscosity);
+ lua_pushinteger(L, player->move_resistance);
return 1;
}
{
LocalPlayer *player = getobject(L, 1);
- lua_newtable(L);
- lua_pushnumber(L, player->physics_override_speed);
- lua_setfield(L, -2, "speed");
+ push_physics_override(L, player->physics_override_speed, player->physics_override_jump, player->physics_override_gravity, player->physics_override_sneak, player->physics_override_sneak_glitch, player->physics_override_new_move);
- lua_pushnumber(L, player->physics_override_jump);
- lua_setfield(L, -2, "jump");
-
- lua_pushnumber(L, player->physics_override_gravity);
- lua_setfield(L, -2, "gravity");
-
- lua_pushboolean(L, player->physics_override_sneak);
- lua_setfield(L, -2, "sneak");
+ return 1;
+}
- lua_pushboolean(L, player->physics_override_sneak_glitch);
- lua_setfield(L, -2, "sneak_glitch");
+// set_physics_override(self, override)
+int LuaLocalPlayer::l_set_physics_override(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
- lua_pushboolean(L, player->physics_override_new_move);
- lua_setfield(L, -2, "new_move");
+ player->physics_override_speed = getfloatfield_default(
+ L, 2, "speed", player->physics_override_speed);
+ player->physics_override_jump = getfloatfield_default(
+ L, 2, "jump", player->physics_override_jump);
+ player->physics_override_gravity = getfloatfield_default(
+ L, 2, "gravity", player->physics_override_gravity);
+ player->physics_override_sneak = getboolfield_default(
+ L, 2, "sneak", player->physics_override_sneak);
+ player->physics_override_sneak_glitch = getboolfield_default(
+ L, 2, "sneak_glitch", player->physics_override_sneak_glitch);
+ player->physics_override_new_move = getboolfield_default(
+ L, 2, "new_move", player->physics_override_new_move);
- return 1;
+ return 0;
}
int LuaLocalPlayer::l_get_last_pos(lua_State *L)
set("dig", c.dig);
set("place", c.place);
// Player movement in polar coordinates and non-binary speed
- set("movement_speed", c.movement_speed);
- set("movement_direction", c.movement_direction);
+ lua_pushnumber(L, c.movement_speed);
+ lua_setfield(L, -2, "movement_speed");
+ lua_pushnumber(L, c.movement_direction);
+ lua_setfield(L, -2, "movement_direction");
// Provide direction keys to ensure compatibility
- set("up", player->keyPressed & (1 << 0)); // Up, down, left, and right were removed in favor of
- set("down", player->keyPressed & (1 << 1)); // analog direction indicators and are therefore not
- set("left", player->keyPressed & (1 << 2)); // available as booleans anymore. The corresponding values
- set("right", player->keyPressed & (1 << 3)); // can still be read from the keyPressed bits though.
+ set("up", c.direction_keys & (1 << 0));
+ set("down", c.direction_keys & (1 << 1));
+ set("left", c.direction_keys & (1 << 2));
+ set("right", c.direction_keys & (1 << 3));
return 1;
}
return 1;
}
+// set_pos(self, pos)
+int LuaLocalPlayer::l_set_pos(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+
+ v3f pos = checkFloatPos(L, 2);
+ player->setPosition(pos);
+ getClient(L)->sendPlayerPos();
+ return 0;
+}
+
// get_movement_acceleration(self)
int LuaLocalPlayer::l_get_movement_acceleration(lua_State *L)
{
return 1;
}
+// get_object(self)
+int LuaLocalPlayer::l_get_object(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+ ClientEnvironment &env = getClient(L)->getEnv();
+ ClientActiveObject *obj = env.getGenericCAO(player->getCAO()->getId());
+
+ push_objectRef(L, obj->getId());
+
+ return 1;
+}
+
+// get_hotbar_size(self)
+int LuaLocalPlayer::l_get_hotbar_size(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+ lua_pushnumber(L, player->hud_hotbar_itemcount);
+
+ return 1;
+}
+
LuaLocalPlayer *LuaLocalPlayer::checkobject(lua_State *L, int narg)
{
luaL_checktype(L, narg, LUA_TUSERDATA);
const char LuaLocalPlayer::className[] = "LocalPlayer";
const luaL_Reg LuaLocalPlayer::methods[] = {
luamethod(LuaLocalPlayer, get_velocity),
+ luamethod(LuaLocalPlayer, set_velocity),
+ luamethod(LuaLocalPlayer, get_yaw),
+ luamethod(LuaLocalPlayer, set_yaw),
+ luamethod(LuaLocalPlayer, get_pitch),
+ luamethod(LuaLocalPlayer, set_pitch),
luamethod(LuaLocalPlayer, get_hp),
luamethod(LuaLocalPlayer, get_name),
luamethod(LuaLocalPlayer, get_wield_index),
+ luamethod(LuaLocalPlayer, set_wield_index),
luamethod(LuaLocalPlayer, get_wielded_item),
luamethod(LuaLocalPlayer, is_attached),
luamethod(LuaLocalPlayer, is_touching_ground),
luamethod(LuaLocalPlayer, is_in_liquid),
luamethod(LuaLocalPlayer, is_in_liquid_stable),
- luamethod(LuaLocalPlayer, get_liquid_viscosity),
luamethod(LuaLocalPlayer, is_climbing),
luamethod(LuaLocalPlayer, swimming_vertical),
luamethod(LuaLocalPlayer, get_physics_override),
+ luamethod(LuaLocalPlayer, set_physics_override),
// TODO: figure our if these are useful in any way
luamethod(LuaLocalPlayer, get_last_pos),
luamethod(LuaLocalPlayer, get_last_velocity),
luamethod(LuaLocalPlayer, get_control),
luamethod(LuaLocalPlayer, get_breath),
luamethod(LuaLocalPlayer, get_pos),
+ luamethod(LuaLocalPlayer, set_pos),
luamethod(LuaLocalPlayer, get_movement_acceleration),
luamethod(LuaLocalPlayer, get_movement_speed),
luamethod(LuaLocalPlayer, get_movement),
luamethod(LuaLocalPlayer, hud_remove),
luamethod(LuaLocalPlayer, hud_change),
luamethod(LuaLocalPlayer, hud_get),
+ luamethod(LuaLocalPlayer, get_object),
+ luamethod(LuaLocalPlayer, get_hotbar_size),
+ luamethod(LuaLocalPlayer, get_move_resistance),
+
{0, 0}
};
// get_velocity(self)
static int l_get_velocity(lua_State *L);
+ // set_velocity(self, vel)
+ static int l_set_velocity(lua_State *L);
+
+ // get_yaw(self)
+ static int l_get_yaw(lua_State *L);
+
+ // set_yaw(self, yaw)
+ static int l_set_yaw(lua_State *L);
+
+ // get_pitch(self)
+ static int l_get_pitch(lua_State *L);
+
+ // set_pitch(self,pitch)
+ static int l_set_pitch(lua_State *L);
+
// get_hp(self)
static int l_get_hp(lua_State *L);
// get_wield_index(self)
static int l_get_wield_index(lua_State *L);
+ // set_wield_index(self)
+ static int l_set_wield_index(lua_State *L);
+
// get_wielded_item(self)
static int l_get_wielded_item(lua_State *L);
+ // get_hotbar_size(self)
+ static int l_get_hotbar_size(lua_State *L);
+
static int l_is_attached(lua_State *L);
static int l_is_touching_ground(lua_State *L);
static int l_is_in_liquid(lua_State *L);
static int l_is_in_liquid_stable(lua_State *L);
- static int l_get_liquid_viscosity(lua_State *L);
static int l_is_climbing(lua_State *L);
static int l_swimming_vertical(lua_State *L);
static int l_get_physics_override(lua_State *L);
+ static int l_set_physics_override(lua_State *L);
static int l_get_override_pos(lua_State *L);
// get_pos(self)
static int l_get_pos(lua_State *L);
+ // set_pos(self, pos)
+ static int l_set_pos(lua_State *L);
+
// get_movement_acceleration(self)
static int l_get_movement_acceleration(lua_State *L);
// hud_get(self, id)
static int l_hud_get(lua_State *L);
+ static int l_get_move_resistance(lua_State *L);
+
+ // get_object(self)
+ static int l_get_object(lua_State *L);
+
LocalPlayer *m_localplayer = nullptr;
public:
lua_newtable(L);
int table2 = lua_gettop(L);
int internal_index = 1;
- for (const std::string &addon_mods_path : game.addon_mods_paths) {
+ for (const auto &addon_mods_path : game.addon_mods_paths) {
lua_pushnumber(L, internal_index);
- lua_pushstring(L, addon_mods_path.c_str());
+ lua_pushstring(L, addon_mods_path.second.c_str());
lua_settable(L, table2);
internal_index++;
}
const char *name = luaL_checkstring(L, 1);
int gameidx = luaL_checkinteger(L,2) -1;
+ StringMap use_settings;
+ luaL_checktype(L, 3, LUA_TTABLE);
+ lua_pushnil(L);
+ while (lua_next(L, 3) != 0) {
+ // key at index -2 and value at index -1
+ use_settings[luaL_checkstring(L, -2)] = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 1);
+
std::string path = porting::path_user + DIR_DELIM
"worlds" + DIR_DELIM
+ sanitizeDirName(name, "world_");
std::vector<SubgameSpec> games = getAvailableGames();
+ if (gameidx < 0 || gameidx >= (int) games.size()) {
+ lua_pushstring(L, "Invalid game index");
+ return 1;
+ }
- if ((gameidx >= 0) &&
- (gameidx < (int) games.size())) {
+ // Set the settings for world creation
+ // this is a bad hack but the best we have right now..
+ StringMap backup;
+ for (auto it : use_settings) {
+ if (g_settings->existsLocal(it.first))
+ backup[it.first] = g_settings->get(it.first);
+ g_settings->set(it.first, it.second);
+ }
- // Create world if it doesn't exist
- try {
- loadGameConfAndInitWorld(path, name, games[gameidx], true);
- lua_pushnil(L);
- } catch (const BaseException &e) {
- lua_pushstring(L, (std::string("Failed to initialize world: ") + e.what()).c_str());
- }
- } else {
- lua_pushstring(L, "Invalid game index");
+ // Create world if it doesn't exist
+ try {
+ loadGameConfAndInitWorld(path, name, games[gameidx], true);
+ lua_pushnil(L);
+ } catch (const BaseException &e) {
+ auto err = std::string("Failed to initialize world: ") + e.what();
+ lua_pushstring(L, err.c_str());
+ }
+
+ // Restore previous settings
+ for (auto it : use_settings) {
+ auto it2 = backup.find(it.first);
+ if (it2 == backup.end())
+ g_settings->remove(it.first); // wasn't set before
+ else
+ g_settings->set(it.first, it2->second); // was set before
}
+
return 1;
}
return 1;
}
+ /******************************************************************************/
+ int ModApiMainMenu::l_get_modpaths(lua_State *L)
+ {
+ lua_newtable(L);
+
+ ModApiMainMenu::l_get_modpath(L);
+ lua_setfield(L, -2, "mods");
+
+ for (const std::string &component : getEnvModPaths()) {
+ lua_pushstring(L, component.c_str());
+ lua_setfield(L, -2, fs::AbsolutePath(component).c_str());
+ }
+ return 1;
+ }
+
/******************************************************************************/
int ModApiMainMenu::l_get_clientmodpath(lua_State *L)
{
/******************************************************************************/
int ModApiMainMenu::l_get_temp_path(lua_State *L)
{
- lua_pushstring(L, fs::TempPath().c_str());
+ if (lua_isnoneornil(L, 1) || !lua_toboolean(L, 1))
+ lua_pushstring(L, fs::TempPath().c_str());
+ else
+ lua_pushstring(L, fs::CreateTempFile().c_str());
return 1;
}
const char *destination = luaL_checkstring(L, 2);
bool keep_source = true;
+ if (!lua_isnoneornil(L, 3))
+ keep_source = readParam<bool>(L, 3);
- if ((!lua_isnone(L,3)) &&
- (!lua_isnil(L,3))) {
- keep_source = readParam<bool>(L,3);
- }
-
- std::string absolute_destination = fs::RemoveRelativePathComponents(destination);
- std::string absolute_source = fs::RemoveRelativePathComponents(source);
+ std::string abs_destination = fs::RemoveRelativePathComponents(destination);
+ std::string abs_source = fs::RemoveRelativePathComponents(source);
- if ((ModApiMainMenu::mayModifyPath(absolute_destination))) {
- bool retval = fs::CopyDir(absolute_source,absolute_destination);
-
- if (retval && (!keep_source)) {
-
- retval &= fs::RecursiveDelete(absolute_source);
- }
- lua_pushboolean(L,retval);
+ if (!ModApiMainMenu::mayModifyPath(abs_destination) ||
+ (!keep_source && !ModApiMainMenu::mayModifyPath(abs_source))) {
+ lua_pushboolean(L, false);
return 1;
}
- lua_pushboolean(L,false);
+
+ bool retval;
+ if (keep_source)
+ retval = fs::CopyDir(abs_source, abs_destination);
+ else
+ retval = fs::MoveDir(abs_source, abs_destination);
+ lua_pushboolean(L, retval);
return 1;
}
std::string absolute_destination = fs::RemoveRelativePathComponents(destination);
if (ModApiMainMenu::mayModifyPath(absolute_destination)) {
- auto rendering_engine = getGuiEngine(L)->m_rendering_engine;
- fs::CreateAllDirs(absolute_destination);
- lua_pushboolean(L, fs::extractZipFile(rendering_engine->get_filesystem(), zipfile, destination));
+ auto fs = RenderingEngine::get_raw_device()->getFileSystem();
+ bool ok = fs::extractZipFile(fs, zipfile, destination);
+ lua_pushboolean(L, ok);
return 1;
}
if (fs::PathStartsWith(path, path_user + DIR_DELIM "client"))
return true;
+ if (fs::PathStartsWith(path, path_user + DIR_DELIM "clientmods"))
+ return true;
+ if (fs::PathStartsWith(path, path_user + DIR_DELIM "textures"))
+ return true;
if (fs::PathStartsWith(path, path_user + DIR_DELIM "games"))
return true;
if (fs::PathStartsWith(path, path_user + DIR_DELIM "mods"))
/******************************************************************************/
int ModApiMainMenu::l_gettext(lua_State *L)
{
- std::string text = strgettext(std::string(luaL_checkstring(L, 1)));
- lua_pushstring(L, text.c_str());
+ const char *srctext = luaL_checkstring(L, 1);
+ const char *text = *srctext ? gettext(srctext) : "";
+ lua_pushstring(L, text);
return 1;
}
API_FCT(get_mapgen_names);
API_FCT(get_user_path);
API_FCT(get_modpath);
+ API_FCT(get_modpaths);
API_FCT(get_clientmodpath);
API_FCT(get_gamepath);
API_FCT(get_texturepath);
API_FCT(get_mapgen_names);
API_FCT(get_user_path);
API_FCT(get_modpath);
+ API_FCT(get_modpaths);
API_FCT(get_clientmodpath);
API_FCT(get_gamepath);
API_FCT(get_texturepath);
API_FCT(delete_dir);
API_FCT(copy_dir);
API_FCT(is_dir);
- //API_FCT(extract_zip); //TODO remove dependency to GuiEngine
+ API_FCT(extract_zip);
API_FCT(may_modify_path);
API_FCT(download_file);
API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto);
- //API_FCT(gettext); (gettext lib isn't threadsafe)
+ API_FCT(gettext);
}
#include "lua_api/l_internal.h"
#include "common/c_converter.h"
#include "common/c_content.h"
+ #include "common/c_packer.h"
#include "cpp_api/s_base.h"
#include "cpp_api/s_security.h"
#include "scripting_server.h"
return 1;
}
+ // get_server_max_lag()
+ int ModApiServer::l_get_server_max_lag(lua_State *L)
+ {
+ NO_MAP_LOCK_REQUIRED;
+ GET_ENV_PTR;
+ lua_pushnumber(L, env->getMaxLagEstimate());
+ return 1;
+ }
// print(text)
int ModApiServer::l_print(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- Server *server = getServer(L);
+ if (!getEnv(L))
+ throw LuaError("Can't ban player before server has started up");
+ Server *server = getServer(L);
const char *name = luaL_checkstring(L, 1);
RemotePlayer *player = server->getEnv().getPlayer(name);
if (!player) {
return 1;
}
- // kick_player(name, [reason]) -> success
- int ModApiServer::l_kick_player(lua_State *L)
+ // disconnect_player(name, [reason]) -> success
+ int ModApiServer::l_disconnect_player(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
+
+ if (!getEnv(L))
+ throw LuaError("Can't kick player before server has started up");
+
const char *name = luaL_checkstring(L, 1);
- std::string message("Kicked");
+ std::string message;
if (lua_isstring(L, 2))
- message.append(": ").append(readParam<std::string>(L, 2));
+ message.append(readParam<std::string>(L, 2));
else
- message.append(".");
+ message.append("Disconnected.");
+
+ Server *server = getServer(L);
- RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name);
- if (player == NULL) {
+ RemotePlayer *player = server->getEnv().getPlayer(name);
+ if (!player) {
lua_pushboolean(L, false); // No such player
return 1;
}
- getServer(L)->DenyAccess_Legacy(player->getPeerId(), utf8_to_wide(message));
+
+ server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message);
lua_pushboolean(L, true);
return 1;
}
+// remove_player(name)
int ModApiServer::l_remove_player(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
std::string name = luaL_checkstring(L, 1);
ServerEnvironment *s_env = dynamic_cast<ServerEnvironment *>(getEnv(L));
- assert(s_env);
+ if (!s_env)
+ throw LuaError("Can't remove player before server has started up");
RemotePlayer *player = s_env->getPlayer(name.c_str());
if (!player)
{
NO_MAP_LOCK_REQUIRED;
std::string modname = luaL_checkstring(L, 1);
- const ModSpec *mod = getServer(L)->getModSpec(modname);
- if (!mod) {
+ const ModSpec *mod = getGameDef(L)->getModSpec(modname);
+ if (!mod)
lua_pushnil(L);
- return 1;
- }
- lua_pushstring(L, mod->path.c_str());
+ else
+ lua_pushstring(L, mod->path.c_str());
return 1;
}
// Get a list of mods
std::vector<std::string> modlist;
- getServer(L)->getModNames(modlist);
+ for (auto &it : getGameDef(L)->getMods())
+ modlist.emplace_back(it.name);
std::sort(modlist.begin(), modlist.end());
// Package them up for Lua
lua_createtable(L, modlist.size(), 0);
- std::vector<std::string>::iterator iter = modlist.begin();
+ auto iter = modlist.begin();
for (u16 i = 0; iter != modlist.end(); ++iter) {
lua_pushstring(L, iter->c_str());
lua_rawseti(L, -2, ++i);
int ModApiServer::l_get_worldpath(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- std::string worldpath = getServer(L)->getWorldPath();
- lua_pushstring(L, worldpath.c_str());
+ const Server *srv = getServer(L);
+ lua_pushstring(L, srv->getWorldPath().c_str());
return 1;
}
CHECK_SECURE_PATH(L, filepath.c_str(), false);
- u32 token = server->getScriptIface()->allocateDynamicMediaCallback(2);
+ u32 token = server->getScriptIface()->allocateDynamicMediaCallback(L, 2);
bool ok = server->dynamicAddMedia(filepath, token, to_player, ephemeral);
if (!ok)
int ModApiServer::l_is_singleplayer(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- lua_pushboolean(L, getServer(L)->isSingleplayer());
+ const Server *srv = getServer(L);
+ lua_pushboolean(L, srv->isSingleplayer());
return 1;
}
return 0;
}
+ // do_async_callback(func, params, mod_origin)
+ int ModApiServer::l_do_async_callback(lua_State *L)
+ {
+ NO_MAP_LOCK_REQUIRED;
+ ServerScripting *script = getScriptApi<ServerScripting>(L);
+
+ luaL_checktype(L, 1, LUA_TFUNCTION);
+ luaL_checktype(L, 2, LUA_TTABLE);
+ luaL_checktype(L, 3, LUA_TSTRING);
+
+ call_string_dump(L, 1);
+ size_t func_length;
+ const char *serialized_func_raw = lua_tolstring(L, -1, &func_length);
+
+ PackedValue *param = script_pack(L, 2);
+
+ std::string mod_origin = readParam<std::string>(L, 3);
+
+ u32 jobId = script->queueAsync(
+ std::string(serialized_func_raw, func_length),
+ param, mod_origin);
+
+ lua_settop(L, 0);
+ lua_pushinteger(L, jobId);
+ return 1;
+ }
+
+ // register_async_dofile(path)
+ int ModApiServer::l_register_async_dofile(lua_State *L)
+ {
+ NO_MAP_LOCK_REQUIRED;
+
+ std::string path = readParam<std::string>(L, 1);
+ CHECK_SECURE_PATH(L, path.c_str(), false);
+
+ // Find currently running mod name (only at init time)
+ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
+ if (!lua_isstring(L, -1))
+ return 0;
+ std::string modname = readParam<std::string>(L, -1);
+
+ getServer(L)->m_async_init_files.emplace_back(modname, path);
+ lua_pushboolean(L, true);
+ return 1;
+ }
+
+ // serialize_roundtrip(value)
+ // Meant for unit testing the packer from Lua
+ int ModApiServer::l_serialize_roundtrip(lua_State *L)
+ {
+ NO_MAP_LOCK_REQUIRED;
+
+ int top = lua_gettop(L);
+ auto *pv = script_pack(L, 1);
+ if (top != lua_gettop(L))
+ throw LuaError("stack values leaked");
+
+ #ifndef NDEBUG
+ script_dump_packed(pv);
+ #endif
+
+ top = lua_gettop(L);
+ script_unpack(L, pv);
+ delete pv;
+ if (top + 1 != lua_gettop(L))
+ throw LuaError("stack values leaked");
+
+ return 1;
+ }
+
void ModApiServer::Initialize(lua_State *L, int top)
{
API_FCT(request_shutdown);
API_FCT(get_server_status);
API_FCT(get_server_uptime);
+ API_FCT(get_server_max_lag);
API_FCT(get_worldpath);
API_FCT(is_singleplayer);
API_FCT(get_ban_list);
API_FCT(get_ban_description);
API_FCT(ban_player);
- API_FCT(kick_player);
+ API_FCT(disconnect_player);
+ API_FCT(remove_player);
API_FCT(unban_player_or_ip);
API_FCT(notify_authentication_modified);
+
+ API_FCT(do_async_callback);
+ API_FCT(register_async_dofile);
+ API_FCT(serialize_roundtrip);
+ }
+
+ void ModApiServer::InitializeAsync(lua_State *L, int top)
+ {
+ API_FCT(get_worldpath);
+ API_FCT(is_singleplayer);
+
+ API_FCT(get_current_modname);
+ API_FCT(get_modpath);
+ API_FCT(get_modnames);
}
#include "util/hex.h"
#include "util/sha1.h"
#include "util/png.h"
- #include <algorithm>
#include <cstdio>
// log([level,] text)
return 1;
}
- // get_dig_params(groups, tool_capabilities)
+ // get_dig_params(groups, tool_capabilities[, wear])
int ModApiUtil::l_get_dig_params(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
ItemGroupList groups;
read_groups(L, 1, groups);
ToolCapabilities tp = read_tool_capabilities(L, 2);
- push_dig_params(L, getDigParams(groups, &tp));
+ if (lua_isnoneornil(L, 3)) {
+ push_dig_params(L, getDigParams(groups, &tp));
+ } else {
+ u16 wear = readParam<int>(L, 3);
+ push_dig_params(L, getDigParams(groups, &tp, wear));
+ }
return 1;
}
- // get_hit_params(groups, tool_capabilities[, time_from_last_punch])
+ // get_hit_params(groups, tool_capabilities[, time_from_last_punch, [, wear]])
int ModApiUtil::l_get_hit_params(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
std::unordered_map<std::string, int> groups;
read_groups(L, 1, groups);
ToolCapabilities tp = read_tool_capabilities(L, 2);
- if(lua_isnoneornil(L, 3))
- push_hit_params(L, getHitParams(groups, &tp));
- else
- push_hit_params(L, getHitParams(groups, &tp, readParam<float>(L, 3)));
+ float time_from_last_punch = readParam<float>(L, 3, 1000000);
+ int wear = readParam<int>(L, 4, 0);
+ push_hit_params(L, getHitParams(groups, &tp,
+ time_from_last_punch, wear));
return 1;
}
return 1;
}
+ // rmdir(path, recursive)
+ int ModApiUtil::l_rmdir(lua_State *L)
+ {
+ NO_MAP_LOCK_REQUIRED;
+ const char *path = luaL_checkstring(L, 1);
+ CHECK_SECURE_PATH(L, path, true);
+
+ bool recursive = readParam<bool>(L, 2, false);
+
+ if (recursive)
+ lua_pushboolean(L, fs::RecursiveDelete(path));
+ else
+ lua_pushboolean(L, fs::DeleteSingleFileOrEmptyDirectory(path));
+
+ return 1;
+ }
+
+ // cpdir(source, destination)
+ int ModApiUtil::l_cpdir(lua_State *L)
+ {
+ NO_MAP_LOCK_REQUIRED;
+ const char *source = luaL_checkstring(L, 1);
+ const char *destination = luaL_checkstring(L, 2);
+ CHECK_SECURE_PATH(L, source, false);
+ CHECK_SECURE_PATH(L, destination, true);
+
+ lua_pushboolean(L, fs::CopyDir(source, destination));
+ return 1;
+ }
+
+ // mpdir(source, destination)
+ int ModApiUtil::l_mvdir(lua_State *L)
+ {
+ NO_MAP_LOCK_REQUIRED;
+ const char *source = luaL_checkstring(L, 1);
+ const char *destination = luaL_checkstring(L, 2);
+ CHECK_SECURE_PATH(L, source, true);
+ CHECK_SECURE_PATH(L, destination, true);
+
+ lua_pushboolean(L, fs::MoveDir(source, destination));
+ return 1;
+ }
+
// get_dir_list(path, is_dir)
int ModApiUtil::l_get_dir_list(lua_State *L)
{
return 1;
}
- // We have to make sure that this function is being called directly by
- // a mod, otherwise a malicious mod could override this function and
- // steal its return value.
- lua_Debug info;
- // Make sure there's only one item below this function on the stack...
- if (lua_getstack(L, 2, &info)) {
- return 0;
- }
- FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed");
- FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed");
- // ...and that that item is the main file scope.
- if (strcmp(info.what, "main") != 0) {
- return 0;
- }
-
- // Get mod name
- lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
- if (!lua_isstring(L, -1)) {
- return 0;
- }
-
- // Check secure.trusted_mods
- std::string mod_name = readParam<std::string>(L, -1);
- std::string trusted_mods = g_settings->get("secure.trusted_mods");
- trusted_mods.erase(std::remove_if(trusted_mods.begin(),
- trusted_mods.end(), static_cast<int(*)(int)>(&std::isspace)),
- trusted_mods.end());
- std::vector<std::string> mod_list = str_split(trusted_mods, ',');
- if (std::find(mod_list.begin(), mod_list.end(), mod_name) ==
- mod_list.end()) {
+ if (!ScriptApiSecurity::checkWhitelisted(L, "secure.trusted_mods")) {
return 0;
}
API_FCT(decompress);
API_FCT(mkdir);
+ API_FCT(rmdir);
+ API_FCT(cpdir);
+ API_FCT(mvdir);
API_FCT(get_dir_list);
API_FCT(safe_file_write);
API_FCT(compress);
API_FCT(decompress);
+ API_FCT(request_insecure_environment);
+
API_FCT(encode_base64);
API_FCT(decode_base64);
API_FCT(sha1);
API_FCT(colorspec_to_colorstring);
API_FCT(colorspec_to_bytes);
+
+ LuaSettings::create(L, g_settings, g_settings_path);
+ lua_setfield(L, top, "settings");
}
void ModApiUtil::InitializeAsync(lua_State *L, int top)
API_FCT(decompress);
API_FCT(mkdir);
+ API_FCT(rmdir);
+ API_FCT(cpdir);
+ API_FCT(mvdir);
API_FCT(get_dir_list);
+ API_FCT(safe_file_write);
+
+ API_FCT(request_insecure_environment);
API_FCT(encode_base64);
API_FCT(decode_base64);
API_FCT(colorspec_to_colorstring);
API_FCT(colorspec_to_bytes);
+ API_FCT(encode_png);
+
API_FCT(get_last_run_mod);
API_FCT(set_last_run_mod);
ServerEnvironment::ServerEnvironment(ServerMap *map,
ServerScripting *scriptIface, Server *server,
- const std::string &path_world):
+ const std::string &path_world, MetricsBackend *mb):
Environment(server),
m_map(map),
m_script(scriptIface),
m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
+
+ m_step_time_counter = mb->addCounter(
+ "minetest_env_step_time", "Time spent in environment step (in microseconds)");
+
+ m_active_block_gauge = mb->addGauge(
+ "minetest_env_active_blocks", "Number of active blocks");
+
+ m_active_object_gauge = mb->addGauge(
+ "minetest_env_active_objects", "Number of active objects");
}
ServerEnvironment::~ServerEnvironment()
void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
const std::string &str_reason, bool reconnect)
{
- for (RemotePlayer *player : m_players) {
- m_server->DenyAccessVerCompliant(player->getPeerId(),
- player->protocol_version, reason, str_reason, reconnect);
- }
+ for (RemotePlayer *player : m_players)
+ m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect);
}
void ServerEnvironment::saveLoadedPlayers(bool force)
for (ActiveABM &aabm : *m_aabms[c]) {
if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y))
continue;
-
+
if (myrand() % aabm.chance != 0)
continue;
// Tell the object about removal
obj->removingFromEnvironment();
// Deregister in scripting api
- m_script->removeObjectReference(obj);
+ m_script->removeObjectReference(dynamic_cast<ActiveObject *>(obj));
// Delete active object
if (obj->environmentDeletes())
void ServerEnvironment::step(float dtime)
{
ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
+ const auto start_time = porting::getTimeUs();
+
/* Step time of day */
stepTimeOfDay(dtime);
m_active_blocks.update(players, active_block_range, active_object_range,
blocks_removed, blocks_added);
+ m_active_block_gauge->set(m_active_blocks.size());
+
/*
Handle removed blocks
*/
*/
m_script->environment_Step(dtime);
+ m_script->stepAsync();
+
/*
Step active objects
*/
send_recommended = true;
}
- auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) {
+ u32 object_count = 0;
+
+ auto cb_state = [&] (ServerActiveObject *obj) {
if (obj->isGone())
return;
+ object_count++;
// Step object
obj->step(dtime, send_recommended);
obj->dumpAOMessagesToQueue(m_active_object_messages);
};
m_ao_manager.step(dtime, cb_state);
+
+ m_active_object_gauge->set(object_count);
}
/*
// Send outdated detached inventories
m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
+
+ const auto end_time = porting::getTimeUs();
+ m_step_time_counter->increment(end_time - start_time);
}
ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos)
}
// Register reference in scripting api (must be done before post-init)
- m_script->addObjectReference(object);
+ m_script->addObjectReference(dynamic_cast<ActiveObject *>(object));
// Post-initialize object
object->addedToEnvironment(dtime_s);
// Tell the object about removal
obj->removingFromEnvironment();
// Deregister in scripting api
- m_script->removeObjectReference(obj);
+ m_script->removeObjectReference(dynamic_cast<ActiveObject *>(obj));
// Delete
if (obj->environmentDeletes())
// Tell the object about removal
obj->removingFromEnvironment();
// Deregister in scripting api
- m_script->removeObjectReference(obj);
+ m_script->removeObjectReference(dynamic_cast<ActiveObject *>(obj));
// Delete active object
if (obj->environmentDeletes())
#include "gamedef.h"
#include "modchannels.h"
#include "content/mods.h"
+ #include "database/database-dummy.h"
#include "util/numeric.h"
#include "porting.h"
~TestGameDef();
IItemDefManager *getItemDefManager() { return m_itemdef; }
+ IWritableItemDefManager *getWritableItemDefManager() { return m_itemdef; }
const NodeDefManager *getNodeDefManager() { return m_nodedef; }
+ NodeDefManager *getWritableNodeDefManager() { return m_nodedef; }
ICraftDefManager *getCraftDefManager() { return m_craftdef; }
ITextureSource *getTextureSource() { return m_texturesrc; }
IShaderSource *getShaderSource() { return m_shadersrc; }
scene::ISceneManager *getSceneManager() { return m_scenemgr; }
IRollbackManager *getRollbackManager() { return m_rollbackmgr; }
EmergeManager *getEmergeManager() { return m_emergemgr; }
+ ModMetadataDatabase *getModStorageDatabase() { return m_mod_storage_database; }
scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; }
bool checkLocalPrivilege(const std::string &priv) { return false; }
return testmodspec;
}
virtual const ModSpec* getModSpec(const std::string &modname) const { return NULL; }
- virtual std::string getModStoragePath() const { return "."; }
virtual bool registerModStorage(ModMetadata *meta) { return true; }
virtual void unregisterModStorage(const std::string &name) {}
bool joinModChannel(const std::string &channel);
}
private:
- IItemDefManager *m_itemdef = nullptr;
- const NodeDefManager *m_nodedef = nullptr;
+ IWritableItemDefManager *m_itemdef = nullptr;
+ NodeDefManager *m_nodedef = nullptr;
ICraftDefManager *m_craftdef = nullptr;
ITextureSource *m_texturesrc = nullptr;
IShaderSource *m_shadersrc = nullptr;
scene::ISceneManager *m_scenemgr = nullptr;
IRollbackManager *m_rollbackmgr = nullptr;
EmergeManager *m_emergemgr = nullptr;
+ ModMetadataDatabase *m_mod_storage_database = nullptr;
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
};
TestGameDef::TestGameDef() :
+ m_mod_storage_database(new Database_Dummy()),
m_modchannel_mgr(new ModChannelMgr())
{
m_itemdef = createItemDefManager();
{
delete m_itemdef;
delete m_nodedef;
+ delete m_mod_storage_database;
}
#!/bin/bash
set -e
-CORE_GIT=https://github.com/minetest/minetest
+CORE_GIT=https://github.com/EliasFleckenstein03/dragonfireclient
CORE_BRANCH=master
-CORE_NAME=minetest
-GAME_GIT=https://github.com/minetest/minetest_game
+CORE_NAME=dragonfireclient
+GAME_GIT=https://git.minetest.land/MineClone2/MineClone2
GAME_BRANCH=master
-GAME_NAME=minetest_game
+GAME_NAME=MineClone2
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ $# -ne 1 ]; then
libdir=$builddir/libs
# Test which win32 compiler is present
- which i686-w64-mingw32-gcc &>/dev/null &&
- toolchain_file=$dir/toolchain_i686-w64-mingw32.cmake
- which i686-w64-mingw32-gcc-posix &>/dev/null &&
- toolchain_file=$dir/toolchain_i686-w64-mingw32-posix.cmake
+ command -v i686-w64-mingw32-gcc >/dev/null &&
+ compiler=i686-w64-mingw32-gcc
+ command -v i686-w64-mingw32-gcc-posix >/dev/null &&
+ compiler=i686-w64-mingw32-gcc-posix
- if [ -z "$toolchain_file" ]; then
- echo "Unable to determine which mingw32 compiler to use"
+ if [ -z "$compiler" ]; then
+ echo "Unable to determine which MinGW compiler to use"
exit 1
fi
+ toolchain_file=$dir/toolchain_${compiler/-gcc/}.cmake
echo "Using $toolchain_file"
- irrlicht_version=1.9.0mt3
- ogg_version=1.3.4
+ # Try to find runtime DLLs in various paths (varies by distribution, sigh)
+ tmp=$(dirname "$(command -v $compiler)")/..
+ runtime_dlls=
+ for name in lib{gcc_,stdc++-,winpthread-}'*'.dll; do
+ for dir in $tmp/i686-w64-mingw32/{bin,lib} $tmp/lib/gcc/i686-w64-mingw32/*; do
+ [ -d "$dir" ] || continue
+ file=$(echo $dir/$name)
+ [ -f "$file" ] && { runtime_dlls+="$file;"; break; }
+ done
+ done
+ [ -z "$runtime_dlls" ] &&
+ echo "The compiler runtime DLLs could not be found, they might be missing in the final package."
+
+ # Get stuff
+ irrlicht_version=1.9.0mt5
+ ogg_version=1.3.5
+ openal_version=1.21.1
vorbis_version=1.3.7
- curl_version=7.76.1
+ curl_version=7.81.0
gettext_version=0.20.1
- freetype_version=2.10.4
- sqlite3_version=3.35.5
+ freetype_version=2.11.1
+ sqlite3_version=3.37.2
luajit_version=2.1.0-beta3
leveldb_version=1.23
zlib_version=1.2.11
- zstd_version=1.4.9
+ zstd_version=1.5.2
mkdir -p $libdir
fi
}
- # Get stuff
+ # 'dw2' just points to rebuilt versions after a toolchain change
+ # this distinction should be gotten rid of next time
+
cd $libdir
download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win32.zip" irrlicht-$irrlicht_version.zip
- download "http://minetest.kitsunemimi.pw/zlib-$zlib_version-win32.zip"
+ download "http://minetest.kitsunemimi.pw/dw2/zlib-$zlib_version-win32.zip"
download "http://minetest.kitsunemimi.pw/zstd-$zstd_version-win32.zip"
download "http://minetest.kitsunemimi.pw/libogg-$ogg_version-win32.zip"
- download "http://minetest.kitsunemimi.pw/libvorbis-$vorbis_version-win32.zip"
+ download "http://minetest.kitsunemimi.pw/dw2/libvorbis-$vorbis_version-win32.zip"
download "http://minetest.kitsunemimi.pw/curl-$curl_version-win32.zip"
- download "http://minetest.kitsunemimi.pw/gettext-$gettext_version-win32.zip"
+ download "http://minetest.kitsunemimi.pw/dw2/gettext-$gettext_version-win32.zip"
download "http://minetest.kitsunemimi.pw/freetype2-$freetype_version-win32.zip" freetype-$freetype_version.zip
download "http://minetest.kitsunemimi.pw/sqlite3-$sqlite3_version-win32.zip"
- download "http://minetest.kitsunemimi.pw/luajit-$luajit_version-win32.zip"
- download "http://minetest.kitsunemimi.pw/libleveldb-$leveldb_version-win32.zip" leveldb-$leveldb_version.zip
- download "http://minetest.kitsunemimi.pw/openal_stripped.zip" '' unzip_nofolder
+ download "http://minetest.kitsunemimi.pw/dw2/luajit-$luajit_version-win32.zip"
+ download "http://minetest.kitsunemimi.pw/dw2/libleveldb-$leveldb_version-win32.zip" leveldb-$leveldb_version.zip
+ download "http://minetest.kitsunemimi.pw/openal-soft-$openal_version-win32.zip"
# Set source dir, downloading Minetest as needed
if [ -n "$EXISTING_MINETEST_DIR" ]; then
sourcedir="$( cd "$EXISTING_MINETEST_DIR" && pwd )"
else
+ cd $builddir
sourcedir=$PWD/$CORE_NAME
[ -d $CORE_NAME ] && { pushd $CORE_NAME; git pull; popd; } || \
git clone -b $CORE_BRANCH $CORE_GIT $CORE_NAME
if [ -z "$NO_MINETEST_GAME" ]; then
+ cd $sourcedir
[ -d games/$GAME_NAME ] && { pushd games/$GAME_NAME; git pull; popd; } || \
git clone -b $GAME_BRANCH $GAME_GIT games/$GAME_NAME
fi
# Build the thing
cd $builddir
[ -d build ] && rm -rf build
- mkdir build
- cd build
irr_dlls=$(echo $libdir/irrlicht/lib/*.dll | tr ' ' ';')
vorbis_dlls=$(echo $libdir/libvorbis/bin/libvorbis{,file}-*.dll | tr ' ' ';')
gettext_dlls=$(echo $libdir/gettext/bin/lib{intl,iconv}-*.dll | tr ' ' ';')
- cmake -S $sourcedir -B . \
+ cmake -S $sourcedir -B build \
-DCMAKE_TOOLCHAIN_FILE=$toolchain_file \
-DCMAKE_INSTALL_PREFIX=/tmp \
-DVERSION_EXTRA=$git_hash \
-DBUILD_CLIENT=1 -DBUILD_SERVER=0 \
+ -DEXTRA_DLL="$runtime_dlls" \
\
-DENABLE_SOUND=1 \
-DENABLE_CURL=1 \
-DENABLE_GETTEXT=1 \
- -DENABLE_FREETYPE=1 \
-DENABLE_LEVELDB=1 \
\
-DCMAKE_PREFIX_PATH=$libdir/irrlicht \
-DVORBIS_DLL="$vorbis_dlls" \
-DVORBISFILE_LIBRARY=$libdir/libvorbis/lib/libvorbisfile.dll.a \
\
- -DOPENAL_INCLUDE_DIR=$libdir/openal_stripped/include/AL \
- -DOPENAL_LIBRARY=$libdir/openal_stripped/lib/libOpenAL32.dll.a \
- -DOPENAL_DLL=$libdir/openal_stripped/bin/OpenAL32.dll \
+ -DOPENAL_INCLUDE_DIR=$libdir/openal/include/AL \
+ -DOPENAL_LIBRARY=$libdir/openal/lib/libOpenAL32.dll.a \
+ -DOPENAL_DLL=$libdir/openal/bin/OpenAL32.dll \
\
-DCURL_DLL=$libdir/curl/bin/libcurl-4.dll \
-DCURL_INCLUDE_DIR=$libdir/curl/include \
-DCURL_LIBRARY=$libdir/curl/lib/libcurl.dll.a \
\
- -DGETTEXT_MSGFMT=`which msgfmt` \
+ -DGETTEXT_MSGFMT=`command -v msgfmt` \
-DGETTEXT_DLL="$gettext_dlls" \
-DGETTEXT_INCLUDE_DIR=$libdir/gettext/include \
-DGETTEXT_LIBRARY=$libdir/gettext/lib/libintl.dll.a \
-DLEVELDB_LIBRARY=$libdir/leveldb/lib/libleveldb.dll.a \
-DLEVELDB_DLL=$libdir/leveldb/bin/libleveldb.dll
- make -j$(nproc)
+ cmake --build build -j$(nproc)
- [ -z "$NO_PACKAGE" ] && make package
+ [ -z "$NO_PACKAGE" ] && cmake --build build --target package
exit 0
# EOF
#!/bin/bash
set -e
-CORE_GIT=https://github.com/minetest/minetest
+CORE_GIT=https://github.com/EliasFleckenstein03/dragonfireclient
CORE_BRANCH=master
-CORE_NAME=minetest
-GAME_GIT=https://github.com/minetest/minetest_game
+CORE_NAME=dragonfireclient
+GAME_GIT=https://git.minetest.land/MineClone2/MineClone2
GAME_BRANCH=master
-GAME_NAME=minetest_game
+GAME_NAME=MineClone2
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ $# -ne 1 ]; then
libdir=$builddir/libs
# Test which win64 compiler is present
- which x86_64-w64-mingw32-gcc &>/dev/null &&
- toolchain_file=$dir/toolchain_x86_64-w64-mingw32.cmake
- which x86_64-w64-mingw32-gcc-posix &>/dev/null &&
- toolchain_file=$dir/toolchain_x86_64-w64-mingw32-posix.cmake
+ command -v x86_64-w64-mingw32-gcc >/dev/null &&
+ compiler=x86_64-w64-mingw32-gcc
+ command -v x86_64-w64-mingw32-gcc-posix >/dev/null &&
+ compiler=x86_64-w64-mingw32-gcc-posix
- if [ -z "$toolchain_file" ]; then
- echo "Unable to determine which mingw32 compiler to use"
+ if [ -z "$compiler" ]; then
+ echo "Unable to determine which MinGW compiler to use"
exit 1
fi
+ toolchain_file=$dir/toolchain_${compiler/-gcc/}.cmake
echo "Using $toolchain_file"
- irrlicht_version=1.9.0mt3
- ogg_version=1.3.4
+ # Try to find runtime DLLs in various paths (varies by distribution, sigh)
+ tmp=$(dirname "$(command -v $compiler)")/..
+ runtime_dlls=
+ for name in lib{gcc_,stdc++-,winpthread-}'*'.dll; do
+ for dir in $tmp/x86_64-w64-mingw32/{bin,lib} $tmp/lib/gcc/x86_64-w64-mingw32/*; do
+ [ -d "$dir" ] || continue
+ file=$(echo $dir/$name)
+ [ -f "$file" ] && { runtime_dlls+="$file;"; break; }
+ done
+ done
+ [ -z "$runtime_dlls" ] &&
+ echo "The compiler runtime DLLs could not be found, they might be missing in the final package."
+
+ # Get stuff
+ irrlicht_version=1.9.0mt5
+ ogg_version=1.3.5
+ openal_version=1.21.1
vorbis_version=1.3.7
- curl_version=7.76.1
+ curl_version=7.81.0
gettext_version=0.20.1
- freetype_version=2.10.4
- sqlite3_version=3.35.5
+ freetype_version=2.11.1
+ sqlite3_version=3.37.2
luajit_version=2.1.0-beta3
leveldb_version=1.23
zlib_version=1.2.11
- zstd_version=1.4.9
+ zstd_version=1.5.2
mkdir -p $libdir
fi
}
- # Get stuff
cd $libdir
download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win64.zip" irrlicht-$irrlicht_version.zip
download "http://minetest.kitsunemimi.pw/zlib-$zlib_version-win64.zip"
download "http://minetest.kitsunemimi.pw/sqlite3-$sqlite3_version-win64.zip"
download "http://minetest.kitsunemimi.pw/luajit-$luajit_version-win64.zip"
download "http://minetest.kitsunemimi.pw/libleveldb-$leveldb_version-win64.zip" leveldb-$leveldb_version.zip
- download "http://minetest.kitsunemimi.pw/openal_stripped64.zip" 'openal_stripped.zip' unzip_nofolder
+ download "http://minetest.kitsunemimi.pw/openal-soft-$openal_version-win64.zip"
# Set source dir, downloading Minetest as needed
if [ -n "$EXISTING_MINETEST_DIR" ]; then
sourcedir="$( cd "$EXISTING_MINETEST_DIR" && pwd )"
else
+ cd $builddir
sourcedir=$PWD/$CORE_NAME
[ -d $CORE_NAME ] && { pushd $CORE_NAME; git pull; popd; } || \
git clone -b $CORE_BRANCH $CORE_GIT $CORE_NAME
if [ -z "$NO_MINETEST_GAME" ]; then
+ cd $sourcedir
[ -d games/$GAME_NAME ] && { pushd games/$GAME_NAME; git pull; popd; } || \
git clone -b $GAME_BRANCH $GAME_GIT games/$GAME_NAME
fi
# Build the thing
cd $builddir
[ -d build ] && rm -rf build
- mkdir build
- cd build
irr_dlls=$(echo $libdir/irrlicht/lib/*.dll | tr ' ' ';')
vorbis_dlls=$(echo $libdir/libvorbis/bin/libvorbis{,file}-*.dll | tr ' ' ';')
gettext_dlls=$(echo $libdir/gettext/bin/lib{intl,iconv}-*.dll | tr ' ' ';')
- cmake -S $sourcedir -B . \
+ cmake -S $sourcedir -B build \
-DCMAKE_TOOLCHAIN_FILE=$toolchain_file \
-DCMAKE_INSTALL_PREFIX=/tmp \
-DVERSION_EXTRA=$git_hash \
-DBUILD_CLIENT=1 -DBUILD_SERVER=0 \
+ -DEXTRA_DLL="$runtime_dlls" \
\
-DENABLE_SOUND=1 \
-DENABLE_CURL=1 \
-DENABLE_GETTEXT=1 \
- -DENABLE_FREETYPE=1 \
-DENABLE_LEVELDB=1 \
\
-DCMAKE_PREFIX_PATH=$libdir/irrlicht \
-DVORBIS_DLL="$vorbis_dlls" \
-DVORBISFILE_LIBRARY=$libdir/libvorbis/lib/libvorbisfile.dll.a \
\
- -DOPENAL_INCLUDE_DIR=$libdir/openal_stripped/include/AL \
- -DOPENAL_LIBRARY=$libdir/openal_stripped/lib/libOpenAL32.dll.a \
- -DOPENAL_DLL=$libdir/openal_stripped/bin/OpenAL32.dll \
+ -DOPENAL_INCLUDE_DIR=$libdir/openal/include/AL \
+ -DOPENAL_LIBRARY=$libdir/openal/lib/libOpenAL32.dll.a \
+ -DOPENAL_DLL=$libdir/openal/bin/OpenAL32.dll \
\
-DCURL_DLL=$libdir/curl/bin/libcurl-4.dll \
-DCURL_INCLUDE_DIR=$libdir/curl/include \
-DCURL_LIBRARY=$libdir/curl/lib/libcurl.dll.a \
\
- -DGETTEXT_MSGFMT=`which msgfmt` \
+ -DGETTEXT_MSGFMT=`command -v msgfmt` \
-DGETTEXT_DLL="$gettext_dlls" \
-DGETTEXT_INCLUDE_DIR=$libdir/gettext/include \
-DGETTEXT_LIBRARY=$libdir/gettext/lib/libintl.dll.a \
-DLEVELDB_LIBRARY=$libdir/leveldb/lib/libleveldb.dll.a \
-DLEVELDB_DLL=$libdir/leveldb/bin/libleveldb.dll
- make -j$(nproc)
+ cmake --build build -j$(nproc)
- [ -z "$NO_PACKAGE" ] && make package
+ [ -z "$NO_PACKAGE" ] && cmake --build build --target package
exit 0
# EOF