From a76e7698b21d51594d82e329d3825ee86e653295 Mon Sep 17 00:00:00 2001 From: Rogier Date: Mon, 25 Jul 2016 18:43:15 +0200 Subject: Make minetest abort on lua panic Currently, lua does a regular exit() after a lua panic, which can make a problem hard to debug. Invoking FATAL_ERROR() instead will print some useful information, and abort() minetest, so that a debugger can be used to analyze the situation. --- src/script/cpp_api/s_base.cpp | 13 +++++++++++++ src/script/cpp_api/s_base.h | 2 ++ 2 files changed, 15 insertions(+) (limited to 'src/script/cpp_api') diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 679a517ee..cbe5735a7 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -40,6 +40,7 @@ extern "C" { #include #include +#include class ModNameStorer @@ -77,6 +78,8 @@ ScriptApiBase::ScriptApiBase() : m_luastack = luaL_newstate(); FATAL_ERROR_IF(!m_luastack, "luaL_newstate() failed"); + lua_atpanic(m_luastack, &luaPanic); + luaL_openlibs(m_luastack); // Make the ScriptApiBase* accessible to ModApiBase @@ -120,6 +123,16 @@ ScriptApiBase::~ScriptApiBase() lua_close(m_luastack); } +int ScriptApiBase::luaPanic(lua_State *L) +{ + std::ostringstream oss; + oss << "LUA PANIC: unprotected error in call to Lua API (" + << lua_tostring(L, -1) << ")"; + FATAL_ERROR(oss.str().c_str()); + // NOTREACHED + return 0; +} + void ScriptApiBase::loadMod(const std::string &script_path, const std::string &mod_name) { diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index f52474f00..c27235255 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -118,6 +118,8 @@ protected: #endif private: + static int luaPanic(lua_State *L); + lua_State* m_luastack; Server* m_server; -- cgit v1.2.3 From f5070b44542edba97a20445c3774c221077cc4a2 Mon Sep 17 00:00:00 2001 From: sapier Date: Sun, 15 Jan 2017 13:36:53 +0100 Subject: Added lua tracebacks to some errors where you have been blind to what… (#5043) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added lua tracebacks to some errors where you have been blind to what actually went wrong --- src/emerge.cpp | 2 +- src/script/common/c_converter.cpp | 4 +++- src/script/cpp_api/s_env.cpp | 12 +++++++++--- src/server.cpp | 5 +++-- 4 files changed, 16 insertions(+), 7 deletions(-) (limited to 'src/script/cpp_api') diff --git a/src/emerge.cpp b/src/emerge.cpp index 3f0a46010..1c9719c48 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -564,7 +564,7 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, m_server->getScriptIface()->environment_OnGenerated( minp, maxp, m_mapgen->blockseed); } catch (LuaError &e) { - m_server->setAsyncFatalError("Lua: " + std::string(e.what())); + m_server->setAsyncFatalError("Lua: finishGen" + std::string(e.what())); } EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index f36298915..fc516d56a 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -26,15 +26,17 @@ extern "C" { #include "util/serialize.h" #include "util/string.h" #include "common/c_converter.h" +#include "common/c_internal.h" #include "constants.h" #define CHECK_TYPE(index, name, type) do { \ int t = lua_type(L, (index)); \ if (t != (type)) { \ + std::string traceback = script_get_backtrace(L); \ throw LuaError(std::string("Invalid ") + (name) + \ " (expected " + lua_typename(L, (type)) + \ - " got " + lua_typename(L, t) + ")."); \ + " got " + lua_typename(L, t) + ").\n" + traceback); \ } \ } while(0) #define CHECK_POS_COORD(name) CHECK_TYPE(-1, "position coordinate '" name "'", LUA_TNUMBER) diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 913d8539d..b1404bf22 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -54,7 +54,9 @@ void ScriptApiEnv::environment_Step(float dtime) try { runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { - getServer()->setAsyncFatalError(e.what()); + getServer()->setAsyncFatalError( + std::string("environment_Step: ") + e.what() + "\n" + + script_get_backtrace(L)); } } @@ -75,7 +77,9 @@ void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &t try { runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { - getServer()->setAsyncFatalError(e.what()); + getServer()->setAsyncFatalError( + std::string("player_event: ") + e.what() + "\n" + + script_get_backtrace(L) ); } } @@ -237,7 +241,9 @@ void ScriptApiEnv::on_emerge_area_completion( try { PCALL_RES(lua_pcall(L, 4, 0, error_handler)); } catch (LuaError &e) { - server->setAsyncFatalError(e.what()); + server->setAsyncFatalError( + std::string("on_emerge_area_completion: ") + e.what() + "\n" + + script_get_backtrace(L)); } lua_pop(L, 1); // Pop error handler diff --git a/src/server.cpp b/src/server.cpp index 7380d37c2..29dce5a4a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -107,7 +107,8 @@ void *ServerThread::run() } catch (con::ConnectionBindFailed &e) { m_server->setAsyncFatalError(e.what()); } catch (LuaError &e) { - m_server->setAsyncFatalError("Lua: " + std::string(e.what())); + m_server->setAsyncFatalError( + "ServerThread::run Lua: " + std::string(e.what())); } } @@ -487,7 +488,7 @@ void Server::step(float dtime) g_settings->get("kick_msg_crash"), g_settings->getBool("ask_reconnect_on_crash")); } - throw ServerError(async_err); + throw ServerError("AsyncErr: " + async_err); } } -- cgit v1.2.3 From 39123fcce5e983e83234971571b3cfaa458970b5 Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sat, 21 Jan 2017 22:05:54 +0100 Subject: Remove os.exit from the Lua secure sandbox (#5090) os.exit will exit not using proper resource liberation paths. Mods should call the proper exit mod using our API --- src/script/cpp_api/s_security.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src/script/cpp_api') diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 1b1f148cd..be2b884cc 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -99,7 +99,6 @@ void ScriptApiSecurity::initializeSecurity() "clock", "date", "difftime", - "exit", "getenv", "setlocale", "time", -- cgit v1.2.3 From d04d8aba7029a2501854a2838fd282b81358a54e Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Thu, 12 Jan 2017 15:46:30 +0100 Subject: Add hardware node coloring. Includes: - Increase ContentFeatures serialization version - Color property and palettes for nodes - paramtype2 = "color", "colored facedir" or "colored wallmounted" --- client/shaders/nodes_shader/opengl_vertex.glsl | 43 +- .../water_surface_shader/opengl_vertex.glsl | 45 +- client/shaders/wielded_shader/opengl_vertex.glsl | 1 - doc/lua_api.txt | 27 +- src/client/tile.cpp | 3 - src/client/tile.h | 34 +- src/clientenvironment.cpp | 4 +- src/content_mapblock.cpp | 326 ++++++++----- src/game.cpp | 30 +- src/mapblock_mesh.cpp | 263 ++++++----- src/mapblock_mesh.h | 57 ++- src/mapnode.cpp | 20 +- src/mapnode.h | 12 +- src/mesh.cpp | 47 +- src/mesh.h | 14 +- src/minimap.cpp | 31 +- src/minimap.h | 4 +- src/network/networkprotocol.h | 5 +- src/nodedef.cpp | 513 +++++++++++++++++---- src/nodedef.h | 108 +++-- src/particles.cpp | 65 ++- src/particles.h | 19 +- src/script/common/c_content.cpp | 18 + src/script/cpp_api/s_node.cpp | 3 + src/shader.cpp | 2 +- src/wieldmesh.cpp | 62 ++- src/wieldmesh.h | 5 + 27 files changed, 1207 insertions(+), 554 deletions(-) (limited to 'src/script/cpp_api') diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 44c48cc4c..3ac79c26d 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -1,7 +1,8 @@ uniform mat4 mWorldViewProj; uniform mat4 mWorld; -uniform float dayNightRatio; +// Color of the light emitted by the sun. +uniform vec3 dayLight; uniform vec3 eyePosition; uniform float animationTimer; @@ -14,6 +15,8 @@ varying vec3 tsEyeVec; varying vec3 tsLightVec; varying float area_enable_parallax; +// Color of the light emitted by the light sources. +const vec3 artificialLight = vec3(1.04, 1.04, 1.04); const float e = 2.718281828459; const float BS = 10.0; @@ -119,31 +122,23 @@ float disp_z; v.z = dot(eyeVec, normal); tsEyeVec = normalize (v); + // Calculate color. + // Red, green and blue components are pre-multiplied with + // the brightness, so now we have to multiply these + // colors with the color of the incoming light. + // The pre-baked colors are halved to prevent overflow. vec4 color; - float day = gl_Color.r; - float night = gl_Color.g; - float light_source = gl_Color.b; - - float rg = mix(night, day, dayNightRatio); - rg += light_source * 2.5; // Make light sources brighter - float b = rg; - - // Moonlight is blue - b += (day - night) / 13.0; - rg -= (day - night) / 23.0; - + // The alpha gives the ratio of sunlight in the incoming light. + float nightRatio = 1 - gl_Color.a; + color.rgb = gl_Color.rgb * (gl_Color.a * dayLight.rgb + + nightRatio * artificialLight.rgb) * 2; + color.a = 1; + // Emphase blue a bit in darker places // See C++ implementation in mapblock_mesh.cpp finalColorBlend() - b += max(0.0, (1.0 - abs(b - 0.13) / 0.17) * 0.025); - - // Artificial light is yellow-ish - // See C++ implementation in mapblock_mesh.cpp finalColorBlend() - rg += max(0.0, (1.0 - abs(rg - 0.85) / 0.15) * 0.065); - - color.r = rg; - color.g = rg; - color.b = b; - - color.a = gl_Color.a; + float brightness = (color.r + color.g + color.b) / 3; + color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) + + 0.07 * brightness); + gl_FrontColor = gl_BackColor = clamp(color, 0.0, 1.0); } diff --git a/client/shaders/water_surface_shader/opengl_vertex.glsl b/client/shaders/water_surface_shader/opengl_vertex.glsl index a930e7b8f..112db9bb5 100644 --- a/client/shaders/water_surface_shader/opengl_vertex.glsl +++ b/client/shaders/water_surface_shader/opengl_vertex.glsl @@ -1,7 +1,8 @@ uniform mat4 mWorldViewProj; uniform mat4 mWorld; -uniform float dayNightRatio; +// Color of the light emitted by the sun. +uniform vec3 dayLight; uniform vec3 eyePosition; uniform float animationTimer; @@ -13,6 +14,8 @@ varying vec3 lightVec; varying vec3 tsEyeVec; varying vec3 tsLightVec; +// Color of the light emitted by the light sources. +const vec3 artificialLight = vec3(1.04, 1.04, 1.04); const float e = 2.718281828459; const float BS = 10.0; @@ -112,31 +115,23 @@ void main(void) eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz; tsEyeVec = eyeVec * tbnMatrix; + // Calculate color. + // Red, green and blue components are pre-multiplied with + // the brightness, so now we have to multiply these + // colors with the color of the incoming light. + // The pre-baked colors are halved to prevent overflow. vec4 color; - float day = gl_Color.r; - float night = gl_Color.g; - float light_source = gl_Color.b; - - float rg = mix(night, day, dayNightRatio); - rg += light_source * 2.5; // Make light sources brighter - float b = rg; - - // Moonlight is blue - b += (day - night) / 13.0; - rg -= (day - night) / 23.0; - + // The alpha gives the ratio of sunlight in the incoming light. + float nightRatio = 1 - gl_Color.a; + color.rgb = gl_Color.rgb * (gl_Color.a * dayLight.rgb + + nightRatio * artificialLight.rgb) * 2; + color.a = 1; + // Emphase blue a bit in darker places // See C++ implementation in mapblock_mesh.cpp finalColorBlend() - b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025); - - // Artificial light is yellow-ish - // See C++ implementation in mapblock_mesh.cpp finalColorBlend() - rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065); - - color.r = rg; - color.g = rg; - color.b = b; - - color.a = gl_Color.a; - gl_FrontColor = gl_BackColor = clamp(color,0.0,1.0); + float brightness = (color.r + color.g + color.b) / 3; + color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) + + 0.07 * brightness); + + gl_FrontColor = gl_BackColor = clamp(color, 0.0, 1.0); } diff --git a/client/shaders/wielded_shader/opengl_vertex.glsl b/client/shaders/wielded_shader/opengl_vertex.glsl index 86c626896..9f05b833a 100644 --- a/client/shaders/wielded_shader/opengl_vertex.glsl +++ b/client/shaders/wielded_shader/opengl_vertex.glsl @@ -1,7 +1,6 @@ uniform mat4 mWorldViewProj; uniform mat4 mWorld; -uniform float dayNightRatio; uniform vec3 eyePosition; uniform float animationTimer; diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e5a3362ee..2a0b72053 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -638,6 +638,19 @@ node definition: bit 4 (0x10) - Makes the plant mesh 1.4x larger bit 5 (0x20) - Moves each face randomly a small bit down (1/8 max) bits 6-7 are reserved for future use. + paramtype2 == "color" + ^ `param2` tells which color is picked from the palette. + The palette should have 256 pixels. + paramtype2 == "colorfacedir" + ^ Same as `facedir`, but with colors. + The first three bits of `param2` tells which color + is picked from the palette. + The palette should have 8 pixels. + paramtype2 == "colorwallmounted" + ^ Same as `wallmounted`, but with colors. + The first five bits of `param2` tells which color + is picked from the palette. + The palette should have 32 pixels. collision_box = { type = "fixed", fixed = { @@ -3707,6 +3720,9 @@ Definition tables when displacement mapping is used Directions are from the point of view of the tile texture, not the node it's on +* `{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. * deprecated, yet still supported field names: * `image` (name) @@ -3749,8 +3765,17 @@ Definition tables special_tiles = {tile definition 1, Tile definition 2}, --[[ ^ Special textures of node; used rarely (old field name: special_materials) ^ List can be shortened to needed length ]] - alpha = 255, + color = ColorSpec, --[[ + ^ The node's original color will be multiplied with this color. + ^ If the node has a palette, then this setting only has an effect + ^ in the inventory and on the wield item. ]] use_texture_alpha = false, -- Use texture's alpha channel + palette = "palette.png", --[[ + ^ The node's `param2` is used to select a pixel from the image + ^ (pixels are arranged from left to right and from top to bottom). + ^ The node's color will be multiplied with the selected pixel's + ^ color. Tiles can override this behavior. + ^ Only when `paramtype2` supports palettes. ]] post_effect_color = "green#0F", -- If player is inside node, see "ColorSpec" paramtype = "none", -- See "Nodes" --[[ ^ paramtype = "light" allows light to propagate from or through the node with light value diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 4d2166342..539c29445 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -378,9 +378,6 @@ public: video::ITexture* generateTextureFromMesh( const TextureFromMeshParams ¶ms); - // Generates an image from a full string like - // "stone.png^mineral_coal.png^[crack:1:0". - // Shall be called from the main thread. video::IImage* generateImage(const std::string &name); video::ITexture* getNormalTexture(const std::string &name); diff --git a/src/client/tile.h b/src/client/tile.h index 452804801..d04ab918a 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -108,6 +108,12 @@ public: const std::string &name, u32 *id = NULL) = 0; virtual IrrlichtDevice* getDevice()=0; virtual bool isKnownSourceImage(const std::string &name)=0; + /*! Generates an image from a full string like + * "stone.png^mineral_coal.png^[crack:1:0". + * Shall be called from the main thread. + * The returned Image should be dropped. + */ + virtual video::IImage* generateImage(const std::string &name)=0; virtual video::ITexture* generateTextureFromMesh( const TextureFromMeshParams ¶ms)=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0; @@ -192,7 +198,6 @@ struct TileSpec texture(NULL), normal_texture(NULL), flags_texture(NULL), - alpha(255), material_type(TILE_MATERIAL_BASIC), material_flags( //0 // <- DEBUG, Use the one below @@ -201,22 +206,30 @@ struct TileSpec shader_id(0), animation_frame_count(1), animation_frame_length_ms(0), - rotation(0) + rotation(0), + has_color(false), + color(), + emissive_light(0) { } + /*! + * Two tiles are equal if they can be appended to + * the same mesh buffer. + */ bool operator==(const TileSpec &other) const { return ( texture_id == other.texture_id && - /* texture == other.texture && */ - alpha == other.alpha && material_type == other.material_type && material_flags == other.material_flags && rotation == other.rotation ); } + /*! + * Two tiles are not equal if they must be in different mesh buffers. + */ bool operator!=(const TileSpec &other) const { return !(*this == other); @@ -233,7 +246,7 @@ struct TileSpec material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; break; case TILE_MATERIAL_LIQUID_TRANSPARENT: - material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; break; case TILE_MATERIAL_LIQUID_OPAQUE: material.MaterialType = video::EMT_SOLID; @@ -274,8 +287,6 @@ struct TileSpec video::ITexture *normal_texture; video::ITexture *flags_texture; - // Vertex alpha (when MATERIAL_ALPHA_VERTEX is used) - u8 alpha; // Material parameters u8 material_type; u8 material_flags; @@ -286,5 +297,14 @@ struct TileSpec std::vector frames; u8 rotation; + //! If true, the tile has its own color. + bool has_color; + /*! + * The color of the tile, or if the tile does not own + * a color then the color of the node owning this tile. + */ + video::SColor color; + //! This much light does the tile emit. + u8 emissive_light; }; #endif diff --git a/src/clientenvironment.cpp b/src/clientenvironment.cpp index b32a02f2d..77f53d214 100644 --- a/src/clientenvironment.cpp +++ b/src/clientenvironment.cpp @@ -334,9 +334,7 @@ void ClientEnvironment::step(float dtime) node_at_lplayer = m_map->getNodeNoEx(p); u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef()); - u8 day = light & 0xff; - u8 night = (light >> 8) & 0xff; - finalColorBlend(lplayer->light_color, day, night, day_night_ratio); + final_color_blend(&lplayer->light_color, light, day_night_ratio); } /* diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index a7134590b..742bfb1fd 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -32,28 +32,29 @@ with this program; if not, write to the Free Software Foundation, Inc., // Create a cuboid. -// collector - the MeshCollector for the resulting polygons -// box - the position and size of the box -// tiles - the tiles (materials) to use (for all 6 faces) -// tilecount - number of entries in tiles, 1<=tilecount<=6 -// c - vertex colour - used for all -// txc - texture coordinates - this is a list of texture coordinates -// for the opposite corners of each face - therefore, there -// should be (2+2)*6=24 values in the list. Alternatively, pass -// NULL to use the entire texture for each face. The order of -// the faces in the list is up-down-right-left-back-front -// (compatible with ContentFeatures). If you specified 0,0,1,1 -// for each face, that would be the same as passing NULL. +// collector - the MeshCollector for the resulting polygons +// box - the position and size of the box +// tiles - the tiles (materials) to use (for all 6 faces) +// tilecount - number of entries in tiles, 1<=tilecount<=6 +// c - colors of the cuboid's six sides +// txc - texture coordinates - this is a list of texture coordinates +// for the opposite corners of each face - therefore, there +// should be (2+2)*6=24 values in the list. Alternatively, +// pass NULL to use the entire texture for each face. The +// order of the faces in the list is up-down-right-left-back- +// front (compatible with ContentFeatures). If you specified +// 0,0,1,1 for each face, that would be the same as +// passing NULL. +// light source - if greater than zero, the box's faces will not be shaded void makeCuboid(MeshCollector *collector, const aabb3f &box, - TileSpec *tiles, int tilecount, video::SColor &c, const f32* txc) + TileSpec *tiles, int tilecount, const video::SColor *c, + const f32* txc, const u8 light_source) { assert(tilecount >= 1 && tilecount <= 6); // pre-condition v3f min = box.MinEdge; v3f max = box.MaxEdge; - - if(txc == NULL) { static const f32 txc_default[24] = { 0,0,1,1, @@ -66,38 +67,53 @@ void makeCuboid(MeshCollector *collector, const aabb3f &box, txc = txc_default; } + video::SColor c1 = c[0]; + video::SColor c2 = c[1]; + video::SColor c3 = c[2]; + video::SColor c4 = c[3]; + video::SColor c5 = c[4]; + video::SColor c6 = c[5]; + if (!light_source) { + applyFacesShading(c1, v3f(0, 1, 0)); + applyFacesShading(c2, v3f(0, -1, 0)); + applyFacesShading(c3, v3f(1, 0, 0)); + applyFacesShading(c4, v3f(-1, 0, 0)); + applyFacesShading(c5, v3f(0, 0, 1)); + applyFacesShading(c6, v3f(0, 0, -1)); + } + video::S3DVertex vertices[24] = { // up - video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]), - video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]), - video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]), - video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]), + video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c1, txc[0],txc[1]), + video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c1, txc[2],txc[1]), + video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c1, txc[2],txc[3]), + video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c1, txc[0],txc[3]), // down - video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]), - video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]), - video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]), - video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]), + video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c2, txc[4],txc[5]), + video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c2, txc[6],txc[5]), + video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c2, txc[6],txc[7]), + video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c2, txc[4],txc[7]), // right - video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]), - video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]), - video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]), - video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]), + video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c3, txc[ 8],txc[9]), + video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c3, txc[10],txc[9]), + video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c3, txc[10],txc[11]), + video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c3, txc[ 8],txc[11]), // left - video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]), - video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]), - video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]), - video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]), + video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c4, txc[12],txc[13]), + video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c4, txc[14],txc[13]), + video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c4, txc[14],txc[15]), + video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c4, txc[12],txc[15]), // back - video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]), - video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]), - video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]), - video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]), + video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c5, txc[16],txc[17]), + video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c5, txc[18],txc[17]), + video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c5, txc[18],txc[19]), + video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c5, txc[16],txc[19]), // front - video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]), - video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]), - video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]), - video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]), + video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c6, txc[20],txc[21]), + video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c6, txc[22],txc[21]), + video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c6, txc[22],txc[23]), + video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c6, txc[20],txc[23]), }; for(int i = 0; i < 6; i++) @@ -164,6 +180,31 @@ void makeCuboid(MeshCollector *collector, const aabb3f &box, } } +// Create a cuboid. +// collector - the MeshCollector for the resulting polygons +// box - the position and size of the box +// tiles - the tiles (materials) to use (for all 6 faces) +// tilecount - number of entries in tiles, 1<=tilecount<=6 +// c - color of the cuboid +// txc - texture coordinates - this is a list of texture coordinates +// for the opposite corners of each face - therefore, there +// should be (2+2)*6=24 values in the list. Alternatively, +// pass NULL to use the entire texture for each face. The +// order of the faces in the list is up-down-right-left-back- +// front (compatible with ContentFeatures). If you specified +// 0,0,1,1 for each face, that would be the same as +// passing NULL. +// light source - if greater than zero, the box's faces will not be shaded +void makeCuboid(MeshCollector *collector, const aabb3f &box, TileSpec *tiles, + int tilecount, const video::SColor &c, const f32* txc, + const u8 light_source) +{ + video::SColor color[6]; + for (u8 i = 0; i < 6; i++) + color[i] = c; + makeCuboid(collector, box, tiles, tilecount, color, txc, light_source); +} + static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef, MeshMakeData *data, MapNode n, int v, int *neighbors) { @@ -181,6 +222,18 @@ static inline int NeighborToIndex(const v3s16 &pos) return 9 * pos.X + 3 * pos.Y + pos.Z + 13; } +/*! + * Returns the i-th special tile for a map node. + */ +static TileSpec getSpecialTile(const ContentFeatures &f, + const MapNode &n, u8 i) +{ + TileSpec copy = f.special_tiles[i]; + if (!copy.has_color) + n.getColor(f, ©.color); + return copy; +} + /* TODO: Fix alpha blending for special nodes Currently only the last element rendered is blended correct @@ -227,8 +280,13 @@ void mapblock_mesh_generate_special(MeshMakeData *data, /* Add water sources to mesh if using new style */ - TileSpec tile_liquid = f.special_tiles[0]; + TileSpec tile_liquid = getSpecialTile(f, n, 0); TileSpec tile_liquid_bfculled = getNodeTile(n, p, v3s16(0,0,0), data); + u16 l = getInteriorLight(n, 0, nodedef); + video::SColor c1 = encode_light_and_color(l, + tile_liquid.color, f.light_source); + video::SColor c2 = encode_light_and_color(l, + tile_liquid_bfculled.color, f.light_source); bool top_is_same_liquid = false; MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z)); @@ -237,9 +295,6 @@ void mapblock_mesh_generate_special(MeshMakeData *data, if(ntop.getContent() == c_flowing || ntop.getContent() == c_source) top_is_same_liquid = true; - u16 l = getInteriorLight(n, 0, nodedef); - video::SColor c = MapBlock_LightColor(f.alpha, l, f.light_source); - /* Generate sides */ @@ -285,15 +340,18 @@ void mapblock_mesh_generate_special(MeshMakeData *data, // Use backface culled material if neighbor doesn't have a // solidness of 0 const TileSpec *current_tile = &tile_liquid; - if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) + video::SColor *c = &c1; + if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) { current_tile = &tile_liquid_bfculled; + c = &c2; + } video::S3DVertex vertices[4] = { - video::S3DVertex(-BS/2,0,BS/2,0,0,0, c, 0,1), - video::S3DVertex(BS/2,0,BS/2,0,0,0, c, 1,1), - video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0), + video::S3DVertex(-BS/2,0,BS/2,0,0,0, *c, 0,1), + video::S3DVertex(BS/2,0,BS/2,0,0,0, *c, 1,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, *c, 1,0), + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, *c, 0,0), }; /* @@ -359,10 +417,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data, video::S3DVertex vertices[4] = { - video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,0), + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c1, 0,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, c1, 1,1), + video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c1, 1,0), + video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c1, 0,0), }; v3f offset(p.X * BS, (p.Y + 0.5) * BS, p.Z * BS); @@ -380,8 +438,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, /* Add flowing liquid to mesh */ - TileSpec tile_liquid = f.special_tiles[0]; - TileSpec tile_liquid_bfculled = f.special_tiles[1]; + TileSpec tile_liquid = getSpecialTile(f, n, 0); + TileSpec tile_liquid_bfculled = getSpecialTile(f, n, 1); bool top_is_same_liquid = false; MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z)); @@ -404,7 +462,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data, // Otherwise use the light of this node (the liquid) else l = getInteriorLight(n, 0, nodedef); - video::SColor c = MapBlock_LightColor(f.alpha, l, f.light_source); + video::SColor c1 = encode_light_and_color(l, + tile_liquid.color, f.light_source); + video::SColor c2 = encode_light_and_color(l, + tile_liquid_bfculled.color, f.light_source); u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8); @@ -552,7 +613,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, is liquid, don't draw side face */ if (top_is_same_liquid && - neighbor_data.flags & neighborflag_top_is_same_liquid) + (neighbor_data.flags & neighborflag_top_is_same_liquid)) continue; content_t neighbor_content = neighbor_data.content; @@ -574,15 +635,18 @@ void mapblock_mesh_generate_special(MeshMakeData *data, // Use backface culled material if neighbor doesn't have a // solidness of 0 const TileSpec *current_tile = &tile_liquid; - if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) + video::SColor *c = &c1; + if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) { current_tile = &tile_liquid_bfculled; + c = &c2; + } video::S3DVertex vertices[4] = { - video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0), + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, *c, 0,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, *c, 1,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, *c, 1,0), + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, *c, 0,0), }; /* @@ -656,10 +720,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data, { video::S3DVertex vertices[4] = { - video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,0), + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c1, 0,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, c1, 1,1), + video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c1, 1,0), + video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c1, 0,0), }; // To get backface culling right, the vertices need to go @@ -720,8 +784,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data); u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); - + video::SColor c = encode_light_and_color(l, tile.color, + f.light_source); for(u32 j=0; j<6; j++) { // Check this neighbor @@ -731,13 +795,17 @@ void mapblock_mesh_generate_special(MeshMakeData *data, // Don't make face if neighbor is of same type if(n2.getContent() == n.getContent()) continue; + video::SColor c2=c; + if(!f.light_source) + applyFacesShading(c2, v3f(dir.X, dir.Y, dir.Z)); + // The face at Z+ video::S3DVertex vertices[4] = { - video::S3DVertex(-BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 1,1), - video::S3DVertex(BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 0,1), - video::S3DVertex(BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 0,0), - video::S3DVertex(-BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 1,0), + video::S3DVertex(-BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 1,1), + video::S3DVertex(BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 0,1), + video::S3DVertex(BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 0,0), + video::S3DVertex(-BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c2, 1,0), }; // Rotations in the g_6dirs format @@ -784,12 +852,20 @@ void mapblock_mesh_generate_special(MeshMakeData *data, v3s16( 0, 0,-1) }; + u16 l = getInteriorLight(n, 1, nodedef); u8 i; TileSpec tiles[6]; for (i = 0; i < 6; i++) tiles[i] = getNodeTile(n, p, dirs[i], data); + video::SColor tile0color = encode_light_and_color(l, + tiles[0].color, f.light_source); + video::SColor tile0colors[6]; + for (i = 0; i < 6; i++) + tile0colors[i] = tile0color; + TileSpec glass_tiles[6]; + video::SColor glasscolor[6]; if (tiles[1].texture && tiles[2].texture && tiles[3].texture) { glass_tiles[0] = tiles[2]; glass_tiles[1] = tiles[3]; @@ -801,14 +877,15 @@ void mapblock_mesh_generate_special(MeshMakeData *data, for (i = 0; i < 6; i++) glass_tiles[i] = tiles[1]; } + for (i = 0; i < 6; i++) + glasscolor[i] = encode_light_and_color(l, glass_tiles[i].color, + f.light_source); u8 param2 = n.getParam2(); bool H_merge = ! bool(param2 & 128); bool V_merge = ! bool(param2 & 64); param2 = param2 & 63; - u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); v3f pos = intToFloat(p, BS); static const float a = BS / 2; static const float g = a - 0.003; @@ -947,7 +1024,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, 1-tx2, 1-ty2, 1-tx1, 1-ty1, tx1, 1-ty2, tx2, 1-ty1, }; - makeCuboid(&collector, box, &tiles[0], 1, c, txc1); + makeCuboid(&collector, box, &tiles[0], 1, tile0colors, + txc1, f.light_source); } for(i = 0; i < 6; i++) @@ -971,16 +1049,21 @@ void mapblock_mesh_generate_special(MeshMakeData *data, 1-tx2, 1-ty2, 1-tx1, 1-ty1, tx1, 1-ty2, tx2, 1-ty1, }; - makeCuboid(&collector, box, &glass_tiles[i], 1, c, txc2); + makeCuboid(&collector, box, &glass_tiles[i], 1, glasscolor, + txc2, f.light_source); } if (param2 > 0 && f.special_tiles[0].texture) { // Interior volume level is in range 0 .. 63, // convert it to -0.5 .. 0.5 float vlev = (((float)param2 / 63.0 ) * 2.0 - 1.0); + TileSpec tile=getSpecialTile(f, n, 0); + video::SColor special_color = encode_light_and_color(l, + tile.color, f.light_source); TileSpec interior_tiles[6]; for (i = 0; i < 6; i++) - interior_tiles[i] = f.special_tiles[0]; + interior_tiles[i] = tile; + float offset = 0.003; box = aabb3f(visible_faces[3] ? -b : -a + offset, visible_faces[1] ? -b : -a + offset, @@ -1004,22 +1087,24 @@ void mapblock_mesh_generate_special(MeshMakeData *data, 1-tx2, 1-ty2, 1-tx1, 1-ty1, tx1, 1-ty2, tx2, 1-ty1, }; - makeCuboid(&collector, box, interior_tiles, 6, c, txc3); + makeCuboid(&collector, box, interior_tiles, 6, special_color, + txc3, f.light_source); } break;} case NDT_ALLFACES: { TileSpec tile_leaves = getNodeTile(n, p, v3s16(0,0,0), data); - u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + video::SColor c = encode_light_and_color(l, + tile_leaves.color, f.light_source); v3f pos = intToFloat(p, BS); aabb3f box(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2); box.MinEdge += pos; box.MaxEdge += pos; - makeCuboid(&collector, box, &tile_leaves, 1, c, NULL); + makeCuboid(&collector, box, &tile_leaves, 1, c, NULL, + f.light_source); break;} case NDT_ALLFACES_OPTIONAL: // This is always pre-converted to something else @@ -1046,7 +1131,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + video::SColor c = encode_light_and_color(l, tile.color, + f.light_source); float s = BS/2*f.visual_scale; // Wall at X+ of node @@ -1087,7 +1173,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; u16 l = getInteriorLight(n, 0, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + video::SColor c = encode_light_and_color(l, tile.color, + f.light_source); float d = (float)BS/16; float s = BS/2*f.visual_scale; @@ -1132,7 +1219,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + video::SColor c = encode_light_and_color(l, tile.color, + f.light_source); float s = BS / 2 * f.visual_scale; // add sqrt(2) visual scale @@ -1302,7 +1390,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + video::SColor c = encode_light_and_color(l, tile.color, + f.light_source); float s = BS / 2 * f.visual_scale; @@ -1437,7 +1526,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, tile_rot.rotation = 1; u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + video::SColor c = encode_light_and_color(l, tile.color, + f.light_source); const f32 post_rad=(f32)BS/8; const f32 bar_rad=(f32)BS/16; @@ -1456,7 +1546,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, 4/16.,0,8/16.,1, 8/16.,0,12/16.,1, 12/16.,0,16/16.,1}; - makeCuboid(&collector, post, &tile_rot, 1, c, postuv); + makeCuboid(&collector, post, &tile_rot, 1, c, postuv, + f.light_source); // Now a section of fence, +X, if there's a post there v3s16 p2 = p; @@ -1477,11 +1568,11 @@ void mapblock_mesh_generate_special(MeshMakeData *data, 0/16.,8/16.,16/16.,10/16., 0/16.,14/16.,16/16.,16/16.}; makeCuboid(&collector, bar, &tile_nocrack, 1, - c, xrailuv); + c, xrailuv, f.light_source); bar.MinEdge.Y -= BS/2; bar.MaxEdge.Y -= BS/2; makeCuboid(&collector, bar, &tile_nocrack, 1, - c, xrailuv); + c, xrailuv, f.light_source); } // Now a section of fence, +Z, if there's a post there @@ -1503,11 +1594,11 @@ void mapblock_mesh_generate_special(MeshMakeData *data, 6/16.,6/16.,8/16.,8/16., 10/16.,10/16.,12/16.,12/16.}; makeCuboid(&collector, bar, &tile_nocrack, 1, - c, zrailuv); + c, zrailuv, f.light_source); bar.MinEdge.Y -= BS/2; bar.MaxEdge.Y -= BS/2; makeCuboid(&collector, bar, &tile_nocrack, 1, - c, zrailuv); + c, zrailuv, f.light_source); } break;} case NDT_RAILLIKE: @@ -1616,7 +1707,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; u16 l = getInteriorLight(n, 0, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + video::SColor c = encode_light_and_color(l, tile.color, + f.light_source); float d = (float)BS/64; float s = BS/2; @@ -1627,10 +1719,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data, video::S3DVertex vertices[4] = { - video::S3DVertex(-s, -s+d,-s, 0,0,0, c,0,1), - video::S3DVertex( s, -s+d,-s, 0,0,0, c,1,1), - video::S3DVertex( s, g*s+d, s, 0,0,0, c,1,0), - video::S3DVertex(-s, g*s+d, s, 0,0,0, c,0,0), + video::S3DVertex(-s, -s+d, -s, 0, 0, 0, c, 0, 1), + video::S3DVertex( s, -s+d, -s, 0, 0, 0, c, 1, 1), + video::S3DVertex( s, g*s+d, s, 0, 0, 0, c, 1, 0), + video::S3DVertex(-s, g*s+d, s, 0, 0, 0, c, 0, 0), }; for(s32 i=0; i<4; i++) @@ -1653,10 +1745,16 @@ void mapblock_mesh_generate_special(MeshMakeData *data, v3s16(0, 0, 1), v3s16(0, 0, -1) }; - TileSpec tiles[6]; u16 l = getInteriorLight(n, 1, nodedef); - video::SColor c = MapBlock_LightColor(255, l, f.light_source); + TileSpec tiles[6]; + video::SColor colors[6]; + for(int j = 0; j < 6; j++) { + // Handles facedir rotation for textures + tiles[j] = getNodeTile(n, p, tile_dirs[j], data); + colors[j]= encode_light_and_color(l, tiles[j].color, + f.light_source); + } v3f pos = intToFloat(p, BS); @@ -1696,11 +1794,6 @@ void mapblock_mesh_generate_special(MeshMakeData *data, i = boxes.begin(); i != boxes.end(); ++i) { - for(int j = 0; j < 6; j++) - { - // Handles facedir rotation for textures - tiles[j] = getNodeTile(n, p, tile_dirs[j], data); - } aabb3f box = *i; box.MinEdge += pos; box.MaxEdge += pos; @@ -1747,18 +1840,19 @@ void mapblock_mesh_generate_special(MeshMakeData *data, // front tx1, 1-ty2, tx2, 1-ty1, }; - makeCuboid(&collector, box, tiles, 6, c, txc); + makeCuboid(&collector, box, tiles, 6, colors, txc, f.light_source); } break;} case NDT_MESH: { v3f pos = intToFloat(p, BS); - video::SColor c = MapBlock_LightColor(255, getInteriorLight(n, 1, nodedef), f.light_source); - + u16 l = getInteriorLight(n, 1, nodedef); u8 facedir = 0; - if (f.param_type_2 == CPT2_FACEDIR) { + if (f.param_type_2 == CPT2_FACEDIR || + f.param_type_2 == CPT2_COLORED_FACEDIR) { facedir = n.getFaceDir(nodedef); - } else if (f.param_type_2 == CPT2_WALLMOUNTED) { + } else if (f.param_type_2 == CPT2_WALLMOUNTED || + f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { //convert wallmounted to 6dfacedir. //when cache enabled, it is already converted facedir = n.getWallMounted(nodedef); @@ -1771,10 +1865,13 @@ void mapblock_mesh_generate_special(MeshMakeData *data, if (f.mesh_ptr[facedir]) { // use cached meshes for(u16 j = 0; j < f.mesh_ptr[0]->getMeshBufferCount(); j++) { + const TileSpec &tile = getNodeTileN(n, p, j, data); scene::IMeshBuffer *buf = f.mesh_ptr[facedir]->getMeshBuffer(j); - collector.append(getNodeTileN(n, p, j, data), - (video::S3DVertex *)buf->getVertices(), buf->getVertexCount(), - buf->getIndices(), buf->getIndexCount(), pos, c); + collector.append(tile, (video::S3DVertex *) + buf->getVertices(), buf->getVertexCount(), + buf->getIndices(), buf->getIndexCount(), pos, + encode_light_and_color(l, tile.color, f.light_source), + f.light_source); } } else if (f.mesh_ptr[0]) { // no cache, clone and rotate mesh @@ -1783,10 +1880,13 @@ void mapblock_mesh_generate_special(MeshMakeData *data, recalculateBoundingBox(mesh); meshmanip->recalculateNormals(mesh, true, false); for(u16 j = 0; j < mesh->getMeshBufferCount(); j++) { + const TileSpec &tile = getNodeTileN(n, p, j, data); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - collector.append(getNodeTileN(n, p, j, data), - (video::S3DVertex *)buf->getVertices(), buf->getVertexCount(), - buf->getIndices(), buf->getIndexCount(), pos, c); + collector.append(tile, (video::S3DVertex *) + buf->getVertices(), buf->getVertexCount(), + buf->getIndices(), buf->getIndexCount(), pos, + encode_light_and_color(l, tile.color, f.light_source), + f.light_source); } mesh->drop(); } diff --git a/src/game.cpp b/src/game.cpp index 1070cb1b2..07d429c6b 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -642,7 +642,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_fog_distance; CachedVertexShaderSetting m_animation_timer_vertex; CachedPixelShaderSetting m_animation_timer_pixel; - CachedPixelShaderSetting m_day_night_ratio; + CachedPixelShaderSetting m_day_light; CachedPixelShaderSetting m_eye_position_pixel; CachedVertexShaderSetting m_eye_position_vertex; CachedPixelShaderSetting m_minimap_yaw; @@ -674,7 +674,7 @@ public: m_fog_distance("fogDistance"), m_animation_timer_vertex("animationTimer"), m_animation_timer_pixel("animationTimer"), - m_day_night_ratio("dayNightRatio"), + m_day_light("dayLight"), m_eye_position_pixel("eyePosition"), m_eye_position_vertex("eyePosition"), m_minimap_yaw("yawVec"), @@ -717,8 +717,14 @@ public: m_fog_distance.set(&fog_distance, services); - float daynight_ratio = (float)m_client->getEnv().getDayNightRatio() / 1000.f; - m_day_night_ratio.set(&daynight_ratio, 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); u32 animation_timer = porting::getTimeMs() % 100000; float animation_timer_f = (float)animation_timer / 100000.f; @@ -840,7 +846,8 @@ bool nodePlacementPrediction(Client &client, // Predict param2 for facedir and wallmounted nodes u8 param2 = 0; - if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) { + if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED || + nodedef->get(id).param_type_2 == CPT2_COLORED_WALLMOUNTED) { v3s16 dir = nodepos - neighbourpos; if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { @@ -852,7 +859,8 @@ bool nodePlacementPrediction(Client &client, } } - if (nodedef->get(id).param_type_2 == CPT2_FACEDIR) { + if (nodedef->get(id).param_type_2 == CPT2_FACEDIR || + nodedef->get(id).param_type_2 == CPT2_COLORED_FACEDIR) { v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS); if (abs(dir.X) > abs(dir.Z)) { @@ -3749,11 +3757,9 @@ PointedThing Game::updatePointedThing( light_level = node_light; } - video::SColor c = MapBlock_LightColor(255, light_level, 0); - u8 day = c.getRed(); - u8 night = c.getGreen(); u32 daynight_ratio = client->getEnv().getDayNightRatio(); - finalColorBlend(c, day, night, daynight_ratio); + video::SColor c; + final_color_blend(&c, light_level, daynight_ratio); // Modify final color a bit with time u32 timer = porting::getTimeMs() % 5000; @@ -3964,7 +3970,7 @@ void Game::handleDigging(GameRunData *runData, const ContentFeatures &features = client->getNodeDefManager()->get(n); client->getParticleManager()->addPunchingParticles(client, smgr, - player, nodepos, features.tiles); + player, nodepos, n, features); } } @@ -4011,7 +4017,7 @@ void Game::handleDigging(GameRunData *runData, const ContentFeatures &features = client->getNodeDefManager()->get(wasnode); client->getParticleManager()->addDiggingParticles(client, smgr, - player, nodepos, features.tiles); + player, nodepos, wasnode, features); } runData->dig_time = 0; diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index 143adb410..dfd6f55a5 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -32,12 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/directiontables.h" #include -static void applyFacesShading(video::SColor &color, const float factor) -{ - color.setRed(core::clamp(core::round32(color.getRed() * factor), 0, 255)); - color.setGreen(core::clamp(core::round32(color.getGreen() * factor), 0, 255)); -} - /* MeshMakeData */ @@ -321,19 +315,34 @@ u16 getSmoothLight(v3s16 p, v3s16 corner, MeshMakeData *data) return getSmoothLightCombined(p, data); } -/* - Converts from day + night color values (0..255) - and a given daynight_ratio to the final SColor shown on screen. -*/ -void finalColorBlend(video::SColor& result, - u8 day, u8 night, u32 daynight_ratio) +void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){ + f32 rg = daynight_ratio / 1000.0f - 0.04f; + f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f; + sunlight->r = rg; + sunlight->g = rg; + sunlight->b = b; +} + +void final_color_blend(video::SColor *result, + u16 light, u32 daynight_ratio) { - s32 rg = (day * daynight_ratio + night * (1000-daynight_ratio)) / 1000; - s32 b = rg; + video::SColorf dayLight; + get_sunlight_color(&dayLight, daynight_ratio); + final_color_blend(result, + encode_light_and_color(light, video::SColor(0xFFFFFFFF), 0), dayLight); +} - // Moonlight is blue - b += (day - night) / 13; - rg -= (day - night) / 23; +void final_color_blend(video::SColor *result, + const video::SColor &data, const video::SColorf &dayLight) +{ + static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f); + + video::SColorf c(data); + f32 n = 1 - c.a; + + f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f; + f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f; + f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f; // Emphase blue a bit in darker places // Each entry of this array represents a range of 8 blue levels @@ -341,19 +350,13 @@ void finalColorBlend(video::SColor& result, 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - b += emphase_blue_when_dark[irr::core::clamp(b, 0, 255) / 8]; - b = irr::core::clamp(b, 0, 255); - // Artificial light is yellow-ish - static const u8 emphase_yellow_when_artificial[16] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 15, 15, 15 - }; - rg += emphase_yellow_when_artificial[night/16]; - rg = irr::core::clamp(rg, 0, 255); + b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255), + 0, 255) / 8] / 255.0f; - result.setRed(rg); - result.setGreen(rg); - result.setBlue(b); + result->setRed(core::clamp((s32) (r * 255.0f), 0, 255)); + result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255)); + result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255)); } /* @@ -430,7 +433,7 @@ struct FastFace }; static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, - v3f p, v3s16 dir, v3f scale, u8 light_source, std::vector &dest) + v3f p, v3s16 dir, v3f scale, std::vector &dest) { // Position is at the center of the cube. v3f pos = p * BS; @@ -580,24 +583,25 @@ static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, v3f normal(dir.X, dir.Y, dir.Z); - u8 alpha = tile.alpha; - dest.push_back(FastFace()); FastFace& face = *dest.rbegin(); - face.vertices[0] = video::S3DVertex(vertex_pos[0], normal, - MapBlock_LightColor(alpha, li0, light_source), - core::vector2d(x0+w*abs_scale, y0+h)); - face.vertices[1] = video::S3DVertex(vertex_pos[1], normal, - MapBlock_LightColor(alpha, li1, light_source), - core::vector2d(x0, y0+h)); - face.vertices[2] = video::S3DVertex(vertex_pos[2], normal, - MapBlock_LightColor(alpha, li2, light_source), - core::vector2d(x0, y0)); - face.vertices[3] = video::S3DVertex(vertex_pos[3], normal, - MapBlock_LightColor(alpha, li3, light_source), - core::vector2d(x0+w*abs_scale, y0)); + u16 li[4] = { li0, li1, li2, li3 }; + v2f32 f[4] = { + core::vector2d(x0 + w * abs_scale, y0 + h), + core::vector2d(x0, y0 + h), + core::vector2d(x0, y0), + core::vector2d(x0 + w * abs_scale, y0) }; + + for (u8 i = 0; i < 4; i++) { + video::SColor c = encode_light_and_color(li[i], tile.color, + tile.emissive_light); + if (!tile.emissive_light) + applyFacesShading(c, normal); + + face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]); + } face.tile = tile; } @@ -664,7 +668,10 @@ static u8 face_contents(content_t m1, content_t m2, bool *equivalent, TileSpec getNodeTileN(MapNode mn, v3s16 p, u8 tileindex, MeshMakeData *data) { INodeDefManager *ndef = data->m_client->ndef(); - TileSpec spec = ndef->get(mn).tiles[tileindex]; + const ContentFeatures &f = ndef->get(mn); + TileSpec spec = f.tiles[tileindex]; + if (!spec.has_color) + mn.getColor(f, &spec.color); // Apply temporary crack if (p == data->m_crack_pos_relative) spec.material_flags |= MATERIAL_FLAG_CRACK; @@ -747,8 +754,7 @@ static void getTileInfo( v3s16 &p_corrected, v3s16 &face_dir_corrected, u16 *lights, - TileSpec &tile, - u8 &light_source + TileSpec &tile ) { VoxelManipulator &vmanip = data->m_vmanip; @@ -763,7 +769,8 @@ static void getTileInfo( return; } - const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); + const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags( + blockpos_nodes + p + face_dir); if (n1.getContent() == CONTENT_IGNORE) { makes_face = false; @@ -783,26 +790,25 @@ static void getTileInfo( makes_face = true; - if(mf == 1) - { - tile = getNodeTile(n0, p, face_dir, data); + MapNode n = n0; + + if (mf == 1) { p_corrected = p; face_dir_corrected = face_dir; - light_source = ndef->get(n0).light_source; - } - else - { - tile = getNodeTile(n1, p + face_dir, -face_dir, data); + } else { + n = n1; p_corrected = p + face_dir; face_dir_corrected = -face_dir; - light_source = ndef->get(n1).light_source; } + tile = getNodeTile(n, p_corrected, face_dir_corrected, data); + const ContentFeatures &f = ndef->get(n); + tile.emissive_light = f.light_source; // eg. water and glass - if(equivalent) + if (equivalent) tile.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; - if(data->m_smooth_lighting == false) + if (data->m_smooth_lighting == false) { lights[0] = lights[1] = lights[2] = lights[3] = getFaceLight(n0, n1, face_dir, ndef); @@ -845,10 +851,9 @@ static void updateFastFaceRow( v3s16 face_dir_corrected; u16 lights[4] = {0,0,0,0}; TileSpec tile; - u8 light_source = 0; getTileInfo(data, p, face_dir, makes_face, p_corrected, face_dir_corrected, - lights, tile, light_source); + lights, tile); for(u16 j=0; javg("Meshgen: faces drawn by tiling", 0); for(int i = 1; i < continuous_tiles_count; i++){ @@ -958,7 +962,6 @@ static void updateFastFaceRow( lights[2] = next_lights[2]; lights[3] = next_lights[3]; tile = next_tile; - light_source = next_light_source; p = p_next; } } @@ -1083,12 +1086,14 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): const u16 *indices_p = indices; /* - Revert triangles for nicer looking gradient if vertices - 1 and 3 have same color or 0 and 2 have different color. - getRed() is the day color. + Revert triangles for nicer looking gradient if the + brightness of vertices 1 and 3 differ less than + the brightness of vertices 0 and 2. */ - if(f.vertices[0].Color.getRed() != f.vertices[2].Color.getRed() - || f.vertices[1].Color.getRed() == f.vertices[3].Color.getRed()) + if (abs(f.vertices[0].Color.getAverage() + - f.vertices[2].Color.getAverage()) + > abs(f.vertices[1].Color.getAverage() + - f.vertices[3].Color.getAverage())) indices_p = indices_alternate; collector.append(f.tile, f.vertices, 4, indices_p, 6); @@ -1148,43 +1153,30 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): p.tile.texture = animation_frame.texture; } - u32 vertex_count = m_use_tangent_vertices ? - p.tangent_vertices.size() : p.vertices.size(); - for (u32 j = 0; j < vertex_count; j++) { - v3f *Normal; - video::SColor *vc; - if (m_use_tangent_vertices) { - vc = &p.tangent_vertices[j].Color; - Normal = &p.tangent_vertices[j].Normal; - } else { - vc = &p.vertices[j].Color; - Normal = &p.vertices[j].Normal; - } - // Note applyFacesShading second parameter is precalculated sqrt - // value for speed improvement - // Skip it for lightsources and top faces. - if (!vc->getBlue()) { - if (Normal->Y < -0.5) { - applyFacesShading(*vc, 0.447213); - } else if (Normal->X > 0.5) { - applyFacesShading(*vc, 0.670820); - } else if (Normal->X < -0.5) { - applyFacesShading(*vc, 0.670820); - } else if (Normal->Z > 0.5) { - applyFacesShading(*vc, 0.836660); - } else if (Normal->Z < -0.5) { - applyFacesShading(*vc, 0.836660); - } - } - if (!m_enable_shaders) { - // - Classic lighting (shaders handle this by themselves) - // Set initial real color and store for later updates - u8 day = vc->getRed(); - u8 night = vc->getGreen(); - finalColorBlend(*vc, day, night, 1000); - if (day != night) { - m_daynight_diffs[i][j] = std::make_pair(day, night); + if (!m_enable_shaders) { + // Extract colors for day-night animation + // Dummy sunlight to handle non-sunlit areas + video::SColorf sunlight; + get_sunlight_color(&sunlight, 0); + u32 vertex_count = + m_use_tangent_vertices ? + p.tangent_vertices.size() : p.vertices.size(); + for (u32 j = 0; j < vertex_count; j++) { + video::SColor *vc; + if (m_use_tangent_vertices) { + vc = &p.tangent_vertices[j].Color; + } else { + 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[i][j] = copy; + + // The sunlight ratio has been stored, + // delete alpha (for the final rendering). + vc->setAlpha(255); } } @@ -1358,19 +1350,19 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat if (m_enable_vbo) { m_mesh->setDirty(); } - for(std::map > >::iterator + video::SColorf day_color; + get_sunlight_color(&day_color, daynight_ratio); + for(std::map >::iterator i = m_daynight_diffs.begin(); i != m_daynight_diffs.end(); ++i) { scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); - for(std::map >::iterator + for(std::map::iterator j = i->second.begin(); j != i->second.end(); ++j) { - u8 day = j->second.first; - u8 night = j->second.second; - finalColorBlend(vertices[j->first].Color, day, night, daynight_ratio); + final_color_blend(&(vertices[j->first].Color), j->second, day_color); } } m_last_daynight_ratio = daynight_ratio; @@ -1452,7 +1444,7 @@ void MeshCollector::append(const TileSpec &tile, void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices, - v3f pos, video::SColor c) + v3f pos, video::SColor c, u8 light_source) { if (numIndices > 65535) { dstream<<"FIXME: MeshCollector::append() called with numIndices="<tangent_vertices.size(); for (u32 i = 0; i < numVertices; i++) { + if (!light_source) { + c = original_c; + applyFacesShading(c, vertices[i].Normal); + } video::S3DVertexTangents vert(vertices[i].Pos + pos, vertices[i].Normal, c, vertices[i].TCoords); p->tangent_vertices.push_back(vert); @@ -1489,8 +1486,12 @@ void MeshCollector::append(const TileSpec &tile, } else { vertex_count = p->vertices.size(); for (u32 i = 0; i < numVertices; i++) { - video::S3DVertex vert(vertices[i].Pos + pos, - vertices[i].Normal, c, vertices[i].TCoords); + if (!light_source) { + c = original_c; + applyFacesShading(c, vertices[i].Normal); + } + video::S3DVertex vert(vertices[i].Pos + pos, vertices[i].Normal, c, + vertices[i].TCoords); p->vertices.push_back(vert); } } @@ -1500,3 +1501,33 @@ void MeshCollector::append(const TileSpec &tile, p->indices.push_back(j); } } + +video::SColor encode_light_and_color(u16 light, const video::SColor &color, + u8 emissive_light) +{ + // Get components + f32 day = (light & 0xff) / 255.0f; + f32 night = (light >> 8) / 255.0f; + // Add emissive light + night += emissive_light * 0.01f; + if (night > 255) + night = 255; + // Since we don't know if the day light is sunlight or + // artificial light, assume it is artificial when the night + // light bank is also lit. + if (day < night) + day = 0; + else + day = day - night; + f32 sum = day + night; + // Ratio of sunlight: + float r; + if (sum > 0) + r = day / sum; + else + r = 0; + // Average light: + float b = (day + night) / 2; + return video::SColor(r * 255, b * color.getRed(), b * color.getGreen(), + b * color.getBlue()); +} diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index 5adb7df3f..916703f3e 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -156,8 +156,8 @@ private: // Animation info: day/night transitions // Last daynight_ratio value passed to animate() u32 m_last_daynight_ratio; - // For each meshbuffer, maps vertex indices to (day,night) pairs - std::map > > m_daynight_diffs; + // For each meshbuffer, stores pre-baked colors of sunlit vertices + std::map > m_daynight_diffs; // Camera offset info -> do we have to translate the mesh? v3s16 m_camera_offset; @@ -192,28 +192,53 @@ struct MeshCollector void append(const TileSpec &material, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices, - v3f pos, video::SColor c); + v3f pos, video::SColor c, u8 light_source); }; -// This encodes -// alpha in the A channel of the returned SColor -// day light (0-255) in the R channel of the returned SColor -// night light (0-255) in the G channel of the returned SColor -// light source (0-255) in the B channel of the returned SColor -inline video::SColor MapBlock_LightColor(u8 alpha, u16 light, u8 light_source=0) -{ - return video::SColor(alpha, (light & 0xff), (light >> 8), light_source); -} +/*! + * Encodes light and color of a node. + * The result is not the final color, but a + * half-baked vertex color. + * + * \param light the first 8 bits are day light, + * the last 8 bits are night light + * \param color the node's color + * \param emissive_light amount of light the surface emits, + * from 0 to LIGHT_SUN. + */ +video::SColor encode_light_and_color(u16 light, const video::SColor &color, + u8 emissive_light); // Compute light at node u16 getInteriorLight(MapNode n, s32 increment, INodeDefManager *ndef); u16 getFaceLight(MapNode n, MapNode n2, v3s16 face_dir, INodeDefManager *ndef); u16 getSmoothLight(v3s16 p, v3s16 corner, MeshMakeData *data); -// Converts from day + night color values (0..255) -// and a given daynight_ratio to the final SColor shown on screen. -void finalColorBlend(video::SColor& result, - u8 day, u8 night, u32 daynight_ratio); +/*! + * Returns the sunlight's color from the current + * day-night ratio. + */ +void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio); + +/*! + * Gives the final SColor shown on screen. + * + * \param result output color + * \param light first 8 bits are day light, second 8 bits are + * night light + */ +void final_color_blend(video::SColor *result, + u16 light, u32 daynight_ratio); + +/*! + * Gives the final SColor shown on screen. + * + * \param result output color + * \param data the half-baked vertex color + * \param dayLight color of the sunlight + */ +void final_color_blend(video::SColor *result, + const video::SColor &data, const video::SColorf &dayLight); // Retrieves the TileSpec of a face of a node // Adds MATERIAL_FLAG_CRACK if the node is cracked diff --git a/src/mapnode.cpp b/src/mapnode.cpp index f1a7f3e61..d835daba2 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -55,6 +55,15 @@ MapNode::MapNode(INodeDefManager *ndef, const std::string &name, param2 = a_param2; } +void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const +{ + if (f.palette) { + *color = (*f.palette)[param2]; + return; + } + *color = f.color; +} + void MapNode::setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f) { // If node doesn't contain light data, ignore this @@ -146,7 +155,8 @@ bool MapNode::getLightBanks(u8 &lightday, u8 &lightnight, INodeDefManager *nodem u8 MapNode::getFaceDir(INodeDefManager *nodemgr) const { const ContentFeatures &f = nodemgr->get(*this); - if(f.param_type_2 == CPT2_FACEDIR) + if (f.param_type_2 == CPT2_FACEDIR || + f.param_type_2 == CPT2_COLORED_FACEDIR) return (getParam2() & 0x1F) % 24; return 0; } @@ -154,7 +164,8 @@ u8 MapNode::getFaceDir(INodeDefManager *nodemgr) const u8 MapNode::getWallMounted(INodeDefManager *nodemgr) const { const ContentFeatures &f = nodemgr->get(*this); - if(f.param_type_2 == CPT2_WALLMOUNTED) + if (f.param_type_2 == CPT2_WALLMOUNTED || + f.param_type_2 == CPT2_COLORED_WALLMOUNTED) return getParam2() & 0x07; return 0; } @@ -176,7 +187,7 @@ void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot) { ContentParamType2 cpt2 = nodemgr->get(*this).param_type_2; - if (cpt2 == CPT2_FACEDIR) { + if (cpt2 == CPT2_FACEDIR || cpt2 == CPT2_COLORED_FACEDIR) { static const u8 rotate_facedir[24 * 4] = { // Table value = rotated facedir // Columns: 0, 90, 180, 270 degrees rotation around vertical axis @@ -216,7 +227,8 @@ void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot) u8 index = facedir * 4 + rot; param2 &= ~31; param2 |= rotate_facedir[index]; - } else if (cpt2 == CPT2_WALLMOUNTED) { + } else if (cpt2 == CPT2_WALLMOUNTED || + cpt2 == CPT2_COLORED_WALLMOUNTED) { u8 wmountface = (param2 & 7); if (wmountface <= 1) return; diff --git a/src/mapnode.h b/src/mapnode.h index ae0245cfe..9c56a7e17 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -20,9 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef MAPNODE_HEADER #define MAPNODE_HEADER -#include "irrlichttypes.h" -#include "irr_v3d.h" -#include "irr_aabb3d.h" +#include "irrlichttypes_bloated.h" #include "light.h" #include #include @@ -187,6 +185,14 @@ struct MapNode param2 = p; } + /*! + * Returns the color of the node. + * + * \param f content features of this node + * \param color output, contains the node's color. + */ + void getColor(const ContentFeatures &f, video::SColor *color) const; + void setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f); void setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr); diff --git a/src/mesh.cpp b/src/mesh.cpp index 50465748c..84689b631 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -33,13 +33,25 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY #endif -static void applyFacesShading(video::SColor& color, float factor) +inline static void applyShadeFactor(video::SColor& color, float factor) { color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255)); color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255)); color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255)); } +void applyFacesShading(video::SColor &color, const v3f &normal) +{ + // Many special drawtypes have normals set to 0,0,0 and this + // must result in maximum brightness (no face shadng). + if (normal.Y < -0.5f) + applyShadeFactor (color, 0.447213f); + else if (normal.X > 0.5f || normal.X < -0.5f) + applyShadeFactor (color, 0.670820f); + else if (normal.Z > 0.5f || normal.Z < -0.5f) + applyShadeFactor (color, 0.836660f); +} + scene::IAnimatedMesh* createCubeMesh(v3f scale) { video::SColor c(255,255,255,255); @@ -172,29 +184,18 @@ void setMeshColor(scene::IMesh *mesh, const video::SColor &color) } } -void shadeMeshFaces(scene::IMesh *mesh) +void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor) { - if (mesh == NULL) - return; - - u32 mc = mesh->getMeshBufferCount(); - for (u32 j = 0; j < mc; j++) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) { - video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); - video::SColor &vc = vertex->Color; - // Many special drawtypes have normals set to 0,0,0 and this - // must result in maximum brightness (no face shadng). - if (vertex->Normal.Y < -0.5f) - applyFacesShading (vc, 0.447213f); - else if (vertex->Normal.X > 0.5f || vertex->Normal.X < -0.5f) - applyFacesShading (vc, 0.670820f); - else if (vertex->Normal.Z > 0.5f || vertex->Normal.Z < -0.5f) - applyFacesShading (vc, 0.836660f); - } + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *) buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride); + video::SColor *vc = &(vertex->Color); + // Reset color + *vc = *buffercolor; + // Apply shading + applyFacesShading(*vc, vertex->Normal); } } diff --git a/src/mesh.h b/src/mesh.h index 10df97015..bcf0d771c 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -23,6 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "nodedef.h" +/*! + * Applies shading to a color based on the surface's + * normal vector. + */ +void applyFacesShading(video::SColor &color, const v3f &normal); + /* Create a new cube mesh. Vertices are at (+-scale.X/2, +-scale.Y/2, +-scale.Z/2). @@ -48,11 +54,7 @@ void translateMesh(scene::IMesh *mesh, v3f vec); */ void setMeshColor(scene::IMesh *mesh, const video::SColor &color); -/* - Shade mesh faces according to their normals -*/ - -void shadeMeshFaces(scene::IMesh *mesh); +void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor); /* Set the color of all vertices in the mesh. @@ -87,7 +89,7 @@ void rotateMeshYZby (scene::IMesh *mesh, f64 degrees); scene::IMesh* cloneMesh(scene::IMesh *src_mesh); /* - Convert nodeboxes to mesh. + Convert nodeboxes to mesh. Each tile goes into a different buffer. boxes - set of nodeboxes to be converted into cuboids uv_coords[24] - table of texture uv coords for each cuboid face expand - factor by which cuboids will be resized diff --git a/src/minimap.cpp b/src/minimap.cpp index f49adb517..a2e501751 100644 --- a/src/minimap.cpp +++ b/src/minimap.cpp @@ -144,7 +144,7 @@ MinimapPixel *MinimapUpdateThread::getMinimapPixel(v3s16 pos, if (it != m_blocks_cache.end()) { MinimapMapblock *mmblock = it->second; MinimapPixel *pixel = &mmblock->data[relpos.Z * MAP_BLOCKSIZE + relpos.X]; - if (pixel->id != CONTENT_AIR) { + if (pixel->n.param0 != CONTENT_AIR) { *pixel_height = height + pixel->height; return pixel; } @@ -187,7 +187,7 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height, bool is_radar) for (s16 x = 0; x < size; x++) for (s16 z = 0; z < size; z++) { - u16 id = CONTENT_AIR; + MapNode n(CONTENT_AIR); MinimapPixel *mmpixel = &data->minimap_scan[x + z * size]; if (!is_radar) { @@ -195,14 +195,14 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height, bool is_radar) MinimapPixel *cached_pixel = getMinimapPixel(v3s16(p.X + x, p.Y, p.Z + z), height, &pixel_height); if (cached_pixel) { - id = cached_pixel->id; + n = cached_pixel->n; mmpixel->height = pixel_height; } } else { mmpixel->air_count = getAirCount(v3s16(p.X + x, p.Y, p.Z + z), height); } - mmpixel->id = id; + mmpixel->n = n; } } @@ -372,10 +372,21 @@ void Mapper::blitMinimapPixelsToImageSurface( for (s16 z = 0; z < data->map_size; z++) { MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; - video::SColor c = m_ndef->get(mmpixel->id).minimap_color; - c.setAlpha(240); - - map_image->setPixel(x, data->map_size - z - 1, c); + const ContentFeatures &f = m_ndef->get(mmpixel->n); + const TileDef *tile = &f.tiledef[0]; + // Color of the 0th tile (mostly this is the topmost) + video::SColor tilecolor; + if(tile->has_color) + tilecolor = tile->color; + else + mmpixel->n.getColor(f, &tilecolor); + tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); + tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() + / 255); + tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); + tilecolor.setAlpha(240); + + map_image->setPixel(x, data->map_size - z - 1, tilecolor); u32 h = mmpixel->height; heightmap_image->setPixel(x,data->map_size - z - 1, @@ -617,7 +628,7 @@ void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, v3s16 pos) MapNode n = vmanip->getNodeNoEx(pos + p); if (!surface_found && n.getContent() != CONTENT_AIR) { mmpixel->height = y; - mmpixel->id = n.getContent(); + mmpixel->n = n; surface_found = true; } else if (n.getContent() == CONTENT_AIR) { air_count++; @@ -625,7 +636,7 @@ void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, v3s16 pos) } if (!surface_found) - mmpixel->id = CONTENT_AIR; + mmpixel->n = MapNode(CONTENT_AIR); mmpixel->air_count = air_count; } diff --git a/src/minimap.h b/src/minimap.h index 743b2bff2..81ed0e49f 100644 --- a/src/minimap.h +++ b/src/minimap.h @@ -52,10 +52,10 @@ struct MinimapModeDef { }; struct MinimapPixel { - u16 id; + //! The topmost node that the minimap displays. + MapNode n; u16 height; u16 air_count; - u16 light; }; struct MinimapMapblock { diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 23c8a665b..a511d169b 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -143,9 +143,12 @@ with this program; if not, write to the Free Software Foundation, Inc., serialization of TileAnimation params changed TAT_SHEET_2D Removed client-sided chat perdiction + PROTOCOL VERSION 30: + New ContentFeatures serialization version + Add node and tile color and palette */ -#define LATEST_PROTOCOL_VERSION 29 +#define LATEST_PROTOCOL_VERSION 30 // Server's supported network protocol range #define SERVER_PROTOCOL_VERSION_MIN 13 diff --git a/src/nodedef.cpp b/src/nodedef.cpp index a4af26e87..98f795c7a 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -189,7 +189,9 @@ void NodeBox::deSerialize(std::istream &is) void TileDef::serialize(std::ostream &os, u16 protocol_version) const { - if (protocol_version >= 29) + if (protocol_version >= 30) + writeU8(os, 4); + else if (protocol_version >= 29) writeU8(os, 3); else if (protocol_version >= 26) writeU8(os, 2); @@ -205,6 +207,14 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, tileable_horizontal); writeU8(os, tileable_vertical); } + if (protocol_version >= 30) { + writeU8(os, has_color); + if (has_color) { + writeU8(os, color.getRed()); + writeU8(os, color.getGreen()); + writeU8(os, color.getBlue()); + } + } } void TileDef::deSerialize(std::istream &is, const u8 contenfeatures_version, const NodeDrawType drawtype) @@ -218,6 +228,14 @@ void TileDef::deSerialize(std::istream &is, const u8 contenfeatures_version, con tileable_horizontal = readU8(is); tileable_vertical = readU8(is); } + if (version >= 4) { + has_color = readU8(is); + if (has_color) { + color.setRed(readU8(is)); + color.setGreen(readU8(is)); + color.setBlue(readU8(is)); + } + } if ((contenfeatures_version < 8) && ((drawtype == NDT_MESH) || @@ -351,172 +369,222 @@ void ContentFeatures::reset() connects_to.clear(); connects_to_ids.clear(); connect_sides = 0; + color = video::SColor(0xFFFFFFFF); + palette_name = ""; + palette = NULL; } void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const { - if(protocol_version < 24){ + if (protocol_version < 30) { serializeOld(os, protocol_version); return; } - writeU8(os, protocol_version < 27 ? 7 : 8); + // version + writeU8(os, 9); - os<first); + for (ItemGroupList::const_iterator i = groups.begin(); i != groups.end(); + ++i) { + os << serializeString(i->first); writeS16(os, i->second); } + writeU8(os, param_type); + writeU8(os, param_type_2); + + // visual writeU8(os, drawtype); + os << serializeString(mesh); writeF1000(os, visual_scale); writeU8(os, 6); - for(u32 i = 0; i < 6; i++) + for (u32 i = 0; i < 6; i++) tiledef[i].serialize(os, protocol_version); writeU8(os, CF_SPECIAL_COUNT); - for(u32 i = 0; i < CF_SPECIAL_COUNT; i++){ + for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) { tiledef_special[i].serialize(os, protocol_version); } writeU8(os, alpha); + writeU8(os, color.getRed()); + writeU8(os, color.getGreen()); + writeU8(os, color.getBlue()); + os << serializeString(palette_name); + writeU8(os, waving); + writeU8(os, connect_sides); + writeU16(os, connects_to_ids.size()); + for (std::set::const_iterator i = connects_to_ids.begin(); + i != connects_to_ids.end(); ++i) + writeU16(os, *i); writeU8(os, post_effect_color.getAlpha()); writeU8(os, post_effect_color.getRed()); writeU8(os, post_effect_color.getGreen()); writeU8(os, post_effect_color.getBlue()); - writeU8(os, param_type); - if ((protocol_version < 28) && (param_type_2 == CPT2_MESHOPTIONS)) - writeU8(os, CPT2_NONE); - else - writeU8(os, param_type_2); - writeU8(os, is_ground_content); + writeU8(os, leveled); + + // lighting writeU8(os, light_propagates); writeU8(os, sunlight_propagates); + writeU8(os, light_source); + + // map generation + writeU8(os, is_ground_content); + + // interaction writeU8(os, walkable); writeU8(os, pointable); writeU8(os, diggable); writeU8(os, climbable); writeU8(os, buildable_to); - os<::const_iterator i = connects_to_ids.begin(); - i != connects_to_ids.end(); ++i) - writeU16(os, *i); - writeU8(os, connect_sides); + + // legacy + writeU8(os, legacy_facedir_simple); + writeU8(os, legacy_wallmounted); +} + +void ContentFeatures::correctAlpha() +{ + if (alpha == 0 || alpha == 255) + return; + + for (u32 i = 0; i < 6; i++) { + std::stringstream s; + s << tiledef[i].name << "^[noalpha^[opacity:" << ((int)alpha); + tiledef[i].name = s.str(); + } + + for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) { + std::stringstream s; + s << tiledef_special[i].name << "^[noalpha^[opacity:" << ((int)alpha); + tiledef_special[i].name = s.str(); + } } void ContentFeatures::deSerialize(std::istream &is) { + // version detection int version = readU8(is); - if (version < 7) { + if (version < 9) { deSerializeOld(is, version); return; - } else if (version > 8) { + } else if (version > 9) { throw SerializationError("unsupported ContentFeatures version"); } + // general name = deSerializeString(is); groups.clear(); u32 groups_size = readU16(is); - for(u32 i = 0; i < groups_size; i++){ + for (u32 i = 0; i < groups_size; i++) { std::string name = deSerializeString(is); int value = readS16(is); groups[name] = value; } - drawtype = (enum NodeDrawType)readU8(is); + param_type = (enum ContentParamType) readU8(is); + param_type_2 = (enum ContentParamType2) readU8(is); + // visual + drawtype = (enum NodeDrawType) readU8(is); + mesh = deSerializeString(is); visual_scale = readF1000(is); - if(readU8(is) != 6) + if (readU8(is) != 6) throw SerializationError("unsupported tile count"); - for(u32 i = 0; i < 6; i++) + for (u32 i = 0; i < 6; i++) tiledef[i].deSerialize(is, version, drawtype); - if(readU8(is) != CF_SPECIAL_COUNT) + if (readU8(is) != CF_SPECIAL_COUNT) throw SerializationError("unsupported CF_SPECIAL_COUNT"); - for(u32 i = 0; i < CF_SPECIAL_COUNT; i++) + for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) tiledef_special[i].deSerialize(is, version, drawtype); alpha = readU8(is); + color.setRed(readU8(is)); + color.setGreen(readU8(is)); + color.setBlue(readU8(is)); + palette_name = deSerializeString(is); + waving = readU8(is); + connect_sides = readU8(is); + u16 connects_to_size = readU16(is); + connects_to_ids.clear(); + for (u16 i = 0; i < connects_to_size; i++) + connects_to_ids.insert(readU16(is)); post_effect_color.setAlpha(readU8(is)); post_effect_color.setRed(readU8(is)); post_effect_color.setGreen(readU8(is)); post_effect_color.setBlue(readU8(is)); - param_type = (enum ContentParamType)readU8(is); - param_type_2 = (enum ContentParamType2)readU8(is); - is_ground_content = readU8(is); + leveled = readU8(is); + + // lighting-related light_propagates = readU8(is); sunlight_propagates = readU8(is); + light_source = readU8(is); + light_source = MYMIN(light_source, LIGHT_MAX); + + // map generation + is_ground_content = readU8(is); + + // interaction walkable = readU8(is); pointable = readU8(is); diggable = readU8(is); climbable = readU8(is); buildable_to = readU8(is); - deSerializeString(is); // legacy: used to be metadata_name - liquid_type = (enum LiquidType)readU8(is); + rightclickable = readU8(is); + damage_per_second = readU32(is); + + // liquid + liquid_type = (enum LiquidType) readU8(is); liquid_alternative_flowing = deSerializeString(is); liquid_alternative_source = deSerializeString(is); liquid_viscosity = readU8(is); liquid_renewable = readU8(is); - light_source = readU8(is); - light_source = MYMIN(light_source, LIGHT_MAX); - damage_per_second = readU32(is); + liquid_range = readU8(is); + drowning = readU8(is); + floodable = readU8(is); + + // node boxes node_box.deSerialize(is); selection_box.deSerialize(is); - legacy_facedir_simple = readU8(is); - legacy_wallmounted = readU8(is); + collision_box.deSerialize(is); + + // sounds deSerializeSimpleSoundSpec(sound_footstep, is); deSerializeSimpleSoundSpec(sound_dig, is); deSerializeSimpleSoundSpec(sound_dug, is); - rightclickable = readU8(is); - drowning = readU8(is); - leveled = readU8(is); - liquid_range = readU8(is); - waving = readU8(is); - // If you add anything here, insert it primarily inside the try-catch - // block to not need to increase the version. - try{ - // Stuff below should be moved to correct place in a version that - // otherwise changes the protocol version - mesh = deSerializeString(is); - collision_box.deSerialize(is); - floodable = readU8(is); - u16 connects_to_size = readU16(is); - connects_to_ids.clear(); - for (u16 i = 0; i < connects_to_size; i++) - connects_to_ids.insert(readU16(is)); - connect_sides = readU8(is); - }catch(SerializationError &e) {}; + + // read legacy properties + legacy_facedir_simple = readU8(is); + legacy_wallmounted = readU8(is); } #ifndef SERVER void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef, u32 shader_id, bool use_normal_texture, - bool backface_culling, u8 alpha, u8 material_type) + bool backface_culling, u8 material_type) { tile->shader_id = shader_id; tile->texture = tsrc->getTextureForMesh(tiledef->name, &tile->texture_id); - tile->alpha = alpha; tile->material_type = material_type; // Normal texture and shader flags texture @@ -536,6 +604,13 @@ void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, if (tiledef->tileable_vertical) tile->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL; + // Color + tile->has_color = tiledef->has_color; + if (tiledef->has_color) + tile->color = tiledef->color; + else + tile->color = color; + // Animation parameters int frame_count = 1; if (tile->material_flags & MATERIAL_FLAG_ANIMATION) { @@ -681,6 +756,9 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc is_water_surface = true; } + // Vertex alpha is no longer supported, correct if necessary. + correctAlpha(); + u32 tile_shader[6]; for (u16 j = 0; j < 6; j++) { tile_shader[j] = shdsrc->getShader("nodes_shader", @@ -696,14 +774,14 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc for (u16 j = 0; j < 6; j++) { fillTileAttribs(tsrc, &tiles[j], &tdef[j], tile_shader[j], tsettings.use_normal_texture, - tiledef[j].backface_culling, alpha, material_type); + tiledef[j].backface_culling, material_type); } // Special tiles (fill in f->special_tiles[]) for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) { fillTileAttribs(tsrc, &special_tiles[j], &tiledef_special[j], tile_shader[j], tsettings.use_normal_texture, - tiledef_special[j].backface_culling, alpha, material_type); + tiledef_special[j].backface_culling, material_type); } if ((drawtype == NDT_MESH) && (mesh != "")) { @@ -731,15 +809,19 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc } //Cache 6dfacedir and wallmounted rotated clones of meshes - if (tsettings.enable_mesh_cache && mesh_ptr[0] && (param_type_2 == CPT2_FACEDIR)) { + if (tsettings.enable_mesh_cache && mesh_ptr[0] && + (param_type_2 == CPT2_FACEDIR + || param_type_2 == CPT2_COLORED_FACEDIR)) { for (u16 j = 1; j < 24; j++) { mesh_ptr[j] = cloneMesh(mesh_ptr[0]); rotateMeshBy6dFacedir(mesh_ptr[j], j); recalculateBoundingBox(mesh_ptr[j]); meshmanip->recalculateNormals(mesh_ptr[j], true, false); } - } else if (tsettings.enable_mesh_cache && mesh_ptr[0] && (param_type_2 == CPT2_WALLMOUNTED)) { - static const u8 wm_to_6d[6] = {20, 0, 16+1, 12+3, 8, 4+2}; + } else if (tsettings.enable_mesh_cache && mesh_ptr[0] + && (param_type_2 == CPT2_WALLMOUNTED || + param_type_2 == CPT2_COLORED_WALLMOUNTED)) { + static const u8 wm_to_6d[6] = { 20, 0, 16 + 1, 12 + 3, 8, 4 + 2 }; for (u16 j = 1; j < 6; j++) { mesh_ptr[j] = cloneMesh(mesh_ptr[0]); rotateMeshBy6dFacedir(mesh_ptr[j], wm_to_6d[j]); @@ -775,6 +857,9 @@ public: virtual void removeNode(const std::string &name); virtual void updateAliases(IItemDefManager *idef); virtual void applyTextureOverrides(const std::string &override_filepath); + //! Returns a palette or NULL if not found. Only on client. + std::vector *getPalette(const ContentFeatures &f, + const IGameDef *gamedef); virtual void updateTextures(IGameDef *gamedef, void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress), void *progress_cbk_args); @@ -823,6 +908,9 @@ private: // Next possibly free id content_t m_next_id; + // Maps image file names to loaded palettes. + UNORDERED_MAP > m_palettes; + // NodeResolvers to callback once node registration has ended std::vector m_pending_resolve_callbacks; @@ -1062,7 +1150,8 @@ void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features, if (nodebox.type == NODEBOX_LEVELED) { half_processed.MaxEdge.Y = +BS / 2; } - if (features.param_type_2 == CPT2_FACEDIR) { + if (features.param_type_2 == CPT2_FACEDIR || + features.param_type_2 == CPT2_COLORED_FACEDIR) { // Get maximal coordinate f32 coords[] = { fabsf(half_processed.MinEdge.X), @@ -1309,6 +1398,78 @@ void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath } } +std::vector *CNodeDefManager::getPalette( + const ContentFeatures &f, const IGameDef *gamedef) +{ +#ifndef SERVER + // This works because colors always use the most significant bits + // of param2. If you add a new colored type which uses param2 + // in a more advanced way, you should change this code, too. + u32 palette_pixels = 0; + switch (f.param_type_2) { + case CPT2_COLOR: + palette_pixels = 256; + break; + case CPT2_COLORED_FACEDIR: + palette_pixels = 8; + break; + case CPT2_COLORED_WALLMOUNTED: + palette_pixels = 32; + break; + default: + return NULL; + } + // This many param2 values will have the same color + u32 step = 256 / palette_pixels; + const std::string &name = f.palette_name; + if (name == "") + return NULL; + Client *client = (Client *) gamedef; + ITextureSource *tsrc = client->tsrc(); + + UNORDERED_MAP >::iterator it = + m_palettes.find(name); + if (it == m_palettes.end()) { + // Create palette + if (!tsrc->isKnownSourceImage(name)) { + warningstream << "CNodeDefManager::getPalette(): palette \"" << name + << "\" could not be loaded." << std::endl; + return NULL; + } + video::IImage *img = tsrc->generateImage(name); + std::vector new_palette; + u32 w = img->getDimension().Width; + u32 h = img->getDimension().Height; + // Real area of the image + u32 area = h * w; + if (area != palette_pixels) + warningstream << "CNodeDefManager::getPalette(): the " + << "specified palette image \"" << name << "\" does not " + << "contain exactly " << palette_pixels + << " pixels." << std::endl; + if (area > palette_pixels) + area = palette_pixels; + // For each pixel in the image + for (u32 i = 0; i < area; i++) { + video::SColor c = img->getPixel(i % w, i / w); + // Fill in palette with 'step' colors + for (u32 j = 0; j < step; j++) + new_palette.push_back(c); + } + img->drop(); + // Fill in remaining elements + while (new_palette.size() < 256) + new_palette.push_back(video::SColor(0xFFFFFFFF)); + m_palettes[name] = new_palette; + it = m_palettes.find(name); + } + if (it != m_palettes.end()) + return &((*it).second); + +#endif + return NULL; +} + void CNodeDefManager::updateTextures(IGameDef *gamedef, void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress), void *progress_callback_args) @@ -1325,10 +1486,13 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef, TextureSettings tsettings; tsettings.readSettings(); + m_palettes.clear(); u32 size = m_content_features.size(); for (u32 i = 0; i < size; i++) { - m_content_features[i].updateTextures(tsrc, shdsrc, meshmanip, client, tsettings); + ContentFeatures *f = &(m_content_features[i]); + f->palette = getPalette(*f, gamedef); + f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings); progress_callback(progress_callback_args, i, size); } #endif @@ -1429,6 +1593,19 @@ IWritableNodeDefManager *createNodeDefManager() //// Serialization of old ContentFeatures formats void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const { + u8 compatible_param_type_2 = param_type_2; + if ((protocol_version < 28) + && (compatible_param_type_2 == CPT2_MESHOPTIONS)) + compatible_param_type_2 = CPT2_NONE; + else if (protocol_version < 30) { + if (compatible_param_type_2 == CPT2_COLOR) + compatible_param_type_2 = CPT2_NONE; + else if (compatible_param_type_2 == CPT2_COLORED_FACEDIR) + compatible_param_type_2 = CPT2_FACEDIR; + else if (compatible_param_type_2 == CPT2_COLORED_WALLMOUNTED) + compatible_param_type_2 = CPT2_WALLMOUNTED; + } + if (protocol_version == 13) { writeU8(os, 5); // version @@ -1454,7 +1631,7 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const writeU8(os, post_effect_color.getGreen()); writeU8(os, post_effect_color.getBlue()); writeU8(os, param_type); - writeU8(os, param_type_2); + writeU8(os, compatible_param_type_2); writeU8(os, is_ground_content); writeU8(os, light_propagates); writeU8(os, sunlight_propagates); @@ -1483,9 +1660,9 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const os<first); - writeS16(os, i->second); + i = groups.begin(); i != groups.end(); ++i) { + os<first); + writeS16(os, i->second); } writeU8(os, drawtype); writeF1000(os, visual_scale); @@ -1502,7 +1679,7 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const writeU8(os, post_effect_color.getGreen()); writeU8(os, post_effect_color.getBlue()); writeU8(os, param_type); - writeU8(os, param_type_2); + writeU8(os, compatible_param_type_2); writeU8(os, is_ground_content); writeU8(os, light_propagates); writeU8(os, sunlight_propagates); @@ -1530,6 +1707,68 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const writeU8(os, drowning); writeU8(os, leveled); writeU8(os, liquid_range); + } + else if(protocol_version >= 24 && protocol_version < 30) { + writeU8(os, protocol_version < 27 ? 7 : 8); + + os << serializeString(name); + writeU16(os, groups.size()); + for (ItemGroupList::const_iterator i = groups.begin(); + i != groups.end(); ++i) { + os << serializeString(i->first); + writeS16(os, i->second); + } + writeU8(os, drawtype); + writeF1000(os, visual_scale); + writeU8(os, 6); + for (u32 i = 0; i < 6; i++) + tiledef[i].serialize(os, protocol_version); + writeU8(os, CF_SPECIAL_COUNT); + for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) + tiledef_special[i].serialize(os, protocol_version); + writeU8(os, alpha); + writeU8(os, post_effect_color.getAlpha()); + writeU8(os, post_effect_color.getRed()); + writeU8(os, post_effect_color.getGreen()); + writeU8(os, post_effect_color.getBlue()); + writeU8(os, param_type); + writeU8(os, compatible_param_type_2); + writeU8(os, is_ground_content); + writeU8(os, light_propagates); + writeU8(os, sunlight_propagates); + writeU8(os, walkable); + writeU8(os, pointable); + writeU8(os, diggable); + writeU8(os, climbable); + writeU8(os, buildable_to); + os << serializeString(""); // legacy: used to be metadata_name + writeU8(os, liquid_type); + os << serializeString(liquid_alternative_flowing); + os << serializeString(liquid_alternative_source); + writeU8(os, liquid_viscosity); + writeU8(os, liquid_renewable); + writeU8(os, light_source); + writeU32(os, damage_per_second); + node_box.serialize(os, protocol_version); + selection_box.serialize(os, protocol_version); + writeU8(os, legacy_facedir_simple); + writeU8(os, legacy_wallmounted); + serializeSimpleSoundSpec(sound_footstep, os); + serializeSimpleSoundSpec(sound_dig, os); + serializeSimpleSoundSpec(sound_dug, os); + writeU8(os, rightclickable); + writeU8(os, drowning); + writeU8(os, leveled); + writeU8(os, liquid_range); + writeU8(os, waving); + os << serializeString(mesh); + collision_box.serialize(os, protocol_version); + writeU8(os, floodable); + writeU16(os, connects_to_ids.size()); + for (std::set::const_iterator i = connects_to_ids.begin(); + i != connects_to_ids.end(); ++i) + writeU16(os, *i); + writeU8(os, connect_sides); } else throw SerializationError("ContentFeatures::serialize(): " "Unsupported version requested"); @@ -1642,7 +1881,73 @@ void ContentFeatures::deSerializeOld(std::istream &is, int version) drowning = readU8(is); leveled = readU8(is); liquid_range = readU8(is); - } else { + } else if (version == 7 || version == 8){ + name = deSerializeString(is); + groups.clear(); + u32 groups_size = readU16(is); + for (u32 i = 0; i < groups_size; i++) { + std::string name = deSerializeString(is); + int value = readS16(is); + groups[name] = value; + } + drawtype = (enum NodeDrawType) readU8(is); + + visual_scale = readF1000(is); + if (readU8(is) != 6) + throw SerializationError("unsupported tile count"); + for (u32 i = 0; i < 6; i++) + tiledef[i].deSerialize(is, version, drawtype); + if (readU8(is) != CF_SPECIAL_COUNT) + throw SerializationError("unsupported CF_SPECIAL_COUNT"); + for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) + tiledef_special[i].deSerialize(is, version, drawtype); + alpha = readU8(is); + post_effect_color.setAlpha(readU8(is)); + post_effect_color.setRed(readU8(is)); + post_effect_color.setGreen(readU8(is)); + post_effect_color.setBlue(readU8(is)); + param_type = (enum ContentParamType) readU8(is); + param_type_2 = (enum ContentParamType2) readU8(is); + is_ground_content = readU8(is); + light_propagates = readU8(is); + sunlight_propagates = readU8(is); + walkable = readU8(is); + pointable = readU8(is); + diggable = readU8(is); + climbable = readU8(is); + buildable_to = readU8(is); + deSerializeString(is); // legacy: used to be metadata_name + liquid_type = (enum LiquidType) readU8(is); + liquid_alternative_flowing = deSerializeString(is); + liquid_alternative_source = deSerializeString(is); + liquid_viscosity = readU8(is); + liquid_renewable = readU8(is); + light_source = readU8(is); + light_source = MYMIN(light_source, LIGHT_MAX); + damage_per_second = readU32(is); + node_box.deSerialize(is); + selection_box.deSerialize(is); + legacy_facedir_simple = readU8(is); + legacy_wallmounted = readU8(is); + deSerializeSimpleSoundSpec(sound_footstep, is); + deSerializeSimpleSoundSpec(sound_dig, is); + deSerializeSimpleSoundSpec(sound_dug, is); + rightclickable = readU8(is); + drowning = readU8(is); + leveled = readU8(is); + liquid_range = readU8(is); + waving = readU8(is); + try { + mesh = deSerializeString(is); + collision_box.deSerialize(is); + floodable = readU8(is); + u16 connects_to_size = readU16(is); + connects_to_ids.clear(); + for (u16 i = 0; i < connects_to_size; i++) + connects_to_ids.insert(readU16(is)); + connect_sides = readU8(is); + } catch (SerializationError &e) {}; + }else{ throw SerializationError("unsupported ContentFeatures version"); } } @@ -1736,19 +2041,23 @@ bool CNodeDefManager::nodeboxConnects(MapNode from, MapNode to, u8 connect_face) // does to node declare usable faces? if (f2.connect_sides > 0) { - if ((f2.param_type_2 == CPT2_FACEDIR) && (connect_face >= 4)) { - static const u8 rot[33 * 4] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 - back - 8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 - right - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 16, 8, 4, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - front - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 32, 16, 8, 4 // 32 - left - }; - return (f2.connect_sides & rot[(connect_face * 4) + to.param2]); + if ((f2.param_type_2 == CPT2_FACEDIR || + f2.param_type_2 == CPT2_COLORED_FACEDIR) + && (connect_face >= 4)) { + static const u8 rot[33 * 4] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 4 - back + 8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 8 - right + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 4, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 16 - front + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 32, 16, 8, 4 // 32 - left + }; + return (f2.connect_sides + & rot[(connect_face * 4) + (to.param2 & 0x1F)]); } return (f2.connect_sides & connect_face); } diff --git a/src/nodedef.h b/src/nodedef.h index 183b95d87..6275b41ce 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -68,7 +68,13 @@ enum ContentParamType2 // 2D rotation for things like plants CPT2_DEGROTATE, // Mesh options for plants - CPT2_MESHOPTIONS + CPT2_MESHOPTIONS, + // Index for palette + CPT2_COLOR, + // 3 bits of palette index, then facedir + CPT2_COLORED_FACEDIR, + // 5 bits of palette index, then wallmounted + CPT2_COLORED_WALLMOUNTED }; enum LiquidType @@ -170,6 +176,11 @@ struct TileDef bool backface_culling; // Takes effect only in special cases bool tileable_horizontal; bool tileable_vertical; + //! If true, the tile has its own color. + bool has_color; + //! The color of the tile. + video::SColor color; + struct TileAnimationParams animation; TileDef() @@ -178,6 +189,8 @@ struct TileDef backface_culling = true; tileable_horizontal = true; tileable_vertical = true; + has_color = false; + color = video::SColor(0xFFFFFFFF); animation.type = TAT_NONE; } @@ -191,7 +204,7 @@ struct ContentFeatures { /* Cached stuff - */ + */ #ifndef SERVER // 0 1 2 3 4 5 // up down right left back front @@ -211,12 +224,19 @@ struct ContentFeatures /* Actual data - */ + */ + + // --- GENERAL PROPERTIES --- std::string name; // "" = undefined node ItemGroupList groups; // Same as in itemdef + // Type of MapNode::param1 + ContentParamType param_type; + // Type of MapNode::param2 + ContentParamType2 param_type_2; + + // --- VISUAL PROPERTIES --- - // Visual definition enum NodeDrawType drawtype; std::string mesh; #ifndef SERVER @@ -226,19 +246,38 @@ struct ContentFeatures float visual_scale; // Misc. scale parameter TileDef tiledef[6]; TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid + // If 255, the node is opaque. + // Otherwise it uses texture alpha. u8 alpha; - + // The color of the node. + video::SColor color; + std::string palette_name; + std::vector *palette; + // Used for waving leaves/plants + u8 waving; + // for NDT_CONNECTED pairing + u8 connect_sides; + std::vector connects_to; + std::set connects_to_ids; // Post effect color, drawn when the camera is inside the node. video::SColor post_effect_color; + // Flowing liquid or snow, value = default level + u8 leveled; + + // --- LIGHTING-RELATED --- - // Type of MapNode::param1 - ContentParamType param_type; - // Type of MapNode::param2 - ContentParamType2 param_type_2; - // True for all ground-like things like stone and mud, false for eg. trees - bool is_ground_content; bool light_propagates; bool sunlight_propagates; + // Amount of light the node emits + u8 light_source; + + // --- MAP GENERATION --- + + // True for all ground-like things like stone and mud, false for eg. trees + bool is_ground_content; + + // --- INTERACTION PROPERTIES --- + // This is used for collision detection. // Also for general solidness queries. bool walkable; @@ -250,12 +289,12 @@ struct ContentFeatures bool climbable; // Player can build on these bool buildable_to; - // Liquids flow into and replace node - bool floodable; // Player cannot build to these (placement prediction disabled) bool rightclickable; - // Flowing liquid or snow, value = default level - u8 leveled; + u32 damage_per_second; + + // --- LIQUID PROPERTIES --- + // Whether the node is non-liquid, source liquid or flowing liquid enum LiquidType liquid_type; // If the content is liquid, this is the flowing version of the liquid. @@ -271,29 +310,28 @@ struct ContentFeatures // Number of flowing liquids surrounding source u8 liquid_range; u8 drowning; - // Amount of light the node emits - u8 light_source; - u32 damage_per_second; + // Liquids flow into and replace node + bool floodable; + + // --- NODEBOXES --- + NodeBox node_box; NodeBox selection_box; NodeBox collision_box; - // Used for waving leaves/plants - u8 waving; - // Compatibility with old maps - // Set to true if paramtype used to be 'facedir_simple' - bool legacy_facedir_simple; - // Set to true if wall_mounted used to be set to true - bool legacy_wallmounted; - // for NDT_CONNECTED pairing - u8 connect_sides; - // Sound properties + // --- SOUND PROPERTIES --- + SimpleSoundSpec sound_footstep; SimpleSoundSpec sound_dig; SimpleSoundSpec sound_dug; - std::vector connects_to; - std::set connects_to_ids; + // --- LEGACY --- + + // Compatibility with old maps + // Set to true if paramtype used to be 'facedir_simple' + bool legacy_facedir_simple; + // Set to true if wall_mounted used to be set to true + bool legacy_wallmounted; /* Methods @@ -306,6 +344,14 @@ struct ContentFeatures void deSerialize(std::istream &is); void serializeOld(std::ostream &os, u16 protocol_version) const; void deSerializeOld(std::istream &is, int version); + /*! + * Since vertex alpha is no lnger supported, this method + * adds instructions to the texture names to blend alpha there. + * + * tiledef, tiledef_special and alpha must be initialized + * before calling this. + */ + void correctAlpha(); /* Some handy methods @@ -321,7 +367,7 @@ struct ContentFeatures #ifndef SERVER void fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef, u32 shader_id, bool use_normal_texture, bool backface_culling, - u8 alpha, u8 material_type); + u8 material_type); void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings); #endif diff --git a/src/particles.cpp b/src/particles.cpp index 5f17763e0..e1f292fb6 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -56,7 +56,8 @@ Particle::Particle( v2f texpos, v2f texsize, const struct TileAnimationParams &anim, - u8 glow + u8 glow, + video::SColor color ): scene::ISceneNode(smgr->getRootSceneNode(), smgr) { @@ -77,6 +78,10 @@ Particle::Particle( m_animation_frame = 0; m_animation_time = 0.0; + // Color + m_base_color = color; + m_color = color; + // Particle related m_pos = pos; m_velocity = velocity; @@ -183,12 +188,15 @@ void Particle::updateLight() else light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0); - m_light = decode_light(light + m_glow); + u8 m_light = decode_light(light + m_glow); + m_color.set(255, + m_light * m_base_color.getRed() / 255, + m_light * m_base_color.getGreen() / 255, + m_light * m_base_color.getBlue() / 255); } void Particle::updateVertices() { - video::SColor c(255, m_light, m_light, m_light); f32 tx0, tx1, ty0, ty1; if (m_animation.type != TAT_NONE) { @@ -210,14 +218,14 @@ void Particle::updateVertices() ty1 = m_texpos.Y + m_texsize.Y; } - m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0, - c, tx0, ty1); - m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0, - c, tx1, ty1); - m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0, - c, tx1, ty0); - m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0, - c, tx0, ty0); + m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2, + 0, 0, 0, 0, m_color, tx0, ty1); + m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2, + 0, 0, 0, 0, m_color, tx1, ty1); + m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2, + 0, 0, 0, 0, m_color, tx1, ty0); + m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2, + 0, 0, 0, 0, m_color, tx0, ty0); v3s16 camera_offset = m_env->getCameraOffset(); for(u16 i=0; i<4; i++) @@ -589,35 +597,39 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, } } -void ParticleManager::addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, - LocalPlayer *player, v3s16 pos, const TileSpec tiles[]) +void ParticleManager::addDiggingParticles(IGameDef* gamedef, + scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, + const MapNode &n, const ContentFeatures &f) { for (u16 j = 0; j < 32; j++) // set the amount of particles here { - addNodeParticle(gamedef, smgr, player, pos, tiles); + addNodeParticle(gamedef, smgr, player, pos, n, f); } } -void ParticleManager::addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, - LocalPlayer *player, v3s16 pos, const TileSpec tiles[]) +void ParticleManager::addPunchingParticles(IGameDef* gamedef, + scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, + const MapNode &n, const ContentFeatures &f) { - addNodeParticle(gamedef, smgr, player, pos, tiles); + addNodeParticle(gamedef, smgr, player, pos, n, f); } -void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr, - LocalPlayer *player, v3s16 pos, const TileSpec tiles[]) +void ParticleManager::addNodeParticle(IGameDef* gamedef, + scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos, + const MapNode &n, const ContentFeatures &f) { // Texture u8 texid = myrand_range(0, 5); + const TileSpec &tile = f.tiles[texid]; video::ITexture *texture; struct TileAnimationParams anim; anim.type = TAT_NONE; // Only use first frame of animated texture - if (tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION) - texture = tiles[texid].frames[0].texture; + if (tile.material_flags & MATERIAL_FLAG_ANIMATION) + texture = tile.frames[0].texture; else - texture = tiles[texid].texture; + texture = tile.texture; float size = rand() % 64 / 512.; float visual_size = BS * size; @@ -638,6 +650,12 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s (f32) pos.Z + rand() %100 /200. - 0.25 ); + video::SColor color; + if (tile.has_color) + color = tile.color; + else + n.getColor(f, &color); + Particle* toadd = new Particle( gamedef, smgr, @@ -655,7 +673,8 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s texpos, texsize, anim, - 0); + 0, + color); addParticle(toadd); } diff --git a/src/particles.h b/src/particles.h index 5464e6672..3177f2cfd 100644 --- a/src/particles.h +++ b/src/particles.h @@ -32,6 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc., struct ClientEvent; class ParticleManager; class ClientEnvironment; +class MapNode; +class ContentFeatures; class Particle : public scene::ISceneNode { @@ -53,7 +55,8 @@ class Particle : public scene::ISceneNode v2f texpos, v2f texsize, const struct TileAnimationParams &anim, - u8 glow + u8 glow, + video::SColor color = video::SColor(0xFFFFFFFF) ); ~Particle(); @@ -100,7 +103,10 @@ private: v3f m_acceleration; LocalPlayer *m_player; float m_size; - u8 m_light; + //! Color without lighting + video::SColor m_base_color; + //! Final rendered color + video::SColor m_color; bool m_collisiondetection; bool m_collision_removal; bool m_vertical; @@ -184,13 +190,16 @@ public: scene::ISceneManager* smgr, LocalPlayer *player); void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, - LocalPlayer *player, v3s16 pos, const TileSpec tiles[]); + LocalPlayer *player, v3s16 pos, const MapNode &n, + const ContentFeatures &f); void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr, - LocalPlayer *player, v3s16 pos, const TileSpec tiles[]); + LocalPlayer *player, v3s16 pos, const MapNode &n, + const ContentFeatures &f); void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr, - LocalPlayer *player, v3s16 pos, const TileSpec tiles[]); + LocalPlayer *player, v3s16 pos, const MapNode &n, + const ContentFeatures &f); protected: void addParticle(Particle* toadd); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 84af4583b..ebc951295 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -332,6 +332,10 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) L, index, "tileable_horizontal", default_tiling); tiledef.tileable_vertical = getboolfield_default( L, index, "tileable_vertical", default_tiling); + // color = ... + lua_getfield(L, index, "color"); + tiledef.has_color = read_color(L, -1, &tiledef.color); + lua_pop(L, 1); // animation = {} lua_getfield(L, index, "animation"); tiledef.animation = read_animation_definition(L, -1); @@ -450,6 +454,13 @@ ContentFeatures read_content_features(lua_State *L, int index) if (usealpha) f.alpha = 0; + // Read node color. + lua_getfield(L, index, "color"); + read_color(L, -1, &f.color); + lua_pop(L, 1); + + getstringfield(L, index, "palette", f.palette_name); + /* Other stuff */ lua_getfield(L, index, "post_effect_color"); @@ -461,6 +472,13 @@ ContentFeatures read_content_features(lua_State *L, int index) f.param_type_2 = (ContentParamType2)getenumfield(L, index, "paramtype2", ScriptApiNode::es_ContentParamType2, CPT2_NONE); + if (f.palette_name != "" && + !(f.param_type_2 == CPT2_COLOR || + f.param_type_2 == CPT2_COLORED_FACEDIR || + f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) + warningstream << "Node " << f.name.c_str() + << " has a palette, but not a suitable paramtype2." << std::endl; + // Warn about some deprecated fields warn_if_field_exists(L, index, "wall_mounted", "Deprecated; use paramtype2 = 'wallmounted'"); diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index 379ed773f..23c8f43b9 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -59,6 +59,9 @@ struct EnumString ScriptApiNode::es_ContentParamType2[] = {CPT2_LEVELED, "leveled"}, {CPT2_DEGROTATE, "degrotate"}, {CPT2_MESHOPTIONS, "meshoptions"}, + {CPT2_COLOR, "color"}, + {CPT2_COLORED_FACEDIR, "colorfacedir"}, + {CPT2_COLORED_WALLMOUNTED, "colorwallmounted"}, {0, NULL}, }; diff --git a/src/shader.cpp b/src/shader.cpp index c0ecf738d..79485025b 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -543,7 +543,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; break; case TILE_MATERIAL_LIQUID_TRANSPARENT: - shaderinfo.base_material = video::EMT_TRANSPARENT_VERTEX_ALPHA; + shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; break; case TILE_MATERIAL_LIQUID_OPAQUE: shaderinfo.base_material = video::EMT_SOLID; diff --git a/src/wieldmesh.cpp b/src/wieldmesh.cpp index c305238fe..089a67f33 100644 --- a/src/wieldmesh.cpp +++ b/src/wieldmesh.cpp @@ -318,6 +318,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); m_material_type = shdrsrc->getShaderInfo(shader_id).material; } + m_colors.clear(); // If wield_image is defined, it overrides everything else if (def.wield_image != "") { @@ -358,28 +359,30 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) material_count = 6; } for (u32 i = 0; i < material_count; ++i) { + const TileSpec *tile = &(f.tiles[i]); video::SMaterial &material = m_meshnode->getMaterial(i); material.setFlag(video::EMF_BACK_FACE_CULLING, true); material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); - bool animated = (f.tiles[i].animation_frame_count > 1); + bool animated = (tile->animation_frame_count > 1); if (animated) { - FrameSpec animation_frame = f.tiles[i].frames[0]; + FrameSpec animation_frame = tile->frames[0]; material.setTexture(0, animation_frame.texture); } else { - material.setTexture(0, f.tiles[i].texture); + material.setTexture(0, tile->texture); } + m_colors.push_back(tile->color); material.MaterialType = m_material_type; if (m_enable_shaders) { - if (f.tiles[i].normal_texture) { + if (tile->normal_texture) { if (animated) { - FrameSpec animation_frame = f.tiles[i].frames[0]; + FrameSpec animation_frame = tile->frames[0]; material.setTexture(1, animation_frame.normal_texture); } else { - material.setTexture(1, f.tiles[i].normal_texture); + material.setTexture(1, tile->normal_texture); } } - material.setTexture(2, f.tiles[i].flags_texture); + material.setTexture(2, tile->flags_texture); } } return; @@ -393,11 +396,28 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) changeToMesh(NULL); } -void WieldMeshSceneNode::setColor(video::SColor color) +void WieldMeshSceneNode::setColor(video::SColor c) { assert(!m_lighting); - setMeshColor(m_meshnode->getMesh(), color); - shadeMeshFaces(m_meshnode->getMesh()); + scene::IMesh *mesh=m_meshnode->getMesh(); + if (mesh == NULL) + return; + + u8 red = c.getRed(); + u8 green = c.getGreen(); + u8 blue = c.getBlue(); + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) { + video::SColor bc(0xFFFFFFFF); + if (m_colors.size() > j) + bc = m_colors[j]; + video::SColor buffercolor(255, + bc.getRed() * red / 255, + bc.getGreen() * green / 255, + bc.getBlue() * blue / 255); + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + colorizeMeshBuffer(buf, &buffercolor); + } } void WieldMeshSceneNode::render() @@ -464,7 +484,6 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item) } else if (f.drawtype == NDT_PLANTLIKE) { mesh = getExtrudedMesh(tsrc, tsrc->getTextureName(f.tiles[0].texture_id)); - return mesh; } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES || f.drawtype == NDT_LIQUID || f.drawtype == NDT_FLOWINGLIQUID) { mesh = cloneMesh(g_extrusion_mesh_cache->createCube()); @@ -477,8 +496,6 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item) mesh = cloneMesh(mapblock_mesh.getMesh()); translateMesh(mesh, v3f(-BS, -BS, -BS)); scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); - rotateMeshXZby(mesh, -45); - rotateMeshYZby(mesh, -30); u32 mc = mesh->getMeshBufferCount(); for (u32 i = 0; i < mc; ++i) { @@ -492,28 +509,29 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item) material1.setTexture(3, material2.getTexture(3)); material1.MaterialType = material2.MaterialType; } - return mesh; } - shadeMeshFaces(mesh); - rotateMeshXZby(mesh, -45); - rotateMeshYZby(mesh, -30); - u32 mc = mesh->getMeshBufferCount(); for (u32 i = 0; i < mc; ++i) { - video::SMaterial &material = mesh->getMeshBuffer(i)->getMaterial(); + const TileSpec *tile = &(f.tiles[i]); + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + colorizeMeshBuffer(buf, &tile->color); + video::SMaterial &material = buf->getMaterial(); material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setFlag(video::EMF_TRILINEAR_FILTER, false); material.setFlag(video::EMF_BACK_FACE_CULLING, true); material.setFlag(video::EMF_LIGHTING, false); - if (f.tiles[i].animation_frame_count > 1) { - FrameSpec animation_frame = f.tiles[i].frames[0]; + if (tile->animation_frame_count > 1) { + FrameSpec animation_frame = tile->frames[0]; material.setTexture(0, animation_frame.texture); } else { - material.setTexture(0, f.tiles[i].texture); + material.setTexture(0, tile->texture); } } + + rotateMeshXZby(mesh, -45); + rotateMeshYZby(mesh, -30); return mesh; } return NULL; diff --git a/src/wieldmesh.h b/src/wieldmesh.h index 0162c5e5a..2e78232ae 100644 --- a/src/wieldmesh.h +++ b/src/wieldmesh.h @@ -70,6 +70,11 @@ private: bool m_anisotropic_filter; bool m_bilinear_filter; bool m_trilinear_filter; + /*! + * Stores the colors of the mesh's mesh buffers. + * This does not include lighting. + */ + std::vector m_colors; // Bounding box culling is disabled for this type of scene node, // so this variable is just required so we can implement -- cgit v1.2.3 From 814ee971f70a8ef1fa4a470bcf385300686e9e70 Mon Sep 17 00:00:00 2001 From: sapier Date: Sat, 21 Jan 2017 15:58:07 +0100 Subject: Make entity on_punch have same signature and behaviour as player on_punch --- doc/lua_api.txt | 5 ++++- src/content_sao.cpp | 32 ++++++++++++++++++-------------- src/script/cpp_api/s_entity.cpp | 15 +++++++++------ src/script/cpp_api/s_entity.h | 4 ++-- 4 files changed, 33 insertions(+), 23 deletions(-) (limited to 'src/script/cpp_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ee7d57c2f..ff745c1c2 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1392,7 +1392,7 @@ a non-tool item, so that it can do something else than take damage. On the Lua side, every punch calls: - entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction) + entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction, damage) This should never be called directly, because damage is usually not handled by the entity itself. @@ -1403,6 +1403,9 @@ the entity itself. * `tool_capabilities` can be `nil`. * `direction` is a unit vector, pointing from the source of the punch to the punched object. +* `damage` damage that will be done to entity +Return value of this function will determin if damage is done by this function +(retval true) or shall be done by engine (retval false) To punch an entity/object in Lua, call: diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 35133490e..7a1171eb7 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -578,28 +578,32 @@ int LuaEntitySAO::punch(v3f dir, punchitem, time_from_last_punch); - if (result.did_punch) { - setHP(getHP() - result.damage); + bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher, + time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0); - if (result.damage > 0) { - std::string punchername = puncher ? puncher->getDescription() : "nil"; + if (!damage_handled) { + if (result.did_punch) { + setHP(getHP() - result.damage); - actionstream << getDescription() << " punched by " - << punchername << ", damage " << result.damage - << " hp, health now " << getHP() << " hp" << std::endl; - } + if (result.damage > 0) { + std::string punchername = puncher ? puncher->getDescription() : "nil"; - std::string str = gob_cmd_punched(result.damage, getHP()); - // create message and add to list - ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push(aom); + actionstream << getDescription() << " punched by " + << punchername << ", damage " << result.damage + << " hp, health now " << getHP() << " hp" << std::endl; + } + + std::string str = gob_cmd_punched(result.damage, getHP()); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } } if (getHP() == 0) m_removed = true; - m_env->getScriptIface()->luaentity_Punch(m_id, puncher, - time_from_last_punch, toolcap, dir); + return result.wear; } diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index 378a6bf09..fcd8dd4a0 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -224,10 +224,10 @@ void ScriptApiEntity::luaentity_Step(u16 id, float dtime) } // Calls entity:on_punch(ObjectRef puncher, time_from_last_punch, -// tool_capabilities, direction) -void ScriptApiEntity::luaentity_Punch(u16 id, +// tool_capabilities, direction, damage) +bool ScriptApiEntity::luaentity_Punch(u16 id, ServerActiveObject *puncher, float time_from_last_punch, - const ToolCapabilities *toolcap, v3f dir) + const ToolCapabilities *toolcap, v3f dir, s16 damage) { SCRIPTAPI_PRECHECKHEADER @@ -242,8 +242,8 @@ void ScriptApiEntity::luaentity_Punch(u16 id, // Get function lua_getfield(L, -1, "on_punch"); if (lua_isnil(L, -1)) { - lua_pop(L, 2); // Pop on_punch and entitu - return; + lua_pop(L, 2); // Pop on_punch and entity + return false; } luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); // self @@ -251,11 +251,14 @@ void ScriptApiEntity::luaentity_Punch(u16 id, lua_pushnumber(L, time_from_last_punch); push_tool_capabilities(L, *toolcap); push_v3f(L, dir); + lua_pushnumber(L, damage); setOriginFromTable(object); - PCALL_RES(lua_pcall(L, 5, 0, error_handler)); + PCALL_RES(lua_pcall(L, 6, 0, error_handler)); + bool retval = lua_toboolean(L, -1); lua_pop(L, 2); // Pop object and error handler + return retval; } // Calls entity:on_rightclick(ObjectRef clicker) diff --git a/src/script/cpp_api/s_entity.h b/src/script/cpp_api/s_entity.h index 8df9d7f00..4e2a056bb 100644 --- a/src/script/cpp_api/s_entity.h +++ b/src/script/cpp_api/s_entity.h @@ -38,9 +38,9 @@ public: void luaentity_GetProperties(u16 id, ObjectProperties *prop); void luaentity_Step(u16 id, float dtime); - void luaentity_Punch(u16 id, + bool luaentity_Punch(u16 id, ServerActiveObject *puncher, float time_from_last_punch, - const ToolCapabilities *toolcap, v3f dir); + const ToolCapabilities *toolcap, v3f dir, s16 damage); void luaentity_Rightclick(u16 id, ServerActiveObject *clicker); }; -- cgit v1.2.3 From 3b9ae409c7d7e9445099b86c85392dba4771e08a Mon Sep 17 00:00:00 2001 From: Duane Robertson Date: Mon, 30 Jan 2017 22:37:04 -0600 Subject: Tell on_punch to expect a return value The return value should be interpreted as a boolean saying whether the lua on_punch function handled damage or the system needs to. --- src/script/cpp_api/s_entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/script/cpp_api') diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index fcd8dd4a0..9e2193970 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -254,7 +254,7 @@ bool ScriptApiEntity::luaentity_Punch(u16 id, lua_pushnumber(L, damage); setOriginFromTable(object); - PCALL_RES(lua_pcall(L, 6, 0, error_handler)); + PCALL_RES(lua_pcall(L, 6, 1, error_handler)); bool retval = lua_toboolean(L, -1); lua_pop(L, 2); // Pop object and error handler -- cgit v1.2.3 From 2efae3ffd720095222c800e016286a45c9fe1e5c Mon Sep 17 00:00:00 2001 From: Loic Blot Date: Sat, 21 Jan 2017 15:02:08 +0100 Subject: [CSM] Client side modding * rename GameScripting to ServerScripting * Make getBuiltinLuaPath static serverside * Add on_shutdown callback * Add on_receiving_chat_message & on_sending_chat_message callbacks * ScriptApiBase: use IGameDef instead of Server This permits to share common attribute between client & server * Enable mod security in client side modding without conditions --- builtin/client/init.lua | 22 +++++++ builtin/client/register.lua | 62 +++++++++++++++++++ builtin/init.lua | 3 + src/client.cpp | 45 +++++++++++--- src/client.h | 10 +++ src/content_abm.cpp | 2 +- src/content_sao.cpp | 2 +- src/emerge.cpp | 2 +- src/environment.cpp | 2 +- src/game.cpp | 2 + src/gamedef.h | 7 ++- src/guiFormSpecMenu.cpp | 2 +- src/inventorymanager.cpp | 2 +- src/network/clientpackethandler.cpp | 6 +- src/network/serverpackethandler.cpp | 2 +- src/script/CMakeLists.txt | 7 ++- src/script/clientscripting.cpp | 54 ++++++++++++++++ src/script/clientscripting.h | 40 ++++++++++++ src/script/cpp_api/CMakeLists.txt | 1 + src/script/cpp_api/s_base.cpp | 21 +++++-- src/script/cpp_api/s_base.h | 14 ++++- src/script/cpp_api/s_client.cpp | 61 ++++++++++++++++++ src/script/cpp_api/s_client.h | 36 +++++++++++ src/script/cpp_api/s_security.cpp | 12 ++-- src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_client.cpp | 33 ++++++++++ src/script/lua_api/l_client.h | 36 +++++++++++ src/script/lua_api/l_env.cpp | 6 +- src/script/lua_api/l_env.h | 2 +- src/script/lua_api/l_object.cpp | 2 +- src/script/scripting_game.cpp | 119 ------------------------------------ src/script/scripting_game.h | 57 ----------------- src/script/serverscripting.cpp | 119 ++++++++++++++++++++++++++++++++++++ src/script/serverscripting.h | 57 +++++++++++++++++ src/server.cpp | 6 +- src/server.h | 10 +-- src/serverenvironment.cpp | 4 +- src/serverenvironment.h | 8 +-- src/unittest/test.cpp | 9 ++- 39 files changed, 655 insertions(+), 231 deletions(-) create mode 100644 builtin/client/init.lua create mode 100644 builtin/client/register.lua create mode 100644 src/script/clientscripting.cpp create mode 100644 src/script/clientscripting.h create mode 100644 src/script/cpp_api/s_client.cpp create mode 100644 src/script/cpp_api/s_client.h create mode 100644 src/script/lua_api/l_client.cpp create mode 100644 src/script/lua_api/l_client.h delete mode 100644 src/script/scripting_game.cpp delete mode 100644 src/script/scripting_game.h create mode 100644 src/script/serverscripting.cpp create mode 100644 src/script/serverscripting.h (limited to 'src/script/cpp_api') diff --git a/builtin/client/init.lua b/builtin/client/init.lua new file mode 100644 index 000000000..d14301ade --- /dev/null +++ b/builtin/client/init.lua @@ -0,0 +1,22 @@ +-- Minetest: builtin/client/init.lua +local scriptpath = core.get_builtin_path()..DIR_DELIM +local clientpath = scriptpath.."client"..DIR_DELIM + +dofile(clientpath .. "register.lua") + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_shutdown(function() + print("shutdown client") +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_receiving_chat_messages(function(message) + print("Received message " .. message) + return false +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_sending_chat_messages(function(message) + print("Sending message " .. message) + return false +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua new file mode 100644 index 000000000..c793195a1 --- /dev/null +++ b/builtin/client/register.lua @@ -0,0 +1,62 @@ + +core.callback_origins = {} + +function core.run_callbacks(callbacks, mode, ...) + assert(type(callbacks) == "table") + local cb_len = #callbacks + if cb_len == 0 then + if mode == 2 or mode == 3 then + return true + elseif mode == 4 or mode == 5 then + return false + end + end + local ret + for i = 1, cb_len do + local cb_ret = callbacks[i](...) + + if mode == 0 and i == 1 or mode == 1 and i == cb_len then + ret = cb_ret + elseif mode == 2 then + if not cb_ret or i == 1 then + ret = cb_ret + end + elseif mode == 3 then + if cb_ret then + return cb_ret + end + ret = cb_ret + elseif mode == 4 then + if (cb_ret and not ret) or i == 1 then + ret = cb_ret + end + elseif mode == 5 and cb_ret then + return cb_ret + end + end + return ret +end + +-- +-- Callback registration +-- + +local function make_registration() + local t = {} + local registerfunc = function(func) + t[#t + 1] = func + core.callback_origins[func] = { + mod = core.get_current_modname() or "??", + name = debug.getinfo(1, "n").name or "??" + } + --local origin = core.callback_origins[func] + --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func)) + end + return t, registerfunc +end + +core.registered_on_shutdown, core.register_on_shutdown = make_registration() +core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration() +core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration() + + diff --git a/builtin/init.lua b/builtin/init.lua index b34ad14a0..590f7fa8c 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -27,6 +27,7 @@ minetest = core -- Load other files local scriptdir = core.get_builtin_path() .. DIR_DELIM local gamepath = scriptdir .. "game" .. DIR_DELIM +local clientpath = scriptdir .. "client" .. DIR_DELIM local commonpath = scriptdir .. "common" .. DIR_DELIM local asyncpath = scriptdir .. "async" .. DIR_DELIM @@ -45,6 +46,8 @@ elseif INIT == "mainmenu" then end elseif INIT == "async" then dofile(asyncpath .. "init.lua") +elseif INIT == "client" then + dofile(clientpath .. "init.lua") else error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) end diff --git a/src/client.cpp b/src/client.cpp index 30058a2b0..faf454b35 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -32,28 +32,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client.h" #include "network/clientopcodes.h" #include "filesys.h" -#include "porting.h" #include "mapblock_mesh.h" #include "mapblock.h" #include "minimap.h" -#include "settings.h" +#include "mods.h" #include "profiler.h" #include "gettext.h" -#include "log.h" -#include "nodemetadata.h" -#include "itemdef.h" -#include "shader.h" #include "clientmap.h" #include "clientmedia.h" -#include "sound.h" -#include "IMeshCache.h" -#include "config.h" #include "version.h" #include "drawscene.h" #include "database-sqlite3.h" #include "serialization.h" #include "guiscalingfilter.h" -#include "raycast.h" +#include "script/clientscripting.h" extern gui::IGUIEnvironment* guienv; @@ -269,10 +261,36 @@ Client::Client( m_cache_use_tangent_vertices = m_cache_enable_shaders && ( g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion")); + + m_script = new ClientScripting(this); +} + +void Client::initMods() +{ + std::string script_path = getBuiltinLuaPath() + DIR_DELIM "init.lua"; + + m_script->loadMod(script_path, BUILTIN_MOD_NAME); +} + +const std::string Client::getBuiltinLuaPath() +{ + return porting::path_share + DIR_DELIM + "builtin"; +} + +const std::vector& Client::getMods() const +{ + static std::vector client_modspec_temp; + return client_modspec_temp; +} + +const ModSpec* Client::getModSpec(const std::string &modname) const +{ + return NULL; } void Client::Stop() { + m_script->on_shutdown(); //request all client managed threads to stop m_mesh_update_thread.stop(); // Save local server map @@ -280,6 +298,8 @@ void Client::Stop() infostream << "Local map saving ended." << std::endl; m_localdb->endSave(); } + + delete m_script; } bool Client::isShutdown() @@ -1553,6 +1573,11 @@ void Client::typeChatMessage(const std::wstring &message) if(message == L"") return; + // If message was ate by script API, don't send it to server + if (m_script->on_sending_message(wide_to_utf8(message))) { + return; + } + // Send to others sendChatMessage(message); diff --git a/src/client.h b/src/client.h index b33358d94..2fdade61a 100644 --- a/src/client.h +++ b/src/client.h @@ -305,6 +305,8 @@ private: std::map m_packets; }; +class ClientScripting; + class Client : public con::PeerHandler, public InventoryManager, public IGameDef { public: @@ -328,6 +330,8 @@ public: ~Client(); + void initMods(); + /* request all threads managed by client to be stopped */ @@ -428,6 +432,10 @@ public: ClientEnvironment& getEnv() { return m_env; } ITextureSource *tsrc() { return getTextureSource(); } ISoundManager *sound() { return getSoundManager(); } + static const std::string getBuiltinLuaPath(); + + virtual const std::vector &getMods() const; + virtual const ModSpec* getModSpec(const std::string &modname) const; // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent) void removeNode(v3s16 p); @@ -692,6 +700,8 @@ private: bool m_cache_enable_shaders; bool m_cache_use_tangent_vertices; + ClientScripting *m_script; + DISABLE_CLASS_COPY(Client); }; diff --git a/src/content_abm.cpp b/src/content_abm.cpp index ee444ae77..2ab3a968c 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "mapblock.h" // For getNodeBlockPos #include "map.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "log.h" void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef) { diff --git a/src/content_sao.cpp b/src/content_sao.cpp index d4a218505..93662b035 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "remoteplayer.h" #include "server.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "genericobject.h" std::map ServerActiveObject::m_types; diff --git a/src/emerge.cpp b/src/emerge.cpp index 1c9719c48..8719a9eb3 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_schematic.h" #include "nodedef.h" #include "profiler.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "server.h" #include "serverobject.h" #include "settings.h" diff --git a/src/environment.cpp b/src/environment.cpp index 8c1aad9d3..737d93ecd 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "collision.h" #include "serverobject.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "server.h" #include "daynightratio.h" #include "emerge.h" diff --git a/src/game.cpp b/src/game.cpp index 55b2ccec9..9868142f7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2222,6 +2222,8 @@ bool Game::connectToServer(const std::string &playername, fps_control.last_time = device->getTimer()->getTime(); + client->initMods(); + while (device->run()) { limitFps(&fps_control, &dtime); diff --git a/src/gamedef.h b/src/gamedef.h index cb624bd6a..16b53e24f 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -39,6 +39,7 @@ namespace irr { namespace scene { class ISceneManager; }} +struct ModSpec; /* An interface for fetching game-global definitions like tool and mapnode properties @@ -68,7 +69,11 @@ public: ICraftDefManager *cdef() { return getCraftDefManager(); } MtEventManager *event() { return getEventManager(); } - IRollbackManager *rollback() { return getRollbackManager();} + IRollbackManager *rollback() { return getRollbackManager(); } + + virtual const std::vector &getMods() const = 0; + virtual const ModSpec* getModSpec(const std::string &modname) const = 0; + virtual std::string getWorldPath() const { return ""; } }; #endif diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index ae3fad7c6..19cac6241 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -42,7 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "gettime.h" #include "gettext.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "porting.h" #include "settings.h" #include "client.h" diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 469e7396b..6ebc2994b 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventorymanager.h" #include "log.h" #include "serverenvironment.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "serverobject.h" #include "settings.h" #include "craftdef.h" diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index b11f73e86..f1c44c7d8 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "util/strfnd.h" #include "network/clientopcodes.h" +#include "script/clientscripting.h" #include "util/serialize.h" #include "util/srp.h" #include "tileanimation.h" @@ -411,7 +412,10 @@ void Client::handleCommand_ChatMessage(NetworkPacket* pkt) message += (wchar_t)read_wchar; } - m_chat_queue.push(message); + // If chat message not consummed by client lua API + if (!m_script->on_receiving_message(wide_to_utf8(message))) { + m_chat_queue.push(message); + } } void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index ac428e8ed..b707c6fad 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "player.h" #include "rollback_interface.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "settings.h" #include "tool.h" #include "version.h" diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index 5ef672ca9..c96ccc816 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -3,16 +3,17 @@ add_subdirectory(cpp_api) add_subdirectory(lua_api) # Used by server and client -set(common_SCRIPT_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/scripting_game.cpp +set(common_SCRIPT_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/serverscripting.cpp ${common_SCRIPT_COMMON_SRCS} ${common_SCRIPT_CPP_API_SRCS} ${common_SCRIPT_LUA_API_SRCS} PARENT_SCOPE) # Used by client only -set(client_SCRIPT_SRCS +set(client_SCRIPT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/scripting_mainmenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clientscripting.cpp ${client_SCRIPT_COMMON_SRCS} ${client_SCRIPT_CPP_API_SRCS} ${client_SCRIPT_LUA_API_SRCS} diff --git a/src/script/clientscripting.cpp b/src/script/clientscripting.cpp new file mode 100644 index 000000000..43bc6f94e --- /dev/null +++ b/src/script/clientscripting.cpp @@ -0,0 +1,54 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +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 "clientscripting.h" +#include "client.h" +#include "cpp_api/s_internal.h" +#include "lua_api/l_client.h" +#include "lua_api/l_util.h" + +ClientScripting::ClientScripting(Client *client): + ScriptApiBase() +{ + setGameDef(client); + + SCRIPTAPI_PRECHECKHEADER + + // Security is mandatory client side + initializeSecurity(); + + lua_getglobal(L, "core"); + int top = lua_gettop(L); + + InitializeModApi(L, top); + lua_pop(L, 1); + + // Push builtin initialization type + lua_pushstring(L, "client"); + lua_setglobal(L, "INIT"); + + infostream << "SCRIPTAPI: Initialized client game modules" << std::endl; +} + +void ClientScripting::InitializeModApi(lua_State *L, int top) +{ + ModApiUtil::Initialize(L, top); + ModApiClient::Initialize(L, top); +} diff --git a/src/script/clientscripting.h b/src/script/clientscripting.h new file mode 100644 index 000000000..e2a91f695 --- /dev/null +++ b/src/script/clientscripting.h @@ -0,0 +1,40 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef CLIENT_SCRIPTING_H_ +#define CLIENT_SCRIPTING_H_ + +#include "cpp_api/s_base.h" +#include "cpp_api/s_client.h" +#include "cpp_api/s_security.h" + +class Client; +class ClientScripting: + virtual public ScriptApiBase, + public ScriptApiSecurity, + public ScriptApiClient +{ +public: + ClientScripting(Client *client); + +private: + virtual void InitializeModApi(lua_State *L, int top); +}; +#endif diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index be4d0131e..4b13356a8 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -13,6 +13,7 @@ set(common_SCRIPT_CPP_API_SRCS PARENT_SCOPE) set(client_SCRIPT_CPP_API_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/s_client.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_mainmenu.cpp PARENT_SCOPE) diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index cbe5735a7..6a843810f 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -23,12 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_object.h" #include "common/c_converter.h" #include "serverobject.h" -#include "debug.h" #include "filesys.h" -#include "log.h" #include "mods.h" #include "porting.h" #include "util/string.h" +#include "server.h" +#ifndef SERVER +#include "client.h" +#endif extern "C" { @@ -69,7 +71,8 @@ public: */ ScriptApiBase::ScriptApiBase() : - m_luastackmutex() + m_luastackmutex(), + m_gamedef(NULL) { #ifdef SCRIPTAPI_LOCK_DEBUG m_lock_recursion_count = 0; @@ -113,7 +116,6 @@ ScriptApiBase::ScriptApiBase() : // Default to false otherwise m_secure = false; - m_server = NULL; m_environment = NULL; m_guiengine = NULL; } @@ -333,3 +335,14 @@ void ScriptApiBase::objectrefGet(lua_State *L, u16 id) lua_remove(L, -2); // object_refs lua_remove(L, -2); // core } + +Server* ScriptApiBase::getServer() +{ + return dynamic_cast(m_gamedef); +} +#ifndef SERVER +Client* ScriptApiBase::getClient() +{ + return dynamic_cast(m_gamedef); +} +#endif diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index c27235255..19d71df65 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -55,6 +55,10 @@ extern "C" { setOriginFromTableRaw(index, __FUNCTION__) class Server; +#ifndef SERVER +class Client; +#endif +class IGameDef; class Environment; class GUIEngine; class ServerActiveObject; @@ -75,7 +79,11 @@ public: void addObjectReference(ServerActiveObject *cobj); void removeObjectReference(ServerActiveObject *cobj); - Server* getServer() { return m_server; } + IGameDef *getGameDef() { return m_gamedef; } + Server* getServer(); +#ifndef SERVER + Client* getClient(); +#endif std::string getOrigin() { return m_last_run_mod; } void setOriginDirect(const char *origin); @@ -98,7 +106,7 @@ protected: void scriptError(int result, const char *fxn); void stackDump(std::ostream &o); - void setServer(Server* server) { m_server = server; } + void setGameDef(IGameDef* gamedef) { m_gamedef = gamedef; } Environment* getEnv() { return m_environment; } void setEnv(Environment* env) { m_environment = env; } @@ -122,7 +130,7 @@ private: lua_State* m_luastack; - Server* m_server; + IGameDef* m_gamedef; Environment* m_environment; GUIEngine* m_guiengine; }; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp new file mode 100644 index 000000000..08af8ebdc --- /dev/null +++ b/src/script/cpp_api/s_client.cpp @@ -0,0 +1,61 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +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 "s_client.h" +#include "s_internal.h" + +void ScriptApiClient::on_shutdown() +{ + SCRIPTAPI_PRECHECKHEADER + + // Get registered shutdown hooks + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_shutdown"); + // Call callbacks + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); +} + +bool ScriptApiClient::on_sending_message(const std::string &message) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_chat_messages + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_sending_chat_messages"); + // Call callbacks + lua_pushstring(L, message.c_str()); + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + bool ate = lua_toboolean(L, -1); + return ate; +} + +bool ScriptApiClient::on_receiving_message(const std::string &message) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_chat_messages + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_receiving_chat_messages"); + // Call callbacks + lua_pushstring(L, message.c_str()); + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + bool ate = lua_toboolean(L, -1); + return ate; +} diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h new file mode 100644 index 000000000..08fdd8fc0 --- /dev/null +++ b/src/script/cpp_api/s_client.h @@ -0,0 +1,36 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef S_CLIENT_H_ +#define S_CLIENT_H_ + +#include "cpp_api/s_base.h" + +class ScriptApiClient: virtual public ScriptApiBase +{ +public: + // Calls on_shutdown handlers + void on_shutdown(); + + // Chat message handlers + bool on_sending_message(const std::string &message); + bool on_receiving_message(const std::string &message); +}; +#endif diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index be2b884cc..f85cd0c9c 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -382,9 +382,9 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); lua_pop(L, 1); - const Server *server = script->getServer(); - - if (!server) return false; + const IGameDef *gamedef = script->getGameDef(); + if (!gamedef) + return false; // Get mod name lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); @@ -400,7 +400,7 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, // Allow paths in mod path // Don't bother if write access isn't important, since it will be handled later if (write_required || write_allowed != NULL) { - const ModSpec *mod = server->getModSpec(mod_name); + const ModSpec *mod = gamedef->getModSpec(mod_name); if (mod) { str = fs::AbsolutePath(mod->path); if (!str.empty() && fs::PathStartsWith(abs_path, str)) { @@ -414,7 +414,7 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, // Allow read-only access to all mod directories if (!write_required) { - const std::vector mods = server->getMods(); + const std::vector mods = gamedef->getMods(); for (size_t i = 0; i < mods.size(); ++i) { str = fs::AbsolutePath(mods[i].path); if (!str.empty() && fs::PathStartsWith(abs_path, str)) { @@ -423,7 +423,7 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, } } - str = fs::AbsolutePath(server->getWorldPath()); + str = fs::AbsolutePath(gamedef->getWorldPath()); if (!str.empty()) { // Don't allow access to other paths in the world mod/game path. // These have to be blocked so you can't override a trusted mod diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index e82560696..ea3d75ffa 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -23,5 +23,6 @@ set(common_SCRIPT_LUA_API_SRCS PARENT_SCOPE) set(client_SCRIPT_LUA_API_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/l_client.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu.cpp PARENT_SCOPE) diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp new file mode 100644 index 000000000..9c478602a --- /dev/null +++ b/src/script/lua_api/l_client.cpp @@ -0,0 +1,33 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +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 "l_client.h" +#include "l_internal.h" + +int ModApiClient::l_get_current_modname(lua_State *L) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + return 1; +} + +void ModApiClient::Initialize(lua_State *L, int top) +{ + API_FCT(get_current_modname); +} diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h new file mode 100644 index 000000000..332f00132 --- /dev/null +++ b/src/script/lua_api/l_client.h @@ -0,0 +1,36 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef L_CLIENT_H_ +#define L_CLIENT_H_ + +#include "lua_api/l_base.h" + +class ModApiClient : public ModApiBase +{ +private: + // get_current_modname() + static int l_get_current_modname(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); +}; + +#endif diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 2722e35a4..442c4b99a 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_vmanip.h" #include "common/c_converter.h" #include "common/c_content.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "environment.h" #include "server.h" #include "nodedef.h" @@ -49,7 +49,7 @@ struct EnumString ModApiEnvMod::es_ClearObjectsMode[] = void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, u32 active_object_count, u32 active_object_count_wider) { - GameScripting *scriptIface = env->getScriptIface(); + ServerScripting *scriptIface = env->getScriptIface(); scriptIface->realityCheck(); lua_State *L = scriptIface->getStack(); @@ -92,7 +92,7 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, MapNode n) { - GameScripting *scriptIface = env->getScriptIface(); + ServerScripting *scriptIface = env->getScriptIface(); scriptIface->realityCheck(); lua_State *L = scriptIface->getStack(); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 21b235f84..322959411 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -242,7 +242,7 @@ public: }; struct ScriptCallbackState { - GameScripting *script; + ServerScripting *script; int callback_ref; int args_ref; unsigned int refcount; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 9352812ab..be454ad45 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_sao.h" #include "server.h" #include "hud.h" -#include "scripting_game.h" +#include "serverscripting.h" struct EnumString es_HudElementType[] = { diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp deleted file mode 100644 index 4da752263..000000000 --- a/src/script/scripting_game.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -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 "scripting_game.h" -#include "server.h" -#include "log.h" -#include "settings.h" -#include "cpp_api/s_internal.h" -#include "lua_api/l_areastore.h" -#include "lua_api/l_base.h" -#include "lua_api/l_craft.h" -#include "lua_api/l_env.h" -#include "lua_api/l_inventory.h" -#include "lua_api/l_item.h" -#include "lua_api/l_itemstackmeta.h" -#include "lua_api/l_mapgen.h" -#include "lua_api/l_nodemeta.h" -#include "lua_api/l_nodetimer.h" -#include "lua_api/l_noise.h" -#include "lua_api/l_object.h" -#include "lua_api/l_particles.h" -#include "lua_api/l_rollback.h" -#include "lua_api/l_server.h" -#include "lua_api/l_util.h" -#include "lua_api/l_vmanip.h" -#include "lua_api/l_settings.h" -#include "lua_api/l_http.h" -#include "lua_api/l_storage.h" - -extern "C" { -#include "lualib.h" -} - -GameScripting::GameScripting(Server* server) -{ - setServer(server); - - // setEnv(env) is called by ScriptApiEnv::initializeEnvironment() - // once the environment has been created - - SCRIPTAPI_PRECHECKHEADER - - if (g_settings->getBool("secure.enable_security")) { - initializeSecurity(); - } - - lua_getglobal(L, "core"); - int top = lua_gettop(L); - - lua_newtable(L); - lua_setfield(L, -2, "object_refs"); - - lua_newtable(L); - lua_setfield(L, -2, "luaentities"); - - // Initialize our lua_api modules - InitializeModApi(L, top); - lua_pop(L, 1); - - // Push builtin initialization type - lua_pushstring(L, "game"); - lua_setglobal(L, "INIT"); - - infostream << "SCRIPTAPI: Initialized game modules" << std::endl; -} - -void GameScripting::InitializeModApi(lua_State *L, int top) -{ - // Initialize mod api modules - ModApiCraft::Initialize(L, top); - ModApiEnvMod::Initialize(L, top); - ModApiInventory::Initialize(L, top); - ModApiItemMod::Initialize(L, top); - ModApiMapgen::Initialize(L, top); - ModApiParticles::Initialize(L, top); - ModApiRollback::Initialize(L, top); - ModApiServer::Initialize(L, top); - ModApiUtil::Initialize(L, top); - ModApiHttp::Initialize(L, top); - ModApiStorage::Initialize(L, top); - - // Register reference classes (userdata) - InvRef::Register(L); - ItemStackMetaRef::Register(L); - LuaAreaStore::Register(L); - LuaItemStack::Register(L); - LuaPerlinNoise::Register(L); - LuaPerlinNoiseMap::Register(L); - LuaPseudoRandom::Register(L); - LuaPcgRandom::Register(L); - LuaSecureRandom::Register(L); - LuaVoxelManip::Register(L); - NodeMetaRef::Register(L); - NodeTimerRef::Register(L); - ObjectRef::Register(L); - LuaSettings::Register(L); - StorageRef::Register(L); -} - -void log_deprecated(const std::string &message) -{ - log_deprecated(NULL, message); -} diff --git a/src/script/scripting_game.h b/src/script/scripting_game.h deleted file mode 100644 index 970b3e80d..000000000 --- a/src/script/scripting_game.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef SCRIPTING_GAME_H_ -#define SCRIPTING_GAME_H_ - -#include "cpp_api/s_base.h" -#include "cpp_api/s_entity.h" -#include "cpp_api/s_env.h" -#include "cpp_api/s_inventory.h" -#include "cpp_api/s_node.h" -#include "cpp_api/s_player.h" -#include "cpp_api/s_server.h" -#include "cpp_api/s_security.h" - -/*****************************************************************************/ -/* Scripting <-> Game Interface */ -/*****************************************************************************/ - -class GameScripting : - virtual public ScriptApiBase, - public ScriptApiDetached, - public ScriptApiEntity, - public ScriptApiEnv, - public ScriptApiNode, - public ScriptApiPlayer, - public ScriptApiServer, - public ScriptApiSecurity -{ -public: - GameScripting(Server* server); - - // use ScriptApiBase::loadMod() to load mods - -private: - void InitializeModApi(lua_State *L, int top); -}; - -void log_deprecated(const std::string &message); - -#endif /* SCRIPTING_GAME_H_ */ diff --git a/src/script/serverscripting.cpp b/src/script/serverscripting.cpp new file mode 100644 index 000000000..215b2cfd7 --- /dev/null +++ b/src/script/serverscripting.cpp @@ -0,0 +1,119 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +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 "serverscripting.h" +#include "server.h" +#include "log.h" +#include "settings.h" +#include "cpp_api/s_internal.h" +#include "lua_api/l_areastore.h" +#include "lua_api/l_base.h" +#include "lua_api/l_craft.h" +#include "lua_api/l_env.h" +#include "lua_api/l_inventory.h" +#include "lua_api/l_item.h" +#include "lua_api/l_itemstackmeta.h" +#include "lua_api/l_mapgen.h" +#include "lua_api/l_nodemeta.h" +#include "lua_api/l_nodetimer.h" +#include "lua_api/l_noise.h" +#include "lua_api/l_object.h" +#include "lua_api/l_particles.h" +#include "lua_api/l_rollback.h" +#include "lua_api/l_server.h" +#include "lua_api/l_util.h" +#include "lua_api/l_vmanip.h" +#include "lua_api/l_settings.h" +#include "lua_api/l_http.h" +#include "lua_api/l_storage.h" + +extern "C" { +#include "lualib.h" +} + +ServerScripting::ServerScripting(Server* server) +{ + setGameDef(server); + + // setEnv(env) is called by ScriptApiEnv::initializeEnvironment() + // once the environment has been created + + SCRIPTAPI_PRECHECKHEADER + + if (g_settings->getBool("secure.enable_security")) { + initializeSecurity(); + } + + lua_getglobal(L, "core"); + int top = lua_gettop(L); + + lua_newtable(L); + lua_setfield(L, -2, "object_refs"); + + lua_newtable(L); + lua_setfield(L, -2, "luaentities"); + + // Initialize our lua_api modules + InitializeModApi(L, top); + lua_pop(L, 1); + + // Push builtin initialization type + lua_pushstring(L, "game"); + lua_setglobal(L, "INIT"); + + infostream << "SCRIPTAPI: Initialized game modules" << std::endl; +} + +void ServerScripting::InitializeModApi(lua_State *L, int top) +{ + // Initialize mod api modules + ModApiCraft::Initialize(L, top); + ModApiEnvMod::Initialize(L, top); + ModApiInventory::Initialize(L, top); + ModApiItemMod::Initialize(L, top); + ModApiMapgen::Initialize(L, top); + ModApiParticles::Initialize(L, top); + ModApiRollback::Initialize(L, top); + ModApiServer::Initialize(L, top); + ModApiUtil::Initialize(L, top); + ModApiHttp::Initialize(L, top); + ModApiStorage::Initialize(L, top); + + // Register reference classes (userdata) + InvRef::Register(L); + ItemStackMetaRef::Register(L); + LuaAreaStore::Register(L); + LuaItemStack::Register(L); + LuaPerlinNoise::Register(L); + LuaPerlinNoiseMap::Register(L); + LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); + LuaVoxelManip::Register(L); + NodeMetaRef::Register(L); + NodeTimerRef::Register(L); + ObjectRef::Register(L); + LuaSettings::Register(L); + StorageRef::Register(L); +} + +void log_deprecated(const std::string &message) +{ + log_deprecated(NULL, message); +} diff --git a/src/script/serverscripting.h b/src/script/serverscripting.h new file mode 100644 index 000000000..fd97ea40b --- /dev/null +++ b/src/script/serverscripting.h @@ -0,0 +1,57 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef SERVER_SCRIPTING_H_ +#define SERVER_SCRIPTING_H_ + +#include "cpp_api/s_base.h" +#include "cpp_api/s_entity.h" +#include "cpp_api/s_env.h" +#include "cpp_api/s_inventory.h" +#include "cpp_api/s_node.h" +#include "cpp_api/s_player.h" +#include "cpp_api/s_server.h" +#include "cpp_api/s_security.h" + +/*****************************************************************************/ +/* Scripting <-> Server Game Interface */ +/*****************************************************************************/ + +class ServerScripting: + virtual public ScriptApiBase, + public ScriptApiDetached, + public ScriptApiEntity, + public ScriptApiEnv, + public ScriptApiNode, + public ScriptApiPlayer, + public ScriptApiServer, + public ScriptApiSecurity +{ +public: + ServerScripting(Server* server); + + // use ScriptApiBase::loadMod() to load mods + +private: + void InitializeModApi(lua_State *L, int top); +}; + +void log_deprecated(const std::string &message); + +#endif /* SCRIPTING_GAME_H_ */ diff --git a/src/server.cpp b/src/server.cpp index 8b9f46f85..3adbf40cc 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -38,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "profiler.h" #include "log.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "nodedef.h" #include "itemdef.h" #include "craftdef.h" @@ -269,7 +269,7 @@ Server::Server( // Initialize scripting infostream<<"Server: Initializing Lua"< &modlist) modlist.push_back(it->name); } -std::string Server::getBuiltinLuaPath() +const std::string Server::getBuiltinLuaPath() { return porting::path_share + DIR_DELIM + "builtin"; } diff --git a/src/server.h b/src/server.h index 3eee67b78..417d31bd8 100644 --- a/src/server.h +++ b/src/server.h @@ -53,7 +53,7 @@ class PlayerSAO; class IRollbackManager; struct RollbackAction; class EmergeManager; -class GameScripting; +class ServerScripting; class ServerEnvironment; struct SimpleSoundSpec; class ServerThread; @@ -274,7 +274,7 @@ public: Inventory* createDetachedInventory(const std::string &name, const std::string &player=""); // Envlock and conlock should be locked when using scriptapi - GameScripting *getScriptIface(){ return m_script; } + ServerScripting *getScriptIface(){ return m_script; } // actions: time-reversed list // Return value: success/failure @@ -295,8 +295,8 @@ public: IWritableNodeDefManager* getWritableNodeDefManager(); IWritableCraftDefManager* getWritableCraftDefManager(); - const std::vector &getMods() const { return m_mods; } - const ModSpec* getModSpec(const std::string &modname) const; + virtual const std::vector &getMods() const { return m_mods; } + virtual const ModSpec* getModSpec(const std::string &modname) const; void getModNames(std::vector &modlist); std::string getBuiltinLuaPath(); inline const std::string &getWorldPath() const { return m_path_world; } @@ -540,7 +540,7 @@ private: // Scripting // Envlock and conlock should be locked when using Lua - GameScripting *m_script; + ServerScripting *m_script; // Item definition manager IWritableItemDefManager *m_itemdef; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index f3f489092..ecc7c3150 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "profiler.h" #include "raycast.h" #include "remoteplayer.h" -#include "scripting_game.h" +#include "serverscripting.h" #include "server.h" #include "voxelalgorithms.h" #include "util/serialize.h" @@ -352,7 +352,7 @@ void ActiveBlockList::update(std::vector &active_positions, */ ServerEnvironment::ServerEnvironment(ServerMap *map, - GameScripting *scriptIface, Server *server, + ServerScripting *scriptIface, Server *server, const std::string &path_world) : m_map(map), m_script(scriptIface), diff --git a/src/serverenvironment.h b/src/serverenvironment.h index b7056c00c..b7796b5f1 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -33,7 +33,7 @@ class ServerEnvironment; class ActiveBlockModifier; class ServerActiveObject; class Server; -class GameScripting; +class ServerScripting; /* {Active, Loading} block modifier interface. @@ -194,7 +194,7 @@ typedef UNORDERED_MAP ActiveObjectMap; class ServerEnvironment : public Environment { public: - ServerEnvironment(ServerMap *map, GameScripting *scriptIface, + ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, Server *server, const std::string &path_world); ~ServerEnvironment(); @@ -203,7 +203,7 @@ public: ServerMap & getServerMap(); //TODO find way to remove this fct! - GameScripting* getScriptIface() + ServerScripting* getScriptIface() { return m_script; } Server *getGameDef() @@ -381,7 +381,7 @@ private: // The map ServerMap *m_map; // Lua state - GameScripting* m_script; + ServerScripting* m_script; // Server definition Server *m_server; // World path diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 41ccf0d2d..9beb0afa6 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -19,10 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "test.h" -#include "log.h" #include "nodedef.h" #include "itemdef.h" #include "gamedef.h" +#include "mods.h" content_t t_CONTENT_STONE; content_t t_CONTENT_GRASS; @@ -59,6 +59,13 @@ public: void defineSomeNodes(); + virtual const std::vector &getMods() const + { + static std::vector testmodspec; + return testmodspec; + } + virtual const ModSpec* getModSpec(const std::string &modname) const { return NULL; } + private: IItemDefManager *m_itemdef; INodeDefManager *m_nodedef; -- cgit v1.2.3 From 9978f5af828550d819890fed1fc56d65838a2c4c Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 22 Jan 2017 00:20:55 +0100 Subject: [CSM] Add on_death, on_hp_modification & oh_damage_taken callbacks (#5093) * Add on_death callback * Add on_hp_modification & on_damage_taken callbacks * move preview code to preview.lua --- builtin/client/init.lua | 17 +++-------------- builtin/client/preview.lua | 24 ++++++++++++++++++++++++ builtin/client/register.lua | 3 +++ src/client.h | 2 ++ src/game.cpp | 7 +++---- src/network/clientpackethandler.cpp | 2 ++ src/script/cpp_api/s_client.cpp | 35 +++++++++++++++++++++++++++++++++++ src/script/cpp_api/s_client.h | 4 ++++ 8 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 builtin/client/preview.lua (limited to 'src/script/cpp_api') diff --git a/builtin/client/init.lua b/builtin/client/init.lua index d14301ade..e06dfc995 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -3,20 +3,9 @@ local scriptpath = core.get_builtin_path()..DIR_DELIM local clientpath = scriptpath.."client"..DIR_DELIM dofile(clientpath .. "register.lua") +dofile(clientpath .. "preview.lua") --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_shutdown(function() - print("shutdown client") +core.register_on_death(function() + core.display_chat_message("You died.") end) --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_receiving_chat_messages(function(message) - print("Received message " .. message) - return false -end) - --- This is an example function to ensure it's working properly, should be removed before merge -core.register_on_sending_chat_messages(function(message) - print("Sending message " .. message) - return false -end) diff --git a/builtin/client/preview.lua b/builtin/client/preview.lua new file mode 100644 index 000000000..4b277b0c6 --- /dev/null +++ b/builtin/client/preview.lua @@ -0,0 +1,24 @@ +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_shutdown(function() + print("shutdown client") +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_receiving_chat_messages(function(message) + print("[PREVIEW] Received message " .. message) + return false +end) + +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_on_sending_chat_messages(function(message) + print("[PREVIEW] Sending message " .. message) + return false +end) + +core.register_on_hp_modification(function(hp) + print("[PREVIEW] HP modified " .. hp) +end) + +core.register_on_damage_taken(function(hp) + print("[PREVIEW] Damage taken " .. hp) +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index c793195a1..ddaf4f424 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -58,5 +58,8 @@ end core.registered_on_shutdown, core.register_on_shutdown = make_registration() core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration() core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration() +core.registered_on_death, core.register_on_death = make_registration() +core.registered_on_hp_modification, core.register_on_hp_modification = make_registration() +core.registered_on_damage_taken, core.register_on_damage_taken = make_registration() diff --git a/src/client.h b/src/client.h index 584b13a90..21aeb0575 100644 --- a/src/client.h +++ b/src/client.h @@ -562,6 +562,8 @@ public: m_chat_queue.push(input); } + ClientScripting *getScript() { return m_script; } + private: // Virtual methods from con::PeerHandler diff --git a/src/game.cpp b/src/game.cpp index 9868142f7..2e2a8e0c1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiKeyChangeMenu.h" #include "guiPasswordChange.h" #include "guiVolumeChange.h" -#include "hud.h" #include "mainmenumanager.h" #include "mapblock.h" #include "nodedef.h" // Needed for determining pointing to nodes @@ -61,6 +60,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "minimap.h" #include "mapblock_mesh.h" +#include "script/clientscripting.h" #include "sound.h" @@ -3240,8 +3240,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) if (event.type == CE_PLAYER_DAMAGE && client->getHP() != 0) { - //u16 damage = event.player_damage.amount; - //infostream<<"Player damage: "<getScript()->on_damage_taken(event.player_damage.amount); *damage_flash += 95.0 + 3.2 * event.player_damage.amount; *damage_flash = MYMIN(*damage_flash, 127.0); @@ -3259,7 +3258,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) show_deathscreen(¤t_formspec, client, texture_src, device, &input->joystick); - chat_backend->addMessage(L"", L"You died."); + client->getScript()->on_death(); /* Handle visualization */ *damage_flash = 0; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index d0642c86a..97fa93e95 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -526,6 +526,8 @@ void Client::handleCommand_HP(NetworkPacket* pkt) player->hp = hp; + m_script->on_hp_modification(hp); + if (hp < oldhp) { // Add to ClientEvent queue ClientEvent event; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 08af8ebdc..f0676f4c2 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -59,3 +59,38 @@ bool ScriptApiClient::on_receiving_message(const std::string &message) bool ate = lua_toboolean(L, -1); return ate; } + +void ScriptApiClient::on_damage_taken(int32_t damage_amount) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_chat_messages + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_damage_taken"); + // Call callbacks + lua_pushinteger(L, damage_amount); + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); +} + +void ScriptApiClient::on_hp_modification(int32_t newhp) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_chat_messages + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_hp_modification"); + // Call callbacks + lua_pushinteger(L, newhp); + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); +} + +void ScriptApiClient::on_death() +{ + SCRIPTAPI_PRECHECKHEADER + + // Get registered shutdown hooks + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_death"); + // Call callbacks + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); +} diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 08fdd8fc0..3155c7e67 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -32,5 +32,9 @@ public: // Chat message handlers bool on_sending_message(const std::string &message); bool on_receiving_message(const std::string &message); + + void on_damage_taken(int32_t damage_amount); + void on_hp_modification(int32_t newhp); + void on_death(); }; #endif -- cgit v1.2.3 From 2c19d51409ca903021e0b508e5bc15299c4e51dc Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 22 Jan 2017 11:17:41 +0100 Subject: [CSM] sound_play & sound_stop support + client_lua_api doc (#5096) * squashed: CSM: Implement register_globalstep * Re-use fatal error mechanism from server to disconnect client on CSM error * Little client functions cleanups * squashed: CSM: add core.after function * core.after is shared code between client & server * ModApiUtil get_us_time feature enabled for client --- builtin/client/init.lua | 3 +- builtin/client/preview.lua | 15 +- builtin/client/register.lua | 1 + builtin/common/after.lua | 43 +++ builtin/game/init.lua | 1 + builtin/game/misc.lua | 44 --- doc/client_lua_api.txt | 787 ++++++++++++++++++++++++++++++++++++++ src/client.cpp | 1 + src/client.h | 16 +- src/clientenvironment.cpp | 4 + src/clientenvironment.h | 3 + src/guiEngine.h | 1 + src/script/clientscripting.cpp | 2 + src/script/cpp_api/s_client.cpp | 18 + src/script/cpp_api/s_client.h | 2 + src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_client.h | 3 +- src/script/lua_api/l_mainmenu.cpp | 31 -- src/script/lua_api/l_mainmenu.h | 7 +- src/script/lua_api/l_sound.cpp | 62 +++ src/script/lua_api/l_sound.h | 36 ++ src/script/lua_api/l_util.cpp | 2 + src/script/scripting_mainmenu.cpp | 4 +- 23 files changed, 996 insertions(+), 91 deletions(-) create mode 100644 builtin/common/after.lua create mode 100644 doc/client_lua_api.txt create mode 100644 src/script/lua_api/l_sound.cpp create mode 100644 src/script/lua_api/l_sound.h (limited to 'src/script/cpp_api') diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 4797ac4b6..dd218aab6 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -4,9 +4,10 @@ local clientpath = scriptpath.."client"..DIR_DELIM local commonpath = scriptpath.."common"..DIR_DELIM dofile(clientpath .. "register.lua") +dofile(commonpath .. "after.lua") +dofile(commonpath .. "chatcommands.lua") dofile(clientpath .. "preview.lua") core.register_on_death(function() core.display_chat_message("You died.") end) - diff --git a/builtin/client/preview.lua b/builtin/client/preview.lua index c421791f5..22e8bb97f 100644 --- a/builtin/client/preview.lua +++ b/builtin/client/preview.lua @@ -1,6 +1,6 @@ -- This is an example function to ensure it's working properly, should be removed before merge core.register_on_shutdown(function() - print("shutdown client") + print("[PREVIEW] shutdown client") end) -- This is an example function to ensure it's working properly, should be removed before merge @@ -15,17 +15,28 @@ core.register_on_sending_chat_messages(function(message) return false end) +-- This is an example function to ensure it's working properly, should be removed before merge core.register_on_hp_modification(function(hp) print("[PREVIEW] HP modified " .. hp) end) +-- This is an example function to ensure it's working properly, should be removed before merge core.register_on_damage_taken(function(hp) print("[PREVIEW] Damage taken " .. hp) end) +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_globalstep(function(dtime) + -- print("[PREVIEW] globalstep " .. dtime) +end) + -- This is an example function to ensure it's working properly, should be removed before merge core.register_chatcommand("dump", { func = function(name, param) return true, dump(_G) end, -}) \ No newline at end of file +}) + +core.after(2, function() + print("After 2") +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index ddaf4f424..8b60c1222 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -55,6 +55,7 @@ local function make_registration() return t, registerfunc end +core.registered_globalsteps, core.register_globalstep = make_registration() core.registered_on_shutdown, core.register_on_shutdown = make_registration() core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration() core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration() diff --git a/builtin/common/after.lua b/builtin/common/after.lua new file mode 100644 index 000000000..30a9c7bad --- /dev/null +++ b/builtin/common/after.lua @@ -0,0 +1,43 @@ +local jobs = {} +local time = 0.0 +local last = core.get_us_time() / 1000000 + +core.register_globalstep(function(dtime) + local new = core.get_us_time() / 1000000 + if new > last then + time = time + (new - last) + else + -- Overflow, we may lose a little bit of time here but + -- only 1 tick max, potentially running timers slightly + -- too early. + time = time + new + end + last = new + + if #jobs < 1 then + return + end + + -- Iterate backwards so that we miss any new timers added by + -- a timer callback, and so that we don't skip the next timer + -- in the list if we remove one. + for i = #jobs, 1, -1 do + local job = jobs[i] + if time >= job.expire then + core.set_last_run_mod(job.mod_origin) + job.func(unpack(job.arg)) + table.remove(jobs, i) + end + end +end) + +function core.after(after, func, ...) + assert(tonumber(after) and type(func) == "function", + "Invalid core.after invocation") + jobs[#jobs + 1] = { + func = func, + expire = time + after, + arg = {...}, + mod_origin = core.get_last_run_mod() + } +end diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 793d9fe2b..3e192a30a 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -17,6 +17,7 @@ if core.setting_getbool("profiler.load") then profiler = dofile(scriptpath.."profiler"..DIR_DELIM.."init.lua") end +dofile(commonpath .. "after.lua") dofile(gamepath.."item_entity.lua") dofile(gamepath.."deprecated.lua") dofile(gamepath.."misc.lua") diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 3419c1980..25376c180 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -4,50 +4,6 @@ -- Misc. API functions -- -local jobs = {} -local time = 0.0 -local last = core.get_us_time() / 1000000 - -core.register_globalstep(function(dtime) - local new = core.get_us_time() / 1000000 - if new > last then - time = time + (new - last) - else - -- Overflow, we may lose a little bit of time here but - -- only 1 tick max, potentially running timers slightly - -- too early. - time = time + new - end - last = new - - if #jobs < 1 then - return - end - - -- Iterate backwards so that we miss any new timers added by - -- a timer callback, and so that we don't skip the next timer - -- in the list if we remove one. - for i = #jobs, 1, -1 do - local job = jobs[i] - if time >= job.expire then - core.set_last_run_mod(job.mod_origin) - job.func(unpack(job.arg)) - table.remove(jobs, i) - end - end -end) - -function core.after(after, func, ...) - assert(tonumber(after) and type(func) == "function", - "Invalid core.after invocation") - jobs[#jobs + 1] = { - func = func, - expire = time + after, - arg = {...}, - mod_origin = core.get_last_run_mod() - } -end - function core.check_player_privs(name, ...) local arg_type = type(name) if (arg_type == "userdata" or arg_type == "table") and diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt new file mode 100644 index 000000000..8ce487bf8 --- /dev/null +++ b/doc/client_lua_api.txt @@ -0,0 +1,787 @@ +Minetest Lua Modding API Reference 0.4.15 +========================================= +* More information at +* Developer Wiki: + +Introduction +------------ +Content and functionality can be added to Minetest 0.4 by using Lua +scripting in run-time loaded mods. + +A mod is a self-contained bunch of scripts, textures and other related +things that is loaded by and interfaces with Minetest. + +Mods are contained and ran solely on the server side. Definitions and media +files are automatically transferred to the client. + +If you see a deficiency in the API, feel free to attempt to add the +functionality in the engine and API. You can send such improvements as +source code patches on GitHub (https://github.com/minetest/minetest). + +Programming in Lua +------------------ +If you have any difficulty in understanding this, please read +[Programming in Lua](http://www.lua.org/pil/). + +Startup +------- +Mods are loaded during client startup from the mod load paths by running +the `init.lua` scripts in a shared environment. + +Paths +----- +* `RUN_IN_PLACE=1` (Windows release, local build) + * `$path_user`: + * Linux: `` + * Windows: `` + * `$path_share` + * Linux: `` + * Windows: `` +* `RUN_IN_PLACE=0`: (Linux release) + * `$path_share` + * Linux: `/usr/share/minetest` + * Windows: `/minetest-0.4.x` + * `$path_user`: + * Linux: `$HOME/.minetest` + * Windows: `C:/users//AppData/minetest` (maybe) + +Mod load path +------------- +Generic: + +* `$path_share/clientmods/` +* `$path_user/clientmods/` (User-installed mods) + +In a run-in-place version (e.g. the distributed windows version): + +* `minetest-0.4.x/clientmods/` (User-installed mods) + +On an installed version on Linux: + +* `/usr/share/minetest/clientmods/` +* `$HOME/.minetest/clientmods/` (User-installed mods) + +Modpack support +---------------- +Mods can be put in a subdirectory, if the parent directory, which otherwise +should be a mod, contains a file named `modpack.txt`. This file shall be +empty, except for lines starting with `#`, which are comments. + +Mod directory structure +------------------------ + + mods + |-- modname + | |-- depends.txt + | |-- screenshot.png + | |-- description.txt + | |-- settingtypes.txt + | |-- init.lua + | |-- models + | |-- textures + | | |-- modname_stuff.png + | | `-- modname_something_else.png + | |-- sounds + | |-- media + | `-- + `-- another + + +### modname +The location of this directory can be fetched by using +`minetest.get_modpath(modname)`. + +### `depends.txt` +List of mods that have to be loaded before loading this mod. + +A single line contains a single modname. + +Optional dependencies can be defined by appending a question mark +to a single modname. Their meaning is that if the specified mod +is missing, that does not prevent this mod from being loaded. + +### `screenshot.png` +A screenshot shown in the mod manager within the main menu. It should +have an aspect ratio of 3:2 and a minimum size of 300×200 pixels. + +### `description.txt` +A File containing description to be shown within mainmenu. + +### `settingtypes.txt` +A file in the same format as the one in builtin. It will be parsed by the +settings menu and the settings will be displayed in the "Mods" category. + +### `init.lua` +The main Lua script. Running this script should register everything it +wants to register. Subsequent execution depends on minetest calling the +registered callbacks. + +`minetest.setting_get(name)` and `minetest.setting_getbool(name)` can be used +to read custom or existing settings at load time, if necessary. + +### `sounds` +Media files (sounds) that will be transferred to the +client and will be available for use by the mod. + +Naming convention for registered textual names +---------------------------------------------- +Registered names should generally be in this format: + + "modname:" ( can have characters a-zA-Z0-9_) + +This is to prevent conflicting names from corrupting maps and is +enforced by the mod loader. + +### Example +In the mod `experimental`, there is the ideal item/node/entity name `tnt`. +So the name should be `experimental:tnt`. + +Enforcement can be overridden by prefixing the name with `:`. This can +be used for overriding the registrations of some other mod. + +Example: Any mod can redefine `experimental:tnt` by using the name + + :experimental:tnt + +when registering it. +(also that mod is required to have `experimental` as a dependency) + +The `:` prefix can also be used for maintaining backwards compatibility. + +### Aliases +Aliases can be added by using `minetest.register_alias(name, convert_to)` or +`minetest.register_alias_force(name, convert_to). + +This will make Minetest to convert things called name to things called +`convert_to`. + +The only difference between `minetest.register_alias` and +`minetest.register_alias_force` is that if an item called `name` exists, +`minetest.register_alias` will do nothing while +`minetest.register_alias_force` will unregister it. + +This can be used for maintaining backwards compatibility. + +This can be also used for setting quick access names for things, e.g. if +you have an item called `epiclylongmodname:stuff`, you could do + + minetest.register_alias("stuff", "epiclylongmodname:stuff") + +and be able to use `/giveme stuff`. + +Sounds +------ +Only Ogg Vorbis files are supported. + +For positional playing of sounds, only single-channel (mono) files are +supported. Otherwise OpenAL will play them non-positionally. + +Mods should generally prefix their sounds with `modname_`, e.g. given +the mod name "`foomod`", a sound could be called: + + foomod_foosound.ogg + +Sounds are referred to by their name with a dot, a single digit and the +file extension stripped out. When a sound is played, the actual sound file +is chosen randomly from the matching sounds. + +When playing the sound `foomod_foosound`, the sound is chosen randomly +from the available ones of the following files: + +* `foomod_foosound.ogg` +* `foomod_foosound.0.ogg` +* `foomod_foosound.1.ogg` +* (...) +* `foomod_foosound.9.ogg` + +Examples of sound parameter tables: + + -- Play locationless on all clients + { + gain = 1.0, -- default + } + -- Play locationless to one player + { + to_player = name, + gain = 1.0, -- default + } + -- Play locationless to one player, looped + { + to_player = name, + gain = 1.0, -- default + loop = true, + } + -- Play in a location + { + pos = {x = 1, y = 2, z = 3}, + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + } + -- Play connected to an object, looped + { + object = , + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + loop = true, + } + +Looped sounds must either be connected to an object or played locationless to +one player using `to_player = name,` + +### `SimpleSoundSpec` +* e.g. `""` +* e.g. `"default_place_node"` +* e.g. `{}` +* e.g. `{name = "default_place_node"}` +* e.g. `{name = "default_place_node", gain = 1.0}` + +Representations of simple things +-------------------------------- + +### Position/vector + + {x=num, y=num, z=num} + +For helper functions see "Vector helpers". + +### `pointed_thing` +* `{type="nothing"}` +* `{type="node", under=pos, above=pos}` +* `{type="object", ref=ObjectRef}` + +Flag Specifier Format +--------------------- +Flags using the standardized flag specifier format can be specified in either of +two ways, by string or table. + +The string format is a comma-delimited set of flag names; whitespace and +unrecognized flag fields are ignored. Specifying a flag in the string sets the +flag, and specifying a flag prefixed by the string `"no"` explicitly +clears the flag from whatever the default may be. + +In addition to the standard string flag format, the schematic flags field can +also be a table of flag names to boolean values representing whether or not the +flag is set. Additionally, if a field with the flag name prefixed with `"no"` +is present, mapped to a boolean of any value, the specified flag is unset. + +E.g. A flag field of value + + {place_center_x = true, place_center_y=false, place_center_z=true} + +is equivalent to + + {place_center_x = true, noplace_center_y=true, place_center_z=true} + +which is equivalent to + + "place_center_x, noplace_center_y, place_center_z" + +or even + + "place_center_x, place_center_z" + +since, by default, no schematic attributes are set. + +Formspec +-------- +Formspec defines a menu. Currently not much else than inventories are +supported. It is a string, with a somewhat strange format. + +Spaces and newlines can be inserted between the blocks, as is used in the +examples. + +### Examples + +#### Chest + + size[8,9] + list[context;main;0,0;8,4;] + list[current_player;main;0,5;8,4;] + +#### Furnace + + size[8,9] + list[context;fuel;2,3;1,1;] + list[context;src;2,1;1,1;] + list[context;dst;5,1;2,2;] + list[current_player;main;0,5;8,4;] + +#### Minecraft-like player inventory + + size[8,7.5] + image[1,0.6;1,2;player.png] + list[current_player;main;0,3.5;8,4;] + list[current_player;craft;3,0;3,3;] + list[current_player;craftpreview;7,1;1,1;] + +### Elements + +#### `size[,,]` +* Define the size of the menu in inventory slots +* `fixed_size`: `true`/`false` (optional) +* deprecated: `invsize[,;]` + +#### `container[,]` +* Start of a container block, moves all physical elements in the container by (X, Y) +* Must have matching container_end +* Containers can be nested, in which case the offsets are added + (child containers are relative to parent containers) + +#### `container_end[]` +* End of a container, following elements are no longer relative to this container + +#### `list[;;,;,;]` +* Show an inventory list + +#### `list[;;,;,;]` +* Show an inventory list + +#### `listring[;]` +* Allows to create a ring of inventory lists +* Shift-clicking on items in one element of the ring + will send them to the next inventory list inside the ring +* The first occurrence of an element inside the ring will + determine the inventory where items will be sent to + +#### `listring[]` +* Shorthand for doing `listring[;]` + for the last two inventory lists added by list[...] + +#### `listcolors[;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering + +#### `listcolors[;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border + +#### `listcolors[;;;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border +* Sets default background color of tooltips +* Sets default font color of tooltips + +#### `tooltip[;;,]` +* Adds tooltip for an element +* `` tooltip background color as `ColorString` (optional) +* `` tooltip font color as `ColorString` (optional) + +#### `image[,;,;]` +* Show an image +* Position and size units are inventory slots + +#### `item_image[,;,;]` +* Show an inventory image of registered item/node +* Position and size units are inventory slots + +#### `bgcolor[;]` +* Sets background color of formspec as `ColorString` +* If `true`, the background color is drawn fullscreen (does not effect the size of the formspec) + +#### `background[,;,;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: image shall be sized + 8 times 16px times 4 times 16px. + +#### `background[,;,;;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: + image shall be sized 8 times 16px times 4 times 16px +* If `true` the background is clipped to formspec size + (`x` and `y` are used as offset values, `w` and `h` are ignored) + +#### `pwdfield[,;,;;