From b16252dcae8c6b0e79c20fa4c3cbddc37ad377cb Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 22 Dec 2016 19:29:15 +0100 Subject: Various anticheat improvements * Calculate maximum interact distance from wielded tool * New "interacted_while_dead" cheat_type for the Lua API * Disallow dropping items while dead * Move player to spawn before resurrecting them --- doc/lua_api.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 34c64b8df..d2ddc635b 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2036,9 +2036,10 @@ Call these functions only at load time! * `minetest.register_on_cheat(func(ObjectRef, cheat))` * Called when a player cheats * `cheat`: `{type=}`, where `` is one of: - * `"moved_too_fast"` - * `"interacted_too_far"` - * `"finished_unknown_dig"` + * `moved_too_fast` + * `interacted_too_far` + * `interacted_while_dead` + * `finished_unknown_dig` * `dug_unbreakable` * `dug_too_fast` * `minetest.register_on_chat_message(func(name, message))` -- cgit v1.2.3 From 7057c196c442ff3484b53f48d940f4c9e0ffe23a Mon Sep 17 00:00:00 2001 From: Luke Puchner-Hardman Date: Tue, 23 Sep 2014 14:39:34 +0200 Subject: Added "[sheet" to the texture special commands. "[sheet:WxH:X,Y" assumes the base image is a tilesheet with W*H tiles on it and crops to the tile at position X,Y. Basically it works like "[verticalframe" but in 2D. For testing, I combined the four default_chest images into one. --- doc/lua_api.txt | 5 +++ games/minimal/mods/default/init.lua | 10 +++-- .../mods/default/textures/default_chest.png | Bin 0 -> 263 bytes .../mods/default/textures/default_chest_front.png | Bin 114 -> 0 bytes .../mods/default/textures/default_chest_lock.png | Bin 145 -> 0 bytes .../mods/default/textures/default_chest_side.png | Bin 98 -> 0 bytes .../mods/default/textures/default_chest_top.png | Bin 93 -> 0 bytes src/client/tile.cpp | 43 +++++++++++++++++++++ 8 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 games/minimal/mods/default/textures/default_chest.png delete mode 100644 games/minimal/mods/default/textures/default_chest_front.png delete mode 100644 games/minimal/mods/default/textures/default_chest_lock.png delete mode 100644 games/minimal/mods/default/textures/default_chest_side.png delete mode 100644 games/minimal/mods/default/textures/default_chest_top.png (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index d2ddc635b..648a29303 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -403,6 +403,11 @@ Apply a mask to the base image. The mask is applied using binary AND. +#### `[sheet:x:,` +Retrieves a tile at position x,y from the base image +which it assumes to be a tilesheet with dimensions w,h. + + #### `[colorize::` Colorize the textures with the given color. `` is specified as a `ColorString`. diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua index bff7860e3..f532e7193 100644 --- a/games/minimal/mods/default/init.lua +++ b/games/minimal/mods/default/init.lua @@ -1130,8 +1130,9 @@ minetest.register_node("default:sign_wall", { minetest.register_node("default:chest", { description = "Chest", - tiles ={"default_chest_top.png", "default_chest_top.png", "default_chest_side.png", - "default_chest_side.png", "default_chest_side.png", "default_chest_front.png"}, + tiles ={"default_chest.png^[sheet:2x2:0,0", "default_chest.png^[sheet:2x2:0,0", + "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:1,0", + "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:0,1"}, paramtype2 = "facedir", groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2}, legacy_facedir_simple = true, @@ -1164,8 +1165,9 @@ end minetest.register_node("default:chest_locked", { description = "Locked Chest", - tiles ={"default_chest_top.png", "default_chest_top.png", "default_chest_side.png", - "default_chest_side.png", "default_chest_side.png", "default_chest_lock.png"}, + tiles ={"default_chest.png^[sheet:2x2:0,0", "default_chest.png^[sheet:2x2:0,0", + "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:1,0", + "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:1,1"}, paramtype2 = "facedir", groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2}, legacy_facedir_simple = true, diff --git a/games/minimal/mods/default/textures/default_chest.png b/games/minimal/mods/default/textures/default_chest.png new file mode 100644 index 000000000..9746a3fd9 Binary files /dev/null and b/games/minimal/mods/default/textures/default_chest.png differ diff --git a/games/minimal/mods/default/textures/default_chest_front.png b/games/minimal/mods/default/textures/default_chest_front.png deleted file mode 100644 index 55b076c35..000000000 Binary files a/games/minimal/mods/default/textures/default_chest_front.png and /dev/null differ diff --git a/games/minimal/mods/default/textures/default_chest_lock.png b/games/minimal/mods/default/textures/default_chest_lock.png deleted file mode 100644 index 4b2d1af6c..000000000 Binary files a/games/minimal/mods/default/textures/default_chest_lock.png and /dev/null differ diff --git a/games/minimal/mods/default/textures/default_chest_side.png b/games/minimal/mods/default/textures/default_chest_side.png deleted file mode 100644 index ae4847cb6..000000000 Binary files a/games/minimal/mods/default/textures/default_chest_side.png and /dev/null differ diff --git a/games/minimal/mods/default/textures/default_chest_top.png b/games/minimal/mods/default/textures/default_chest_top.png deleted file mode 100644 index ac41551b0..000000000 Binary files a/games/minimal/mods/default/textures/default_chest_top.png and /dev/null differ diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 7f7535df6..4d2166342 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -1827,6 +1827,49 @@ bool TextureSource::generateImagePart(std::string part_of_name, baseimg->setPixel(x, y, c); } } + /* + [sheet:WxH:X,Y + Retrieves a tile at position X,Y (in tiles) + from the base image it assumes to be a + tilesheet with dimensions W,H (in tiles). + */ + else if (part_of_name.substr(0,7) == "[sheet:") { + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg != NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + Strfnd sf(part_of_name); + sf.next(":"); + u32 w0 = stoi(sf.next("x")); + u32 h0 = stoi(sf.next(":")); + u32 x0 = stoi(sf.next(",")); + u32 y0 = stoi(sf.next(":")); + + core::dimension2d img_dim = baseimg->getDimension(); + core::dimension2d tile_dim(v2u32(img_dim) / v2u32(w0, h0)); + + video::IImage *img = driver->createImage( + video::ECF_A8R8G8B8, tile_dim); + if (!img) { + errorstream << "generateImagePart(): Could not create image " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + img->fill(video::SColor(0,0,0,0)); + v2u32 vdim(tile_dim); + core::rect rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim); + baseimg->copyToWithAlpha(img, v2s32(0), rect, + video::SColor(255,255,255,255), NULL); + + // Replace baseimg + baseimg->drop(); + baseimg = img; + } else { errorstream << "generateImagePart(): Invalid " -- cgit v1.2.3 From a07b032245bef76a7695e139a9daca7cb646a73d Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 23 Dec 2016 14:43:56 +0100 Subject: Add 2D sheet animation for nodes --- doc/lua_api.txt | 21 ++++++++- games/minimal/mods/default/init.lua | 7 ++- .../textures/default_lava_source_animated.png | Bin 2902 -> 3145 bytes src/nodedef.cpp | 2 +- src/particles.cpp | 2 +- src/script/common/c_content.cpp | 25 +++++++--- src/tileanimation.cpp | 52 +++++++++++++++------ src/tileanimation.h | 6 +++ 8 files changed, 88 insertions(+), 27 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 648a29303..6166826af 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3702,7 +3702,26 @@ Definition tables * `image` (name) ### Tile animation definition -* `{type="vertical_frames", aspect_w=16, aspect_h=16, length=3.0}` + + { + type = "vertical_frames", + aspect_w = 16, + -- ^ specify width of a frame in pixels + aspect_h = 16, + -- ^ specify height of a frame in pixels + length = 3.0, + -- ^ specify full loop length + } + + { + type = "sheet_2d", + frames_w = 5, + -- ^ specify width in number of frames + frames_h = 3, + -- ^ specify height in number of frames + frame_length = 0.5, + -- ^ specify length of a single frame + } ### Node definition (`register_node`) diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua index f532e7193..2f73b53ef 100644 --- a/games/minimal/mods/default/init.lua +++ b/games/minimal/mods/default/init.lua @@ -1044,8 +1044,11 @@ minetest.register_node("default:lava_source", { inventory_image = minetest.inventorycube("default_lava.png"), drawtype = "liquid", --tiles ={"default_lava.png"}, - tiles ={ - {name="default_lava_source_animated.png", animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.0}} + tiles = { + { + name = "default_lava_source_animated.png", + animation = {type="sheet_2d", frames_w=3, frames_h=2, frame_length=0.5} + } }, special_tiles = { -- New-style lava source material (mostly unused) diff --git a/games/minimal/mods/default/textures/default_lava_source_animated.png b/games/minimal/mods/default/textures/default_lava_source_animated.png index aa9d57cf1..54f4c0ddd 100644 Binary files a/games/minimal/mods/default/textures/default_lava_source_animated.png and b/games/minimal/mods/default/textures/default_lava_source_animated.png differ diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 21bceb94d..92cdf738e 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -528,7 +528,7 @@ void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, tile->material_flags = 0; if (backface_culling) tile->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; - if (tiledef->animation.type == TAT_VERTICAL_FRAMES) + if (tiledef->animation.type != TAT_NONE) tile->material_flags |= MATERIAL_FLAG_ANIMATION; if (tiledef->tileable_horizontal) tile->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL; diff --git a/src/particles.cpp b/src/particles.cpp index e9ddba986..97f42e2c4 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -570,7 +570,7 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s video::ITexture *texture; // Only use first frame of animated texture - if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION) + if (tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION) texture = tiles[texid].frames[0].texture; else texture = tiles[texid].texture; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 6cd1d040b..b9bcfef69 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -39,6 +39,7 @@ struct EnumString es_TileAnimationType[] = { {TAT_NONE, "none"}, {TAT_VERTICAL_FRAMES, "vertical_frames"}, + {TAT_SHEET_2D, "sheet_2d"}, {0, NULL}, }; @@ -334,16 +335,26 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) // animation = {} lua_getfield(L, index, "animation"); if(lua_istable(L, -1)){ - // {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0} tiledef.animation.type = (TileAnimationType) getenumfield(L, -1, "type", es_TileAnimationType, TAT_NONE); - tiledef.animation.vertical_frames.aspect_w = - getintfield_default(L, -1, "aspect_w", 16); - tiledef.animation.vertical_frames.aspect_h = - getintfield_default(L, -1, "aspect_h", 16); - tiledef.animation.vertical_frames.length = - getfloatfield_default(L, -1, "length", 1.0); + if (tiledef.animation.type == TAT_VERTICAL_FRAMES) { + // {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0} + tiledef.animation.vertical_frames.aspect_w = + getintfield_default(L, -1, "aspect_w", 16); + tiledef.animation.vertical_frames.aspect_h = + getintfield_default(L, -1, "aspect_h", 16); + tiledef.animation.vertical_frames.length = + getfloatfield_default(L, -1, "length", 1.0); + } else if (tiledef.animation.type == TAT_SHEET_2D) { + // {type="sheet_2d", frames_w=5, frames_h=3, frame_length=0.5} + getintfield(L, -1, "frames_w", + tiledef.animation.sheet_2d.frames_w); + getintfield(L, -1, "frames_h", + tiledef.animation.sheet_2d.frames_h); + getfloatfield(L, -1, "frame_length", + tiledef.animation.sheet_2d.frame_length); + } } lua_pop(L, 1); } diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index 891478c9f..a23eecc2e 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., void TileAnimationParams::serialize(std::ostream &os, u16 protocol_version) const { - if(protocol_version < 29 /* TODO bump */) { + if (protocol_version < 29) { if (type == TAT_VERTICAL_FRAMES) { writeU8(os, type); writeU16(os, vertical_frames.aspect_w); @@ -41,47 +41,69 @@ void TileAnimationParams::serialize(std::ostream &os, u16 protocol_version) cons writeU16(os, vertical_frames.aspect_w); writeU16(os, vertical_frames.aspect_h); writeF1000(os, vertical_frames.length); + } else if (type == TAT_SHEET_2D) { + writeU8(os, sheet_2d.frames_w); + writeU8(os, sheet_2d.frames_h); + writeF1000(os, sheet_2d.frame_length); } } void TileAnimationParams::deSerialize(std::istream &is, u16 protocol_version) { type = (TileAnimationType) readU8(is); - if(protocol_version < 29 /* TODO bump */) { + if (protocol_version < 29) { vertical_frames.aspect_w = readU16(is); vertical_frames.aspect_h = readU16(is); vertical_frames.length = readF1000(is); return; } - if(type == TAT_VERTICAL_FRAMES) { + if (type == TAT_VERTICAL_FRAMES) { vertical_frames.aspect_w = readU16(is); vertical_frames.aspect_h = readU16(is); vertical_frames.length = readF1000(is); + } else if (type == TAT_SHEET_2D) { + sheet_2d.frames_w = readU8(is); + sheet_2d.frames_h = readU8(is); + sheet_2d.frame_length = readF1000(is); } } void TileAnimationParams::determineParams(v2u32 texture_size, int *frame_count, int *frame_length_ms) const { - if (type == TAT_NONE) { + if (type == TAT_VERTICAL_FRAMES) { + int frame_height = (float)texture_size.X / + (float)vertical_frames.aspect_w * + (float)vertical_frames.aspect_h; + int _frame_count = texture_size.Y / frame_height; + if (frame_count) + *frame_count = _frame_count; + if (frame_length_ms) + *frame_length_ms = 1000.0 * vertical_frames.length / _frame_count; + } else if (type == TAT_SHEET_2D) { + if (frame_count) + *frame_count = sheet_2d.frames_w * sheet_2d.frames_h; + if (frame_length_ms) + *frame_length_ms = 1000 * sheet_2d.frame_length; + } else { // TAT_NONE *frame_count = 1; *frame_length_ms = 1000; - return; } - int frame_height = (float)texture_size.X / - (float)vertical_frames.aspect_w * - (float)vertical_frames.aspect_h; - if (frame_count) - *frame_count = texture_size.Y / frame_height; - if (frame_length_ms) - *frame_length_ms = 1000.0 * vertical_frames.length / (texture_size.Y / frame_height); } void TileAnimationParams::getTextureModifer(std::ostream &os, v2u32 texture_size, int frame) const { if (type == TAT_NONE) return; - int frame_count; - determineParams(texture_size, &frame_count, NULL); - os << "^[verticalframe:" << frame_count << ":" << frame; + if (type == TAT_VERTICAL_FRAMES) { + int frame_count; + determineParams(texture_size, &frame_count, NULL); + os << "^[verticalframe:" << frame_count << ":" << frame; + } else if (type == TAT_SHEET_2D) { + int q, r; + q = frame / sheet_2d.frames_w; + r = frame % sheet_2d.frames_w; + os << "^[sheet:" << sheet_2d.frames_w << "x" << sheet_2d.frames_h + << ":" << r << "," << q; + } } diff --git a/src/tileanimation.h b/src/tileanimation.h index d5172ed50..289ce515b 100644 --- a/src/tileanimation.h +++ b/src/tileanimation.h @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., enum TileAnimationType { TAT_NONE = 0, TAT_VERTICAL_FRAMES = 1, + TAT_SHEET_2D = 2, }; struct TileAnimationParams { @@ -38,6 +39,11 @@ struct TileAnimationParams { int aspect_h; // height for aspect ratio float length; // seconds } vertical_frames; + struct { + int frames_w; // number of frames left-to-right + int frames_h; // number of frames top-to-bottom + float frame_length; // seconds + } sheet_2d; }; void serialize(std::ostream &os, u16 protocol_version) const; -- cgit v1.2.3 From e8b7179ccd5b1f70eba3f9ac570c5f10474cf7a7 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Wed, 28 Dec 2016 13:01:32 +0000 Subject: Expose and document chatcommands as minetest.registered_chatcommands --- builtin/game/chatcommands.lua | 19 ++++++++++--------- doc/lua_api.txt | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 71edeb26a..eb6edc1c8 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -4,14 +4,15 @@ -- Chat command handler -- -core.chatcommands = {} +core.registered_chatcommands = {} +core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY function core.register_chatcommand(cmd, def) def = def or {} def.params = def.params or "" def.description = def.description or "" def.privs = def.privs or {} def.mod_origin = core.get_current_modname() or "??" - core.chatcommands[cmd] = def + core.registered_chatcommands[cmd] = def end core.register_on_chat_message(function(name, message) @@ -19,7 +20,7 @@ core.register_on_chat_message(function(name, message) if not param then param = "" end - local cmd_def = core.chatcommands[cmd] + local cmd_def = core.registered_chatcommands[cmd] if not cmd_def then return false end @@ -107,7 +108,7 @@ core.register_chatcommand("help", { if param == "" then local msg = "" local cmds = {} - for cmd, def in pairs(core.chatcommands) do + for cmd, def in pairs(core.registered_chatcommands) do if core.check_player_privs(name, def.privs) then cmds[#cmds + 1] = cmd end @@ -118,7 +119,7 @@ core.register_chatcommand("help", { .. " or '/help all' to list everything." elseif param == "all" then local cmds = {} - for cmd, def in pairs(core.chatcommands) do + for cmd, def in pairs(core.registered_chatcommands) do if core.check_player_privs(name, def.privs) then cmds[#cmds + 1] = format_help_line(cmd, def) end @@ -134,7 +135,7 @@ core.register_chatcommand("help", { return true, "Available privileges:\n"..table.concat(privs, "\n") else local cmd = param - local def = core.chatcommands[cmd] + local def = core.registered_chatcommands[cmd] if not def then return false, "Command not available: "..cmd else @@ -161,7 +162,7 @@ local function handle_grant_command(caller, grantname, grantprivstr) if not (caller_privs.privs or caller_privs.basic_privs) then return false, "Your privileges are insufficient." end - + if not core.get_auth_handler().get_auth(grantname) then return false, "Player " .. grantname .. " does not exist." end @@ -204,7 +205,7 @@ core.register_chatcommand("grant", { local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") if not grantname or not grantprivstr then return false, "Invalid parameters (see /help grant)" - end + end return handle_grant_command(name, grantname, grantprivstr) end, }) @@ -215,7 +216,7 @@ core.register_chatcommand("grantme", { func = function(name, param) if param == "" then return false, "Invalid parameters (see /help grantme)" - end + end return handle_grant_command(name, name, param) end, }) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 6166826af..6f83039c8 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2076,6 +2076,7 @@ Call these functions only at load time! ### Other registration functions * `minetest.register_chatcommand(cmd, chatcommand definition)` + * Adds definition to minetest.registered_chatcommands * `minetest.register_privilege(name, definition)` * `definition`: `"description text"` * `definition`: `{ description = "description text", give_to_singleplayer = boolean}` -- cgit v1.2.3 From 545c37f6139125c9a2f9cc0ecfd1d3ff1f44b832 Mon Sep 17 00:00:00 2001 From: LNJ Date: Thu, 5 Jan 2017 22:38:43 +0100 Subject: lua_api.txt: Add registered_chatcommands to global tables --- doc/lua_api.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 6f83039c8..d05db9d49 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2729,6 +2729,8 @@ These functions return the leftover itemstack. * Map of object references, indexed by active object id * `minetest.luaentities` * Map of Lua entities, indexed by active object id +* `minetest.registered_chatcommands` + * Map of registered chat command definitions, indexed by name * `minetest.registered_ores` * List of registered ore definitions. * `minetest.registered_biomes` -- cgit v1.2.3 From ec30d49e026af2d0cb8329eb66aec48d12e79839 Mon Sep 17 00:00:00 2001 From: Rui Date: Tue, 10 Jan 2017 04:39:45 +0900 Subject: Add staticdata parameter to add_entity (#5009) * Add staticdata parameter to add_entity * Add add_entity_with_staticdata to core.features --- builtin/game/features.lua | 1 + doc/lua_api.txt | 2 +- src/script/lua_api/l_env.cpp | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 2aad458da..646b254ea 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -9,6 +9,7 @@ core.features = { no_legacy_abms = true, texture_names_parens = true, area_store_custom_ids = true, + add_entity_with_staticdata = true, } function core.has_feature(arg) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index d05db9d49..fc0f8e1fc 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2173,7 +2173,7 @@ and `minetest.auth_reload` call the authetification handler. * `minetest.get_node_timer(pos)` * Get `NodeTimerRef` -* `minetest.add_entity(pos, name)`: Spawn Lua-defined entity at position +* `minetest.add_entity(pos, name, [staticdata])`: Spawn Lua-defined entity at position * Returns `ObjectRef`, or `nil` if failed * `minetest.add_item(pos, item)`: Spawn item * Returns `ObjectRef`, or `nil` if failed diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 68d10308c..3d9db7917 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -440,7 +440,7 @@ int ModApiEnvMod::l_get_node_timer(lua_State *L) return 1; } -// add_entity(pos, entityname) -> ObjectRef or nil +// add_entity(pos, entityname, [staticdata]) -> ObjectRef or nil // pos = {x=num, y=num, z=num} int ModApiEnvMod::l_add_entity(lua_State *L) { @@ -450,8 +450,10 @@ int ModApiEnvMod::l_add_entity(lua_State *L) v3f pos = checkFloatPos(L, 1); // content const char *name = luaL_checkstring(L, 2); + // staticdata + const char *staticdata = luaL_optstring(L, 3, ""); // Do it - ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, ""); + ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, staticdata); int objectid = env->addActiveObject(obj); // If failed to add, return nothing (reads as nil) if(objectid == 0) -- cgit v1.2.3 From 63c892eedfe21cbca2e2369d28e5e4d4e62ca1bd Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Mon, 16 Jan 2017 13:08:59 +0000 Subject: Rename ObjectRef methods to be consistent and predictable --- doc/lua_api.txt | 22 +++++++------- src/script/lua_api/l_internal.h | 1 + src/script/lua_api/l_object.cpp | 66 ++++++++++++++++++++--------------------- src/script/lua_api/l_object.h | 44 +++++++++++++-------------- 4 files changed, 67 insertions(+), 66 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index fc0f8e1fc..da6a898d9 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2788,9 +2788,9 @@ This is basically a reference to a C++ `ServerActiveObject` #### Methods * `remove()`: remove object (after returning from Lua) * Note: Doesn't work on players, use minetest.kick_player instead -* `getpos()`: returns `{x=num, y=num, z=num}` -* `setpos(pos)`; `pos`=`{x=num, y=num, z=num}` -* `moveto(pos, continuous=false)`: interpolated move +* `get_pos()`: returns `{x=num, y=num, z=num}` +* `set_pos(pos)`; `pos`=`{x=num, y=num, z=num}` +* `move_to(pos, continuous=false)`: interpolated move * `punch(puncher, time_from_last_punch, tool_capabilities, direction)` * `puncher` = another `ObjectRef`, * `time_from_last_punch` = time since last punch action of the puncher @@ -2836,14 +2836,14 @@ This is basically a reference to a C++ `ServerActiveObject` } ##### LuaEntitySAO-only (no-op for other objects) -* `setvelocity({x=num, y=num, z=num})` -* `getvelocity()`: returns `{x=num, y=num, z=num}` -* `setacceleration({x=num, y=num, z=num})` -* `getacceleration()`: returns `{x=num, y=num, z=num}` -* `setyaw(radians)` -* `getyaw()`: returns number in radians -* `settexturemod(mod)` -* `setsprite(p={x=0,y=0}, num_frames=1, framelength=0.2, +* `set_velocity({x=num, y=num, z=num})` +* `get_velocity()`: returns `{x=num, y=num, z=num}` +* `set_acceleration({x=num, y=num, z=num})` +* `get_acceleration()`: returns `{x=num, y=num, z=num}` +* `set_yaw(radians)` +* `get_yaw()`: returns number in radians +* `set_texture_mod(mod)` +* `set_sprite(p={x=0,y=0}, num_frames=1, framelength=0.2, select_horiz_by_yawpitch=false)` * Select sprite from spritesheet with optional animation and DM-style texture selection based on yaw relative to camera diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index 456c8fcce..c610dc5a3 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_internal.h" #define luamethod(class, name) {#name, class::l_##name} +#define luamethod_aliased(class, name, alias) {#name, class::l_##name}, {#alias, class::l_##name} #define API_FCT(name) registerFunction(L, #name, l_##name,top) #define ASYNC_API_FCT(name) engine.registerFunction(#name, l_##name) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 506e56df9..dd29208c5 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -150,9 +150,9 @@ int ObjectRef::l_remove(lua_State *L) return 0; } -// getpos(self) +// get_pos(self) // returns: {x=num, y=num, z=num} -int ObjectRef::l_getpos(lua_State *L) +int ObjectRef::l_get_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -169,8 +169,8 @@ int ObjectRef::l_getpos(lua_State *L) return 1; } -// setpos(self, pos) -int ObjectRef::l_setpos(lua_State *L) +// set_pos(self, pos) +int ObjectRef::l_set_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -184,8 +184,8 @@ int ObjectRef::l_setpos(lua_State *L) return 0; } -// moveto(self, pos, continuous=false) -int ObjectRef::l_moveto(lua_State *L) +// move_to(self, pos, continuous=false) +int ObjectRef::l_move_to(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -821,8 +821,8 @@ int ObjectRef::l_get_nametag_attributes(lua_State *L) /* LuaEntitySAO-only */ -// setvelocity(self, {x=num, y=num, z=num}) -int ObjectRef::l_setvelocity(lua_State *L) +// set_velocity(self, {x=num, y=num, z=num}) +int ObjectRef::l_set_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -834,8 +834,8 @@ int ObjectRef::l_setvelocity(lua_State *L) return 0; } -// getvelocity(self) -int ObjectRef::l_getvelocity(lua_State *L) +// get_velocity(self) +int ObjectRef::l_get_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -847,8 +847,8 @@ int ObjectRef::l_getvelocity(lua_State *L) return 1; } -// setacceleration(self, {x=num, y=num, z=num}) -int ObjectRef::l_setacceleration(lua_State *L) +// set_acceleration(self, {x=num, y=num, z=num}) +int ObjectRef::l_set_acceleration(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -861,8 +861,8 @@ int ObjectRef::l_setacceleration(lua_State *L) return 0; } -// getacceleration(self) -int ObjectRef::l_getacceleration(lua_State *L) +// get_acceleration(self) +int ObjectRef::l_get_acceleration(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -874,8 +874,8 @@ int ObjectRef::l_getacceleration(lua_State *L) return 1; } -// setyaw(self, radians) -int ObjectRef::l_setyaw(lua_State *L) +// set_yaw(self, radians) +int ObjectRef::l_set_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -887,8 +887,8 @@ int ObjectRef::l_setyaw(lua_State *L) return 0; } -// getyaw(self) -int ObjectRef::l_getyaw(lua_State *L) +// get_yaw(self) +int ObjectRef::l_get_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -900,8 +900,8 @@ int ObjectRef::l_getyaw(lua_State *L) return 1; } -// settexturemod(self, mod) -int ObjectRef::l_settexturemod(lua_State *L) +// set_texture_mod(self, mod) +int ObjectRef::l_set_texture_mod(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -913,9 +913,9 @@ int ObjectRef::l_settexturemod(lua_State *L) return 0; } -// setsprite(self, p={x=0,y=0}, num_frames=1, framelength=0.2, +// set_sprite(self, p={x=0,y=0}, num_frames=1, framelength=0.2, // select_horiz_by_yawpitch=false) -int ObjectRef::l_setsprite(lua_State *L) +int ObjectRef::l_set_sprite(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); @@ -1774,9 +1774,9 @@ const char ObjectRef::className[] = "ObjectRef"; const luaL_reg ObjectRef::methods[] = { // ServerActiveObject luamethod(ObjectRef, remove), - luamethod(ObjectRef, getpos), - luamethod(ObjectRef, setpos), - luamethod(ObjectRef, moveto), + luamethod_aliased(ObjectRef, get_pos, getpos), + luamethod_aliased(ObjectRef, set_pos, setpos), + luamethod_aliased(ObjectRef, move_to, moveto), luamethod(ObjectRef, punch), luamethod(ObjectRef, right_click), luamethod(ObjectRef, set_hp), @@ -1800,14 +1800,14 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, set_nametag_attributes), luamethod(ObjectRef, get_nametag_attributes), // LuaEntitySAO-only - luamethod(ObjectRef, setvelocity), - luamethod(ObjectRef, getvelocity), - luamethod(ObjectRef, setacceleration), - luamethod(ObjectRef, getacceleration), - luamethod(ObjectRef, setyaw), - luamethod(ObjectRef, getyaw), - luamethod(ObjectRef, settexturemod), - luamethod(ObjectRef, setsprite), + luamethod_aliased(ObjectRef, set_velocity, setvelocity), + luamethod_aliased(ObjectRef, get_velocity, getvelocity), + luamethod_aliased(ObjectRef, set_acceleration, setacceleration), + luamethod_aliased(ObjectRef, get_acceleration, getacceleration), + luamethod_aliased(ObjectRef, set_yaw, setyaw), + luamethod_aliased(ObjectRef, get_yaw, getyaw), + luamethod_aliased(ObjectRef, set_texture_mod, set_texturemod), + luamethod_aliased(ObjectRef, set_sprite, setsprite), luamethod(ObjectRef, get_entity_name), luamethod(ObjectRef, get_luaentity), // Player-only diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 09f10e417..06b1bb79b 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -57,15 +57,15 @@ private: // remove(self) static int l_remove(lua_State *L); - // getpos(self) + // get_pos(self) // returns: {x=num, y=num, z=num} - static int l_getpos(lua_State *L); + static int l_get_pos(lua_State *L); - // setpos(self, pos) - static int l_setpos(lua_State *L); + // set_pos(self, pos) + static int l_set_pos(lua_State *L); - // moveto(self, pos, continuous=false) - static int l_moveto(lua_State *L); + // move_to(self, pos, continuous=false) + static int l_move_to(lua_State *L); // punch(self, puncher, time_from_last_punch, tool_capabilities, dir) static int l_punch(lua_State *L); @@ -143,30 +143,30 @@ private: /* LuaEntitySAO-only */ - // setvelocity(self, {x=num, y=num, z=num}) - static int l_setvelocity(lua_State *L); + // set_velocity(self, {x=num, y=num, z=num}) + static int l_set_velocity(lua_State *L); - // getvelocity(self) - static int l_getvelocity(lua_State *L); + // get_velocity(self) + static int l_get_velocity(lua_State *L); - // setacceleration(self, {x=num, y=num, z=num}) - static int l_setacceleration(lua_State *L); + // set_acceleration(self, {x=num, y=num, z=num}) + static int l_set_acceleration(lua_State *L); - // getacceleration(self) - static int l_getacceleration(lua_State *L); + // get_acceleration(self) + static int l_get_acceleration(lua_State *L); - // setyaw(self, radians) - static int l_setyaw(lua_State *L); + // set_yaw(self, radians) + static int l_set_yaw(lua_State *L); - // getyaw(self) - static int l_getyaw(lua_State *L); + // get_yaw(self) + static int l_get_yaw(lua_State *L); - // settexturemod(self, mod) - static int l_settexturemod(lua_State *L); + // set_texture_mod(self, mod) + static int l_set_texture_mod(lua_State *L); - // setsprite(self, p={x=0,y=0}, num_frames=1, framelength=0.2, + // set_sprite(self, p={x=0,y=0}, num_frames=1, framelength=0.2, // select_horiz_by_yawpitch=false) - static int l_setsprite(lua_State *L); + static int l_set_sprite(lua_State *L); // DEPRECATED // get_entity_name(self) -- cgit v1.2.3 From c5967f75f0a9827d1b65b384edd6ba07c73ffd2f Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Wed, 18 Jan 2017 10:19:57 +0000 Subject: Add minetest.player_exists() (#5064) --- builtin/game/misc.lua | 13 ++++++++----- doc/lua_api.txt | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 7caa9e7ba..3419c1980 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -56,11 +56,11 @@ function core.check_player_privs(name, ...) elseif arg_type ~= "string" then error("Invalid core.check_player_privs argument type: " .. arg_type, 2) end - + local requested_privs = {...} local player_privs = core.get_player_privs(name) local missing_privileges = {} - + if type(requested_privs[1]) == "table" then -- We were provided with a table like { privA = true, privB = true }. for priv, value in pairs(requested_privs[1]) do @@ -76,11 +76,11 @@ function core.check_player_privs(name, ...) end end end - + if #missing_privileges > 0 then return false, missing_privileges end - + return true, "" end @@ -114,6 +114,10 @@ function core.get_connected_players() return temp_table end +function minetest.player_exists(name) + return minetest.get_auth_handler().get_auth(name) ~= nil +end + -- Returns two position vectors representing a box of `radius` in each -- direction centered around the player corresponding to `player_name` function core.get_player_radius_area(player_name, radius) @@ -244,4 +248,3 @@ end function core.close_formspec(player_name, formname) return minetest.show_formspec(player_name, formname, "") end - diff --git a/doc/lua_api.txt b/doc/lua_api.txt index da6a898d9..c96131455 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2583,6 +2583,7 @@ These functions return the leftover itemstack. ### Misc. * `minetest.get_connected_players()`: returns list of `ObjectRefs` +* `minetest.player_exists(name)`: boolean, whether player exists (regardless of online status) * `minetest.hud_replace_builtin(name, hud_definition)` * Replaces definition of a builtin hud element * `name`: `"breath"` or `"health"` -- cgit v1.2.3 From 7279f0b37335396c85f6bdd7dc67ff56e53df0f9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 14 Jan 2017 16:48:49 +0100 Subject: Add particle animation, glow This is implemented by reusing and extending the TileAnimation code for the methods used by particles. --- doc/lua_api.txt | 7 ++- games/minimal/mods/experimental/init.lua | 37 ++++++++++++++ src/client.h | 5 ++ src/network/clientpackethandler.cpp | 18 +++++++ src/network/networkpacket.cpp | 2 +- src/network/networkpacket.h | 5 +- src/nodedef.cpp | 2 +- src/particles.cpp | 74 ++++++++++++++++++++++----- src/particles.h | 12 ++++- src/script/common/c_content.cpp | 60 +++++++++++++--------- src/script/common/c_content.h | 1 + src/script/lua_api/l_particles.cpp | 27 +++++++++- src/server.cpp | 85 +++++++++++++++++++++++--------- src/server.h | 18 ++++--- src/tileanimation.cpp | 32 ++++++++++-- src/tileanimation.h | 4 +- 16 files changed, 311 insertions(+), 78 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index c96131455..9bdc01c07 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4180,10 +4180,15 @@ The Biome API is still in an experimental phase and subject to change. -- ^ vertical: if true faces player using y axis only texture = "image.png", -- ^ Uses texture (string) - playername = "singleplayer" + playername = "singleplayer", -- ^ optional, if specified spawns particle only on the player's client + animation = {Tile Animation definition}, + -- ^ optional, specifies how to animate the particle texture + glow = 0 + -- ^ optional, specify particle self-luminescence in darkness } + ### `ParticleSpawner` definition (`add_particlespawner`) { diff --git a/games/minimal/mods/experimental/init.lua b/games/minimal/mods/experimental/init.lua index 142734cda..5e98e1a80 100644 --- a/games/minimal/mods/experimental/init.lua +++ b/games/minimal/mods/experimental/init.lua @@ -523,6 +523,43 @@ minetest.register_craft({ } }) +minetest.register_craftitem("experimental:tester_tool_2", { + description = "Tester Tool 2", + inventory_image = "experimental_tester_tool_1.png^[invert:g", + on_use = function(itemstack, user, pointed_thing) + local pos = minetest.get_pointed_thing_position(pointed_thing, true) + if pos == nil then return end + pos = vector.add(pos, {x=0, y=0.5, z=0}) + local tex, anim + if math.random(0, 1) == 0 then + tex = "default_lava_source_animated.png" + anim = {type="sheet_2d", frames_w=3, frames_h=2, frame_length=0.5} + else + tex = "default_lava_flowing_animated.png" + anim = {type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3} + end + + minetest.add_particle({ + pos = pos, + velocity = {x=0, y=0, z=0}, + acceleration = {x=0, y=0.04, z=0}, + expirationtime = 6, + collisiondetection = true, + texture = tex, + animation = anim, + size = 4, + glow = math.random(0, 5), + }) + end, +}) + +minetest.register_craft({ + output = 'experimental:tester_tool_2', + recipe = { + {'group:crumbly','group:crumbly'}, + } +}) + --[[minetest.register_on_joinplayer(function(player) minetest.after(3, function() player:set_inventory_formspec("size[8,7.5]".. diff --git a/src/client.h b/src/client.h index f84246deb..b33358d94 100644 --- a/src/client.h +++ b/src/client.h @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "hud.h" #include "particles.h" #include "mapnode.h" +#include "tileanimation.h" struct MeshMakeData; class MapBlockMesh; @@ -186,6 +187,8 @@ struct ClientEvent bool collision_removal; bool vertical; std::string *texture; + struct TileAnimationParams animation; + u8 glow; } spawn_particle; struct{ u16 amount; @@ -206,6 +209,8 @@ struct ClientEvent bool vertical; std::string *texture; u32 id; + struct TileAnimationParams animation; + u8 glow; } add_particlespawner; struct{ u32 id; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 411982f69..b11f73e86 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/clientopcodes.h" #include "util/serialize.h" #include "util/srp.h" +#include "tileanimation.h" void Client::handleCommand_Deprecated(NetworkPacket* pkt) { @@ -896,9 +897,14 @@ void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) std::string texture = deSerializeLongString(is); bool vertical = false; bool collision_removal = false; + struct TileAnimationParams animation; + animation.type = TAT_NONE; + u8 glow = 0; try { vertical = readU8(is); collision_removal = readU8(is); + animation.deSerialize(is, m_proto_ver); + glow = readU8(is); } catch (...) {} ClientEvent event; @@ -912,6 +918,8 @@ void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) event.spawn_particle.collision_removal = collision_removal; event.spawn_particle.vertical = vertical; event.spawn_particle.texture = new std::string(texture); + event.spawn_particle.animation = animation; + event.spawn_particle.glow = glow; m_client_event_queue.push(event); } @@ -943,12 +951,20 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) bool vertical = false; bool collision_removal = false; + struct TileAnimationParams animation; + animation.type = TAT_NONE; + u8 glow = 0; u16 attached_id = 0; try { *pkt >> vertical; *pkt >> collision_removal; *pkt >> attached_id; + // This is horrible but required (why are there two ways to deserialize pkts?) + std::string datastring(pkt->getRemainingString(), pkt->getRemainingBytes()); + std::istringstream is(datastring, std::ios_base::binary); + animation.deSerialize(is, m_proto_ver); + glow = readU8(is); } catch (...) {} ClientEvent event; @@ -971,6 +987,8 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) event.add_particlespawner.vertical = vertical; event.add_particlespawner.texture = new std::string(texture); event.add_particlespawner.id = id; + event.add_particlespawner.animation = animation; + event.add_particlespawner.glow = glow; m_client_event_queue.push(event); } diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index 388afc18e..91e6c58e2 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -63,7 +63,7 @@ void NetworkPacket::putRawPacket(u8 *data, u32 datasize, u16 peer_id) m_data = std::vector(&data[2], &data[2 + m_datasize]); } -char* NetworkPacket::getString(u32 from_offset) +const char* NetworkPacket::getString(u32 from_offset) { checkReadOffset(from_offset, 0); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index 524470999..3e436aba9 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -41,12 +41,15 @@ public: u16 getPeerId() { return m_peer_id; } u16 getCommand() { return m_command; } const u32 getRemainingBytes() const { return m_datasize - m_read_offset; } + const char* getRemainingString() { return getString(m_read_offset); } // Returns a c-string without copying. // A better name for this would be getRawString() - char* getString(u32 from_offset); + const char* getString(u32 from_offset); // major difference to putCString(): doesn't write len into the buffer void putRawString(const char* src, u32 len); + void putRawString(const std::string &src) + { putRawString(src.c_str(), src.size()); } NetworkPacket& operator>>(std::string& dst); NetworkPacket& operator<<(std::string src); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index b7d023897..a4af26e87 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -541,7 +541,7 @@ void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, if (tile->material_flags & MATERIAL_FLAG_ANIMATION) { int frame_length_ms; tiledef->animation.determineParams(tile->texture->getOriginalSize(), - &frame_count, &frame_length_ms); + &frame_count, &frame_length_ms, NULL); tile->animation_frame_count = frame_count; tile->animation_frame_length_ms = frame_length_ms; } diff --git a/src/particles.cpp b/src/particles.cpp index d9eb3cfa5..5f17763e0 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -54,7 +54,9 @@ Particle::Particle( bool vertical, video::ITexture *texture, v2f texpos, - v2f texsize + v2f texsize, + const struct TileAnimationParams &anim, + u8 glow ): scene::ISceneNode(smgr->getRootSceneNode(), smgr) { @@ -71,7 +73,9 @@ Particle::Particle( m_material.setTexture(0, texture); m_texpos = texpos; m_texsize = texsize; - + m_animation = anim; + m_animation_frame = 0; + m_animation_time = 0.0; // Particle related m_pos = pos; @@ -84,6 +88,7 @@ Particle::Particle( m_collisiondetection = collisiondetection; m_collision_removal = collision_removal; m_vertical = vertical; + m_glow = glow; // Irrlicht stuff m_collisionbox = aabb3f @@ -142,6 +147,18 @@ void Particle::step(float dtime) m_velocity += m_acceleration * dtime; m_pos += m_velocity * dtime; } + if (m_animation.type != TAT_NONE) { + m_animation_time += dtime; + int frame_length_i, frame_count; + m_animation.determineParams( + m_material.getTexture(0)->getSize(), + &frame_count, &frame_length_i, NULL); + float frame_length = frame_length_i / 1000.0; + while (m_animation_time > frame_length) { + m_animation_frame++; + m_animation_time -= frame_length; + } + } // Update lighting updateLight(); @@ -166,16 +183,32 @@ void Particle::updateLight() else light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0); - m_light = decode_light(light); + m_light = decode_light(light + m_glow); } void Particle::updateVertices() { video::SColor c(255, m_light, m_light, m_light); - f32 tx0 = m_texpos.X; - f32 tx1 = m_texpos.X + m_texsize.X; - f32 ty0 = m_texpos.Y; - f32 ty1 = m_texpos.Y + m_texsize.Y; + f32 tx0, tx1, ty0, ty1; + + if (m_animation.type != TAT_NONE) { + const v2u32 texsize = m_material.getTexture(0)->getSize(); + v2f texcoord, framesize_f; + v2u32 framesize; + texcoord = m_animation.getTextureCoords(texsize, m_animation_frame); + m_animation.determineParams(texsize, NULL, NULL, &framesize); + framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y); + + tx0 = m_texpos.X + texcoord.X; + tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X; + ty0 = m_texpos.Y + texcoord.Y; + ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y; + } else { + tx0 = m_texpos.X; + tx1 = m_texpos.X + m_texsize.X; + ty0 = m_texpos.Y; + 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); @@ -210,7 +243,9 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime, float minsize, float maxsize, bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical, - video::ITexture *texture, u32 id, ParticleManager *p_manager) : + video::ITexture *texture, u32 id, const struct TileAnimationParams &anim, + u8 glow, + ParticleManager *p_manager) : m_particlemanager(p_manager) { m_gamedef = gamedef; @@ -234,6 +269,8 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, m_vertical = vertical; m_texture = texture; m_time = 0; + m_animation = anim; + m_glow = glow; for (u16 i = 0; i<=m_amount; i++) { @@ -309,7 +346,9 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) m_vertical, m_texture, v2f(0.0, 0.0), - v2f(1.0, 1.0)); + v2f(1.0, 1.0), + m_animation, + m_glow); m_particlemanager->addParticle(toadd); } i = m_spawntimes.erase(i); @@ -363,7 +402,9 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) m_vertical, m_texture, v2f(0.0, 0.0), - v2f(1.0, 1.0)); + v2f(1.0, 1.0), + m_animation, + m_glow); m_particlemanager->addParticle(toadd); } } @@ -494,6 +535,8 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, event->add_particlespawner.vertical, texture, event->add_particlespawner.id, + event->add_particlespawner.animation, + event->add_particlespawner.glow, this); /* delete allocated content of event */ @@ -529,13 +572,16 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, event->spawn_particle.vertical, texture, v2f(0.0, 0.0), - v2f(1.0, 1.0)); + v2f(1.0, 1.0), + event->spawn_particle.animation, + event->spawn_particle.glow); addParticle(toadd); delete event->spawn_particle.pos; delete event->spawn_particle.vel; delete event->spawn_particle.acc; + delete event->spawn_particle.texture; break; } @@ -564,6 +610,8 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s // Texture u8 texid = myrand_range(0, 5); 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) @@ -605,7 +653,9 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s false, texture, texpos, - texsize); + texsize, + anim, + 0); addParticle(toadd); } diff --git a/src/particles.h b/src/particles.h index 00cb2c08e..5464e6672 100644 --- a/src/particles.h +++ b/src/particles.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" #include "localplayer.h" #include "environment.h" +#include "tileanimation.h" struct ClientEvent; class ParticleManager; @@ -50,7 +51,9 @@ class Particle : public scene::ISceneNode bool vertical, video::ITexture *texture, v2f texpos, - v2f texsize + v2f texsize, + const struct TileAnimationParams &anim, + u8 glow ); ~Particle(); @@ -102,6 +105,10 @@ private: bool m_collision_removal; bool m_vertical; v3s16 m_camera_offset; + struct TileAnimationParams m_animation; + float m_animation_time; + int m_animation_frame; + u8 m_glow; }; class ParticleSpawner @@ -123,6 +130,7 @@ class ParticleSpawner bool vertical, video::ITexture *texture, u32 id, + const struct TileAnimationParams &anim, u8 glow, ParticleManager* p_manager); ~ParticleSpawner(); @@ -156,6 +164,8 @@ class ParticleSpawner bool m_collision_removal; bool m_vertical; u16 m_attached_id; + struct TileAnimationParams m_animation; + u8 m_glow; }; /** diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index b9bcfef69..84af4583b 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -322,7 +322,7 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) } else if(lua_istable(L, index)) { - // {name="default_lava.png", animation={}} + // name="default_lava.png" tiledef.name = ""; getstringfield(L, index, "name", tiledef.name); getstringfield(L, index, "image", tiledef.name); // MaterialSpec compat. @@ -334,28 +334,7 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype) L, index, "tileable_vertical", default_tiling); // animation = {} lua_getfield(L, index, "animation"); - if(lua_istable(L, -1)){ - tiledef.animation.type = (TileAnimationType) - getenumfield(L, -1, "type", es_TileAnimationType, - TAT_NONE); - if (tiledef.animation.type == TAT_VERTICAL_FRAMES) { - // {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0} - tiledef.animation.vertical_frames.aspect_w = - getintfield_default(L, -1, "aspect_w", 16); - tiledef.animation.vertical_frames.aspect_h = - getintfield_default(L, -1, "aspect_h", 16); - tiledef.animation.vertical_frames.length = - getfloatfield_default(L, -1, "length", 1.0); - } else if (tiledef.animation.type == TAT_SHEET_2D) { - // {type="sheet_2d", frames_w=5, frames_h=3, frame_length=0.5} - getintfield(L, -1, "frames_w", - tiledef.animation.sheet_2d.frames_w); - getintfield(L, -1, "frames_h", - tiledef.animation.sheet_2d.frames_h); - getfloatfield(L, -1, "frame_length", - tiledef.animation.sheet_2d.frame_length); - } - } + tiledef.animation = read_animation_definition(L, -1); lua_pop(L, 1); } @@ -925,6 +904,41 @@ void read_inventory_list(lua_State *L, int tableindex, } } +/******************************************************************************/ +struct TileAnimationParams read_animation_definition(lua_State *L, int index) +{ + if(index < 0) + index = lua_gettop(L) + 1 + index; + + struct TileAnimationParams anim; + anim.type = TAT_NONE; + if (!lua_istable(L, index)) + return anim; + + anim.type = (TileAnimationType) + getenumfield(L, index, "type", es_TileAnimationType, + TAT_NONE); + if (anim.type == TAT_VERTICAL_FRAMES) { + // {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0} + anim.vertical_frames.aspect_w = + getintfield_default(L, index, "aspect_w", 16); + anim.vertical_frames.aspect_h = + getintfield_default(L, index, "aspect_h", 16); + anim.vertical_frames.length = + getfloatfield_default(L, index, "length", 1.0); + } else if (anim.type == TAT_SHEET_2D) { + // {type="sheet_2d", frames_w=5, frames_h=3, frame_length=0.5} + getintfield(L, index, "frames_w", + anim.sheet_2d.frames_w); + getintfield(L, index, "frames_h", + anim.sheet_2d.frames_h); + getfloatfield(L, index, "frame_length", + anim.sheet_2d.frame_length); + } + + return anim; +} + /******************************************************************************/ ToolCapabilities read_tool_capabilities( lua_State *L, int table) diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 2a2228b6d..9641f5c9e 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -79,6 +79,7 @@ void push_hit_params (lua_State *L, ItemStack read_item (lua_State *L, int index, Server *srv); +struct TileAnimationParams read_animation_definition(lua_State *L, int index); ToolCapabilities read_tool_capabilities (lua_State *L, int table); void push_tool_capabilities (lua_State *L, diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index 667ac7272..7f415844a 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_object.h" #include "lua_api/l_internal.h" #include "common/c_converter.h" +#include "common/c_content.h" #include "server.h" #include "particles.h" @@ -34,6 +35,8 @@ with this program; if not, write to the Free Software Foundation, Inc., // collision_removal = bool // vertical = bool // texture = e.g."default_wood.png" +// animation = TileAnimation definition +// glow = num int ModApiParticles::l_add_particle(lua_State *L) { MAP_LOCK_REQUIRED; @@ -47,10 +50,13 @@ int ModApiParticles::l_add_particle(lua_State *L) bool collisiondetection, vertical, collision_removal; collisiondetection = vertical = collision_removal = false; + struct TileAnimationParams animation; std::string texture = ""; std::string playername = ""; + u8 glow = 0; + if (lua_gettop(L) > 1) // deprecated { log_deprecated(L, "Deprecated add_particle call with individual parameters instead of definition"); @@ -101,11 +107,18 @@ int ModApiParticles::l_add_particle(lua_State *L) collision_removal = getboolfield_default(L, 1, "collision_removal", collision_removal); vertical = getboolfield_default(L, 1, "vertical", vertical); + + lua_getfield(L, 1, "animation"); + animation = read_animation_definition(L, -1); + lua_pop(L, 1); + texture = getstringfield_default(L, 1, "texture", ""); playername = getstringfield_default(L, 1, "playername", ""); + + glow = getintfield_default(L, 1, "glow", 0); } getServer(L)->spawnParticle(playername, pos, vel, acc, expirationtime, size, - collisiondetection, collision_removal, vertical, texture); + collisiondetection, collision_removal, vertical, texture, animation, glow); return 1; } @@ -127,6 +140,8 @@ int ModApiParticles::l_add_particle(lua_State *L) // collision_removal = bool // vertical = bool // texture = e.g."default_wood.png" +// animation = TileAnimation definition +// glow = num int ModApiParticles::l_add_particlespawner(lua_State *L) { MAP_LOCK_REQUIRED; @@ -139,9 +154,11 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) time= minexptime= maxexptime= minsize= maxsize= 1; bool collisiondetection, vertical, collision_removal; collisiondetection = vertical = collision_removal = false; + struct TileAnimationParams animation; ServerActiveObject *attached = NULL; std::string texture = ""; std::string playername = ""; + u8 glow = 0; if (lua_gettop(L) > 1) //deprecated { @@ -201,6 +218,10 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) collision_removal = getboolfield_default(L, 1, "collision_removal", collision_removal); + lua_getfield(L, 1, "animation"); + animation = read_animation_definition(L, -1); + lua_pop(L, 1); + lua_getfield(L, 1, "attached"); if (!lua_isnil(L, -1)) { ObjectRef *ref = ObjectRef::checkobject(L, -1); @@ -211,6 +232,7 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) vertical = getboolfield_default(L, 1, "vertical", vertical); texture = getstringfield_default(L, 1, "texture", ""); playername = getstringfield_default(L, 1, "playername", ""); + glow = getintfield_default(L, 1, "glow", 0); } u32 id = getServer(L)->addParticleSpawner(amount, time, @@ -223,7 +245,8 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) collision_removal, attached, vertical, - texture, playername); + texture, playername, + animation, glow); lua_pushnumber(L, id); return 1; diff --git a/src/server.cpp b/src/server.cpp index 74d9541c9..d3d5fd3d1 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1662,12 +1662,28 @@ void Server::SendShowFormspecMessage(u16 peer_id, const std::string &formspec, } // Spawns a particle on peer with peer_id -void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f acceleration, +void Server::SendSpawnParticle(u16 peer_id, u16 protocol_version, + v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, bool collisiondetection, bool collision_removal, - bool vertical, const std::string &texture) + bool vertical, const std::string &texture, + const struct TileAnimationParams &animation, u8 glow) { DSTACK(FUNCTION_NAME); + if (peer_id == PEER_ID_INEXISTENT) { + // This sucks and should be replaced by a better solution in a refactor: + std::vector clients = m_clients.getClientIDs(); + for (std::vector::iterator i = clients.begin(); i != clients.end(); ++i) { + RemotePlayer *player = m_env->getPlayer(*i); + if (!player) + continue; + SendSpawnParticle(*i, player->protocol_version, + pos, velocity, acceleration, + expirationtime, size, collisiondetection, + collision_removal, vertical, texture, animation, glow); + } + return; + } NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id); @@ -1676,22 +1692,39 @@ void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f accelerat pkt.putLongString(texture); pkt << vertical; pkt << collision_removal; + // This is horrible but required (why are there two ways to serialize pkts?) + std::ostringstream os(std::ios_base::binary); + animation.serialize(os, protocol_version); + pkt.putRawString(os.str()); + pkt << glow; - if (peer_id != PEER_ID_INEXISTENT) { - Send(&pkt); - } - else { - m_clients.sendToAll(0, &pkt, true); - } + Send(&pkt); } // Adds a ParticleSpawner on peer with peer_id -void Server::SendAddParticleSpawner(u16 peer_id, u16 amount, float spawntime, v3f minpos, v3f maxpos, +void Server::SendAddParticleSpawner(u16 peer_id, u16 protocol_version, + u16 amount, float spawntime, v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime, float minsize, float maxsize, bool collisiondetection, bool collision_removal, - u16 attached_id, bool vertical, const std::string &texture, u32 id) + u16 attached_id, bool vertical, const std::string &texture, u32 id, + const struct TileAnimationParams &animation, u8 glow) { DSTACK(FUNCTION_NAME); + if (peer_id == PEER_ID_INEXISTENT) { + // This sucks and should be replaced: + std::vector clients = m_clients.getClientIDs(); + for (std::vector::iterator i = clients.begin(); i != clients.end(); ++i) { + RemotePlayer *player = m_env->getPlayer(*i); + if (!player) + continue; + SendAddParticleSpawner(*i, player->protocol_version, + amount, spawntime, minpos, maxpos, + minvel, maxvel, minacc, maxacc, minexptime, maxexptime, + minsize, maxsize, collisiondetection, collision_removal, + attached_id, vertical, texture, id, animation, glow); + } + return; + } NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 0, peer_id); @@ -1704,13 +1737,13 @@ void Server::SendAddParticleSpawner(u16 peer_id, u16 amount, float spawntime, v3 pkt << id << vertical; pkt << collision_removal; pkt << attached_id; + // This is horrible but required + std::ostringstream os(std::ios_base::binary); + animation.serialize(os, protocol_version); + pkt.putRawString(os.str()); + pkt << glow; - if (peer_id != PEER_ID_INEXISTENT) { - Send(&pkt); - } - else { - m_clients.sendToAll(0, &pkt, true); - } + Send(&pkt); } void Server::SendDeleteParticleSpawner(u16 peer_id, u32 id) @@ -3165,23 +3198,25 @@ void Server::spawnParticle(const std::string &playername, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, bool collisiondetection, bool collision_removal, - bool vertical, const std::string &texture) + bool vertical, const std::string &texture, + const struct TileAnimationParams &animation, u8 glow) { // m_env will be NULL if the server is initializing if (!m_env) return; - u16 peer_id = PEER_ID_INEXISTENT; + u16 peer_id = PEER_ID_INEXISTENT, proto_ver = 0; if (playername != "") { RemotePlayer *player = m_env->getPlayer(playername.c_str()); if (!player) return; peer_id = player->peer_id; + proto_ver = player->protocol_version; } - SendSpawnParticle(peer_id, pos, velocity, acceleration, + SendSpawnParticle(peer_id, proto_ver, pos, velocity, acceleration, expirationtime, size, collisiondetection, - collision_removal, vertical, texture); + collision_removal, vertical, texture, animation, glow); } u32 Server::addParticleSpawner(u16 amount, float spawntime, @@ -3189,18 +3224,20 @@ u32 Server::addParticleSpawner(u16 amount, float spawntime, float minexptime, float maxexptime, float minsize, float maxsize, bool collisiondetection, bool collision_removal, ServerActiveObject *attached, bool vertical, const std::string &texture, - const std::string &playername) + const std::string &playername, const struct TileAnimationParams &animation, + u8 glow) { // m_env will be NULL if the server is initializing if (!m_env) return -1; - u16 peer_id = PEER_ID_INEXISTENT; + u16 peer_id = PEER_ID_INEXISTENT, proto_ver = 0; if (playername != "") { RemotePlayer *player = m_env->getPlayer(playername.c_str()); if (!player) return -1; peer_id = player->peer_id; + proto_ver = player->protocol_version; } u16 attached_id = attached ? attached->getId() : 0; @@ -3211,11 +3248,11 @@ u32 Server::addParticleSpawner(u16 amount, float spawntime, else id = m_env->addParticleSpawner(spawntime, attached_id); - SendAddParticleSpawner(peer_id, amount, spawntime, + SendAddParticleSpawner(peer_id, proto_ver, amount, spawntime, minpos, maxpos, minvel, maxvel, minacc, maxacc, minexptime, maxexptime, minsize, maxsize, collisiondetection, collision_removal, attached_id, vertical, - texture, id); + texture, id, animation, glow); return id; } diff --git a/src/server.h b/src/server.h index a86f75f1d..e5121bdc3 100644 --- a/src/server.h +++ b/src/server.h @@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mods.h" #include "inventorymanager.h" #include "subgame.h" +#include "tileanimation.h" // struct TileAnimationParams #include "util/numeric.h" #include "util/thread.h" #include "util/basic_macros.h" @@ -252,7 +253,8 @@ public: v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, bool collisiondetection, bool collision_removal, - bool vertical, const std::string &texture); + bool vertical, const std::string &texture, + const struct TileAnimationParams &animation, u8 glow); u32 addParticleSpawner(u16 amount, float spawntime, v3f minpos, v3f maxpos, @@ -263,7 +265,8 @@ public: bool collisiondetection, bool collision_removal, ServerActiveObject *attached, bool vertical, const std::string &texture, - const std::string &playername); + const std::string &playername, const struct TileAnimationParams &animation, + u8 glow); void deleteParticleSpawner(const std::string &playername, u32 id); @@ -428,7 +431,8 @@ private: void sendDetachedInventories(u16 peer_id); // Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) - void SendAddParticleSpawner(u16 peer_id, u16 amount, float spawntime, + void SendAddParticleSpawner(u16 peer_id, u16 protocol_version, + u16 amount, float spawntime, v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, @@ -436,16 +440,18 @@ private: float minsize, float maxsize, bool collisiondetection, bool collision_removal, u16 attached_id, - bool vertical, const std::string &texture, u32 id); + bool vertical, const std::string &texture, u32 id, + const struct TileAnimationParams &animation, u8 glow); void SendDeleteParticleSpawner(u16 peer_id, u32 id); // Spawns particle on peer with peer_id (PEER_ID_INEXISTENT == all) - void SendSpawnParticle(u16 peer_id, + void SendSpawnParticle(u16 peer_id, u16 protocol_version, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, bool collisiondetection, bool collision_removal, - bool vertical, const std::string &texture); + bool vertical, const std::string &texture, + const struct TileAnimationParams &animation, u8 glow); u32 SendActiveObjectRemoveAdd(u16 peer_id, const std::string &datas); void SendActiveObjectMessages(u16 peer_id, const std::string &datas, bool reliable = true); diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index a23eecc2e..67d27d396 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -69,7 +69,8 @@ void TileAnimationParams::deSerialize(std::istream &is, u16 protocol_version) } } -void TileAnimationParams::determineParams(v2u32 texture_size, int *frame_count, int *frame_length_ms) const +void TileAnimationParams::determineParams(v2u32 texture_size, int *frame_count, + int *frame_length_ms, v2u32 *frame_size) const { if (type == TAT_VERTICAL_FRAMES) { int frame_height = (float)texture_size.X / @@ -80,15 +81,17 @@ void TileAnimationParams::determineParams(v2u32 texture_size, int *frame_count, *frame_count = _frame_count; if (frame_length_ms) *frame_length_ms = 1000.0 * vertical_frames.length / _frame_count; + if (frame_size) + *frame_size = v2u32(texture_size.X, frame_height); } else if (type == TAT_SHEET_2D) { if (frame_count) *frame_count = sheet_2d.frames_w * sheet_2d.frames_h; if (frame_length_ms) *frame_length_ms = 1000 * sheet_2d.frame_length; - } else { // TAT_NONE - *frame_count = 1; - *frame_length_ms = 1000; + if (frame_size) + *frame_size = v2u32(texture_size.X / sheet_2d.frames_w, texture_size.Y / sheet_2d.frames_h); } + // caller should check for TAT_NONE } void TileAnimationParams::getTextureModifer(std::ostream &os, v2u32 texture_size, int frame) const @@ -97,7 +100,7 @@ void TileAnimationParams::getTextureModifer(std::ostream &os, v2u32 texture_size return; if (type == TAT_VERTICAL_FRAMES) { int frame_count; - determineParams(texture_size, &frame_count, NULL); + determineParams(texture_size, &frame_count, NULL, NULL); os << "^[verticalframe:" << frame_count << ":" << frame; } else if (type == TAT_SHEET_2D) { int q, r; @@ -107,3 +110,22 @@ void TileAnimationParams::getTextureModifer(std::ostream &os, v2u32 texture_size << ":" << r << "," << q; } } + +v2f TileAnimationParams::getTextureCoords(v2u32 texture_size, int frame) const +{ + v2u32 ret(0, 0); + if (type == TAT_VERTICAL_FRAMES) { + int frame_height = (float)texture_size.X / + (float)vertical_frames.aspect_w * + (float)vertical_frames.aspect_h; + ret = v2u32(0, frame_height * frame); + } else if (type == TAT_SHEET_2D) { + v2u32 frame_size; + determineParams(texture_size, NULL, NULL, &frame_size); + int q, r; + q = frame / sheet_2d.frames_w; + r = frame % sheet_2d.frames_w; + ret = v2u32(r * frame_size.X, q * frame_size.Y); + } + return v2f(ret.X / (float) texture_size.X, ret.Y / (float) texture_size.Y); +} diff --git a/src/tileanimation.h b/src/tileanimation.h index 289ce515b..eecd3eb96 100644 --- a/src/tileanimation.h +++ b/src/tileanimation.h @@ -48,8 +48,10 @@ struct TileAnimationParams { void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is, u16 protocol_version); - void determineParams(v2u32 texture_size, int *frame_count, int *frame_length_ms) const; + void determineParams(v2u32 texture_size, int *frame_count, + int *frame_length_ms, v2u32 *frame_size) const; void getTextureModifer(std::ostream &os, v2u32 texture_size, int frame) const; + v2f getTextureCoords(v2u32 texture_size, int frame) const; }; #endif -- cgit v1.2.3 From efa54f9c460239c23a2014076764d6c6830589e6 Mon Sep 17 00:00:00 2001 From: Elijah Duffy Date: Fri, 20 Jan 2017 10:49:20 -0800 Subject: Add chatcommand unregister and override API (#5076) Introduces two functions to unregister and override chatcommands. minetest.unregister_chatcommand("") and minetest.override_chatcommand("", {}) --- builtin/game/chatcommands.lua | 18 ++++++++++++++++++ doc/lua_api.txt | 4 ++++ 2 files changed, 22 insertions(+) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 199b9e964..08dc1bb1d 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -15,6 +15,24 @@ function core.register_chatcommand(cmd, def) core.registered_chatcommands[cmd] = def end +function core.unregister_chatcommand(name) + if core.registered_chatcommands[name] then + core.registered_chatcommands[name] = nil + else + core.log("warning", "Not unregistering chatcommand " ..name.. + " because it doesn't exist.") + end +end + +function core.override_chatcommand(name, redefinition) + local chatcommand = core.registered_chatcommands[name] + assert(chatcommand, "Attempt to override non-existent chatcommand "..name) + for k, v in pairs(redefinition) do + rawset(chatcommand, k, v) + end + core.registered_chatcommands[name] = chatcommand +end + core.register_on_chat_message(function(name, message) local cmd, param = string.match(message, "^/([^ ]+) *(.*)") if not param then diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9bdc01c07..aada851a1 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2077,6 +2077,10 @@ Call these functions only at load time! ### Other registration functions * `minetest.register_chatcommand(cmd, chatcommand definition)` * Adds definition to minetest.registered_chatcommands +* `minetest.override_chatcommand(name, redefinition)` + * Overrides fields of a chatcommand registered with register_chatcommand. +* `minetest.unregister_chatcommand(name)` + * Unregisters a chatcommands registered with register_chatcommand. * `minetest.register_privilege(name, definition)` * `definition`: `"description text"` * `definition`: `{ description = "description text", give_to_singleplayer = boolean}` -- cgit v1.2.3 From c57b4ff9b592617539aa978374c13cdd5f1603a6 Mon Sep 17 00:00:00 2001 From: sapier Date: Sat, 14 Jan 2017 19:32:10 +0100 Subject: Add Entity get_texture_mod() to Lua API Send texture modifier to clients connecting later too --- doc/lua_api.txt | 1 + src/content_cao.cpp | 26 ++++++++++++++++++++------ src/content_cao.h | 4 +++- src/content_sao.cpp | 12 +++++++++++- src/content_sao.h | 2 ++ src/script/lua_api/l_object.cpp | 13 +++++++++++++ src/script/lua_api/l_object.h | 3 +++ 7 files changed, 53 insertions(+), 8 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index aada851a1..e5a3362ee 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2848,6 +2848,7 @@ This is basically a reference to a C++ `ServerActiveObject` * `set_yaw(radians)` * `get_yaw()`: returns number in radians * `set_texture_mod(mod)` +* `get_texture_mod()` returns current texture modifier * `set_sprite(p={x=0,y=0}, num_frames=1, framelength=0.2, select_horiz_by_yawpitch=false)` * Select sprite from spritesheet with optional animation and DM-style diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 93ac1f785..e829fc761 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -574,6 +574,8 @@ GenericCAO::GenericCAO(Client *client, ClientEnvironment *env): m_anim_framelength(0.2), m_anim_timer(0), m_reset_textures_timer(-1), + m_previous_texture_modifier(""), + m_current_texture_modifier(""), m_visuals_expired(false), m_step_distance_counter(0), m_last_light(255), @@ -952,7 +954,10 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, infostream<<"GenericCAO::addToScene(): \""<= 0) { m_reset_textures_timer -= dtime; - if(m_reset_textures_timer <= 0){ + if(m_reset_textures_timer <= 0) { m_reset_textures_timer = -1; - updateTextures(""); + updateTextures(m_previous_texture_modifier); } } if(getParent() == NULL && fabs(m_prop.automatic_rotate) > 0.001) @@ -1301,7 +1306,7 @@ void GenericCAO::updateTexturePos() } } -void GenericCAO::updateTextures(const std::string &mod) +void GenericCAO::updateTextures(const std::string mod) { ITextureSource *tsrc = m_client->tsrc(); @@ -1309,6 +1314,9 @@ void GenericCAO::updateTextures(const std::string &mod) bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); + m_previous_texture_modifier = m_current_texture_modifier; + m_current_texture_modifier = mod; + if(m_spritenode) { if(m_prop.visual == "sprite") @@ -1611,6 +1619,12 @@ void GenericCAO::processMessage(const std::string &data) updateNodePos(); } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) { std::string mod = deSerializeString(is); + + // immediatly reset a engine issued texture modifier if a mod sends a different one + if (m_reset_textures_timer > 0) { + m_reset_textures_timer = -1; + updateTextures(m_previous_texture_modifier); + } updateTextures(mod); } else if (cmd == GENERIC_CMD_SET_SPRITE) { v2s16 p = readV2S16(is); @@ -1734,7 +1748,7 @@ void GenericCAO::processMessage(const std::string &data) m_reset_textures_timer = 0.05; if(damage >= 2) m_reset_textures_timer += 0.05 * damage; - updateTextures("^[brighten"); + updateTextures(m_current_texture_modifier + "^[brighten"); } } } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) { @@ -1802,7 +1816,7 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, m_reset_textures_timer = 0.05; if(result.damage >= 2) m_reset_textures_timer += 0.05 * result.damage; - updateTextures("^[brighten"); + updateTextures(m_current_texture_modifier + "^[brighten"); } return false; diff --git a/src/content_cao.h b/src/content_cao.h index 846e0690a..f30e90e21 100644 --- a/src/content_cao.h +++ b/src/content_cao.h @@ -102,6 +102,8 @@ private: float m_anim_timer; ItemGroupList m_armor_groups; float m_reset_textures_timer; + std::string m_previous_texture_modifier; // stores texture modifier before punch update + std::string m_current_texture_modifier; // last applied texture modifier bool m_visuals_expired; float m_step_distance_counter; u8 m_last_light; @@ -198,7 +200,7 @@ public: void updateTexturePos(); - void updateTextures(const std::string &mod); + void updateTextures(const std::string mod); void updateAnimation(); diff --git a/src/content_sao.cpp b/src/content_sao.cpp index d6581144f..f0973082d 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -257,7 +257,8 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, m_last_sent_position(0,0,0), m_last_sent_velocity(0,0,0), m_last_sent_position_timer(0), - m_last_sent_move_precision(0) + m_last_sent_move_precision(0), + m_current_texture_modifier("") { // Only register type if no environment supplied if(env == NULL){ @@ -511,6 +512,9 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) } } + msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier)); + message_count++; + writeU8(os, message_count); os.write(msg_os.str().c_str(), msg_os.str().size()); } @@ -687,11 +691,17 @@ v3f LuaEntitySAO::getAcceleration() void LuaEntitySAO::setTextureMod(const std::string &mod) { std::string str = gob_cmd_set_texture_mod(mod); + m_current_texture_modifier = mod; // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } +std::string LuaEntitySAO::getTextureMod() const +{ + return m_current_texture_modifier; +} + void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, bool select_horiz_by_yawpitch) { diff --git a/src/content_sao.h b/src/content_sao.h index 884f0f406..56a26fb0d 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -122,6 +122,7 @@ public: v3f getAcceleration(); void setTextureMod(const std::string &mod); + std::string getTextureMod() const; void setSprite(v2s16 p, int num_frames, float framelength, bool select_horiz_by_yawpitch); std::string getName(); @@ -143,6 +144,7 @@ private: v3f m_last_sent_velocity; float m_last_sent_position_timer; float m_last_sent_move_precision; + std::string m_current_texture_modifier; }; /* diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index f579c0b86..1fa3663ca 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -900,6 +900,19 @@ int ObjectRef::l_set_texture_mod(lua_State *L) return 0; } +// get_texture_mod(self) +int ObjectRef::l_get_texture_mod(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + LuaEntitySAO *co = getluaobject(ref); + if (co == NULL) return 0; + // Do it + std::string mod = co->getTextureMod(); + lua_pushstring(L, mod.c_str()); + return 1; +} + // set_sprite(self, p={x=0,y=0}, num_frames=1, framelength=0.2, // select_horiz_by_yawpitch=false) int ObjectRef::l_set_sprite(lua_State *L) diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 06b1bb79b..96d0abae8 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -164,6 +164,9 @@ private: // set_texture_mod(self, mod) static int l_set_texture_mod(lua_State *L); + // l_get_texture_mod(self) + static int l_get_texture_mod(lua_State *L); + // set_sprite(self, p={x=0,y=0}, num_frames=1, framelength=0.2, // select_horiz_by_yawpitch=false) static int l_set_sprite(lua_State *L); -- 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 'doc/lua_api.txt') 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 2d7a6f2cc0717eb92de4a91326a871d525ce513d Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Thu, 12 Jan 2017 11:27:39 -0800 Subject: Vector: Add vector.sort(a, b): return box edges This function returns the box corners of the smallest box that includes the two given coordinates. --- builtin/common/vector.lua | 5 +++++ doc/lua_api.txt | 1 + 2 files changed, 6 insertions(+) (limited to 'doc/lua_api.txt') diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index 90ba3cc8b..0549f9a56 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -138,3 +138,8 @@ function vector.divide(a, b) z = a.z / b} end end + +function vector.sort(a, b) + return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)}, + {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)} +end diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 2a0b72053..31a1daefb 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1850,6 +1850,7 @@ Spatial Vectors * `vector.round(v)`: returns a vector, each dimension rounded to nearest int * `vector.apply(v, func)`: returns a vector * `vector.equals(v1, v2)`: returns a boolean +* `vector.sort(v1, v2)`: returns minp, maxp vectors of the cuboid defined by v1 and v2 For the following functions `x` can be either a vector or a number: -- cgit v1.2.3 From 7fc67199683d3c60fe0b3ddcb2a9594b4804cc38 Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Thu, 12 Jan 2017 11:56:41 -0800 Subject: core: Add dir_to_yaw and yaw_to_dir helpers These are needed to go from things like entity yaw to a vector and vice versa. --- builtin/game/item.lua | 8 ++++++++ doc/lua_api.txt | 4 ++++ 2 files changed, 12 insertions(+) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/item.lua b/builtin/game/item.lua index bf456a4e0..e51da6d6b 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -147,6 +147,14 @@ function core.wallmounted_to_dir(wallmounted) return wallmounted_to_dir[wallmounted] end +function core.dir_to_yaw(dir) + return -math.atan2(dir.x, dir.z) +end + +function core.yaw_to_dir(yaw) + return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)} +end + function core.get_node_drops(nodename, toolname) local drop = ItemStack({name=nodename}):get_definition().drop if drop == nil then diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 31a1daefb..62a7b81f7 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2403,6 +2403,10 @@ and `minetest.auth_reload` call the authetification handler. * Convert a vector to a wallmounted value, used for `paramtype2="wallmounted"` * `minetest.wallmounted_to_dir(wallmounted)` * Convert a wallmounted value back into a vector aimed directly out the "back" of a node +* `minetest.dir_to_yaw(dir)` + * Convert a vector into a yaw (angle) +* `minetest.yaw_to_dir(yaw)` + * Convert yaw (angle) to a vector * `minetest.get_node_drops(nodename, toolname)` * Returns list of item names. * **Note**: This will be removed or modified in a future version. -- cgit v1.2.3 From b7a98e98500402c3bbdb6d56d0fe42b4f5b3cedb Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Fri, 27 Jan 2017 08:59:30 +0100 Subject: Implement player attribute backend (#4155) * This backend permit mods to store extra players attributes to a common interface. * Add the obj:set_attribute(attr, value) Lua call * Add the obj:get_attribute(attr) Lua call Examples: * player:set_attribute("home:home", "10,25,-78") * player:get_attribute("default:mana") Attributes are saved as a json in the player file in extended_attributes key They are saved only if a modification on the attributes occurs and loaded when emergePlayer is called (they are attached to PlayerSAO). --- doc/lua_api.txt | 2 ++ src/content_sao.cpp | 1 + src/content_sao.h | 37 +++++++++++++++++++++++++++++++++++++ src/remoteplayer.cpp | 38 +++++++++++++++++++++++++++++++++++--- src/remoteplayer.h | 5 ++++- src/script/lua_api/l_object.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ src/script/lua_api/l_object.h | 6 ++++++ src/server.h | 1 - src/serverenvironment.cpp | 3 ++- 9 files changed, 128 insertions(+), 6 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 62a7b81f7..ee7d57c2f 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2899,6 +2899,8 @@ This is basically a reference to a C++ `ServerActiveObject` * `0`: player is drowning, * `1`-`10`: remaining number of bubbles * `11`: bubbles bar is not shown +* `set_attribute(attribute, value)`: sets an extra attribute with value on player +* `get_attribute(attribute)`: returns value for extra attribute. Returns nil if no attribute found. * `set_inventory_formspec(formspec)` * Redefine player's inventory form * Should usually be called in on_joinplayer diff --git a/src/content_sao.cpp b/src/content_sao.cpp index bb62aea7d..35133490e 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -791,6 +791,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer m_pitch(0), m_fov(0), m_wanted_range(0), + m_extended_attributes_modified(false), // public m_physics_override_speed(1), m_physics_override_jump(1), diff --git a/src/content_sao.h b/src/content_sao.h index c3674fa2d..bbf244742 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -180,6 +180,7 @@ public: } }; +typedef UNORDERED_MAP PlayerAttributes; class RemotePlayer; class PlayerSAO : public UnitSAO @@ -249,6 +250,39 @@ public: int getWieldIndex() const; void setWieldIndex(int i); + /* + Modding interface + */ + inline void setExtendedAttribute(const std::string &attr, const std::string &value) + { + m_extra_attributes[attr] = value; + m_extended_attributes_modified = true; + } + + inline bool getExtendedAttribute(const std::string &attr, std::string *value) + { + if (m_extra_attributes.find(attr) == m_extra_attributes.end()) + return false; + + *value = m_extra_attributes[attr]; + return true; + } + + inline const PlayerAttributes &getExtendedAttributes() + { + return m_extra_attributes; + } + + inline bool extendedAttributesModified() const + { + return m_extended_attributes_modified; + } + + inline void setExtendedAttributeModified(bool v) + { + m_extended_attributes_modified = v; + } + /* PlayerSAO-specific */ @@ -343,6 +377,9 @@ private: f32 m_pitch; f32 m_fov; s16 m_wanted_range; + + PlayerAttributes m_extra_attributes; + bool m_extended_attributes_modified; public: float m_physics_override_speed; float m_physics_override_jump; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 18bfa1030..6853ad6d9 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -19,13 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "remoteplayer.h" +#include #include "content_sao.h" #include "filesys.h" #include "gamedef.h" #include "porting.h" // strlcpy +#include "server.h" #include "settings.h" - /* RemotePlayer */ @@ -112,9 +113,23 @@ void RemotePlayer::save(std::string savedir, IGameDef *gamedef) } infostream << "Didn't find free file for player " << m_name << std::endl; - return; } +void RemotePlayer::serializeExtraAttributes(std::string &output) +{ + assert(m_sao); + Json::Value json_root; + const PlayerAttributes &attrs = m_sao->getExtendedAttributes(); + for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) { + json_root[(*it).first] = (*it).second; + } + + Json::FastWriter writer; + output = writer.write(json_root); + m_sao->setExtendedAttributeModified(false); +} + + void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao) { @@ -150,6 +165,20 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, try { sao->setBreath(args.getS32("breath"), false); } catch (SettingNotFoundException &e) {} + + try { + std::string extended_attributes = args.get("extended_attributes"); + Json::Reader reader; + Json::Value attr_root; + reader.parse(extended_attributes, attr_root); + + const Json::Value::Members attr_list = attr_root.getMemberNames(); + for (Json::Value::Members::const_iterator it = attr_list.begin(); + it != attr_list.end(); ++it) { + Json::Value attr_value = attr_root[*it]; + sao->setExtendedAttribute(*it, attr_value.asString()); + } + } catch (SettingNotFoundException &e) {} } inventory.deSerialize(is); @@ -175,7 +204,6 @@ void RemotePlayer::serialize(std::ostream &os) Settings args; args.setS32("version", 1); args.set("name", m_name); - //args.set("password", m_password); // This should not happen assert(m_sao); @@ -185,6 +213,10 @@ void RemotePlayer::serialize(std::ostream &os) args.setFloat("yaw", m_sao->getYaw()); args.setS32("breath", m_sao->getBreath()); + std::string extended_attrs = ""; + serializeExtraAttributes(extended_attrs); + args.set("extended_attributes", extended_attrs); + args.writeLines(os); os<<"PlayerArgsEnd\n"; diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 61b5a23de..f44fb9332 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -25,11 +25,13 @@ with this program; if not, write to the Free Software Foundation, Inc., class PlayerSAO; -enum RemotePlayerChatResult { +enum RemotePlayerChatResult +{ RPLAYER_CHATRESULT_OK, RPLAYER_CHATRESULT_FLOODING, RPLAYER_CHATRESULT_KICK, }; + /* Player on the server */ @@ -135,6 +137,7 @@ private: deSerialize stops reading exactly at the right point. */ void serialize(std::ostream &os); + void serializeExtraAttributes(std::string &output); PlayerSAO *m_sao; bool m_dirty; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index be4451704..9352812ab 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1181,6 +1181,45 @@ int ObjectRef::l_get_breath(lua_State *L) return 1; } +// set_attribute(self, attribute, value) +int ObjectRef::l_set_attribute(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) { + return 0; + } + + std::string attr = luaL_checkstring(L, 2); + std::string value = luaL_checkstring(L, 3); + + if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + co->setExtendedAttribute(attr, value); + } + return 1; +} + +// get_attribute(self, attribute) +int ObjectRef::l_get_attribute(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) { + return 0; + } + + std::string attr = luaL_checkstring(L, 2); + + std::string value = ""; + if (co->getExtendedAttribute(attr, &value)) { + lua_pushstring(L, value.c_str()); + return 1; + } + + return 0; +} + + // set_inventory_formspec(self, formspec) int ObjectRef::l_set_inventory_formspec(lua_State *L) { @@ -1839,6 +1878,8 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, set_look_pitch), luamethod(ObjectRef, get_breath), luamethod(ObjectRef, set_breath), + luamethod(ObjectRef, get_attribute), + luamethod(ObjectRef, set_attribute), luamethod(ObjectRef, set_inventory_formspec), luamethod(ObjectRef, get_inventory_formspec), luamethod(ObjectRef, get_player_control), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 96d0abae8..2c9aa559a 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -226,6 +226,12 @@ private: // get_breath(self, breath) static int l_get_breath(lua_State *L); + // set_attribute(self, attribute, value) + static int l_set_attribute(lua_State *L); + + // get_attribute(self, attribute) + static int l_get_attribute(lua_State *L); + // set_inventory_formspec(self, formspec) static int l_set_inventory_formspec(lua_State *L); diff --git a/src/server.h b/src/server.h index e5121bdc3..8f553ce38 100644 --- a/src/server.h +++ b/src/server.h @@ -576,7 +576,6 @@ private: float m_time_of_day_send_timer; // Uptime of server in seconds MutexedVariable m_uptime; - /* Client interface */ diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 01dc3ff10..7a5cfafd6 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -500,7 +500,8 @@ void ServerEnvironment::saveLoadedPlayers() for (std::vector::iterator it = m_players.begin(); it != m_players.end(); ++it) { - if ((*it)->checkModified()) { + if ((*it)->checkModified() || + ((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) { (*it)->save(players_path, m_server); } } -- 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 'doc/lua_api.txt') 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 79d752ba4f6f0197627cc20f99b2540f63b6dc88 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 2 Jan 2017 15:17:28 +0100 Subject: from_table: Fix crash for missing inventory or field --- doc/lua_api.txt | 3 ++- src/script/lua_api/l_nodemeta.cpp | 46 +++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 20 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ff745c1c2..9a1cb6bac 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2781,8 +2781,9 @@ Can be gotten via `minetest.get_meta(pos)`. * `get_inventory()`: returns `InvRef` * `to_table()`: returns `nil` or `{fields = {...}, inventory = {list1 = {}, ...}}` * `from_table(nil or {})` - * to clear metadata, use from_table(nil) + * Any non-table value will clear the metadata * See "Node Metadata" + * returns `true` on success ### `NodeTimerRef` Node Timers: a high resolution persistent per-node timer. diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 3d03c0c41..631aa68ce 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -250,7 +250,7 @@ int NodeMetaRef::l_from_table(lua_State *L) // clear old metadata first ref->m_env->getMap().removeNodeMetadata(ref->m_p); - if(lua_isnil(L, base)){ + if (!lua_istable(L, base)) { // No metadata lua_pushboolean(L, true); return 1; @@ -258,34 +258,42 @@ int NodeMetaRef::l_from_table(lua_State *L) // Create new metadata NodeMetadata *meta = getmeta(ref, true); - if(meta == NULL){ + if (meta == NULL) { lua_pushboolean(L, false); return 1; } + // Set fields lua_getfield(L, base, "fields"); - int fieldstable = lua_gettop(L); - lua_pushnil(L); - while(lua_next(L, fieldstable) != 0){ - // key at index -2 and value at index -1 - std::string name = lua_tostring(L, -2); - size_t cl; - const char *cs = lua_tolstring(L, -1, &cl); - std::string value(cs, cl); - meta->setString(name, value); - lua_pop(L, 1); // removes value, keeps key for next iteration + if (lua_istable(L, -1)) { + int fieldstable = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, fieldstable) != 0) { + // key at index -2 and value at index -1 + std::string name = lua_tostring(L, -2); + size_t cl; + const char *cs = lua_tolstring(L, -1, &cl); + meta->setString(name, std::string(cs, cl)); + lua_pop(L, 1); // Remove value, keep key for next iteration + } + lua_pop(L, 1); } + // Set inventory Inventory *inv = meta->getInventory(); lua_getfield(L, base, "inventory"); - int inventorytable = lua_gettop(L); - lua_pushnil(L); - while(lua_next(L, inventorytable) != 0){ - // key at index -2 and value at index -1 - std::string name = lua_tostring(L, -2); - read_inventory_list(L, -1, inv, name.c_str(), getServer(L)); - lua_pop(L, 1); // removes value, keeps key for next iteration + if (lua_istable(L, -1)) { + int inventorytable = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, inventorytable) != 0) { + // key at index -2 and value at index -1 + std::string name = lua_tostring(L, -2); + read_inventory_list(L, -1, inv, name.c_str(), getServer(L)); + lua_pop(L, 1); // Remove value, keep key for next iteration + } + lua_pop(L, 1); } + reportMetadataChange(ref); lua_pushboolean(L, true); return 1; -- cgit v1.2.3 From e761b9f48626db2af8b62a0cf85691208951cf0d Mon Sep 17 00:00:00 2001 From: sapier Date: Sat, 14 Jan 2017 23:16:58 +0100 Subject: Add multiply texture modifier Allows colorizing of textures using a color multiplication method. --- doc/lua_api.txt | 7 +++++++ src/client/tile.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9a1cb6bac..219882f46 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -419,6 +419,13 @@ the word "`alpha`", then each texture pixel will contain the RGB of `` and the alpha of `` multiplied by the alpha of the texture pixel. +#### `[multiply:` +Multiplies texture colors with the given color. +`` is specified as a `ColorString`. +Result is more like what you'd expect if you put a color on top of another +color. Meaning white surfaces get a lot of your new color while black parts don't +change very much. + Sounds ------ Only Ogg Vorbis files are supported. diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 539c29445..fbc0f1709 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -555,7 +555,11 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, // color alpha with the destination alpha. // Otherwise, any pixels that are not fully transparent get the color alpha. static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, - video::SColor color, int ratio, bool keep_alpha); + const video::SColor &color, int ratio, bool keep_alpha); + +// paint a texture using the given color +static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, + const video::SColor &color); // Apply a mask to an image static void apply_mask(video::IImage *mask, video::IImage *dst, @@ -1656,6 +1660,30 @@ bool TextureSource::generateImagePart(std::string part_of_name, << filename << "\"."; } } + /* + [multiply:color + multiplys a given color to any pixel of an image + color = color as ColorString + */ + else if (str_starts_with(part_of_name, "[multiply:")) { + Strfnd sf(part_of_name); + sf.next(":"); + std::string color_str = sf.next(":"); + + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg != NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + video::SColor color; + + if (!parseColorString(color_str, color, false)) + return false; + + apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color); + } /* [colorize:color Overlays image with given color @@ -1960,7 +1988,7 @@ static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst Apply color to destination */ static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, - video::SColor color, int ratio, bool keep_alpha) + const video::SColor &color, int ratio, bool keep_alpha) { u32 alpha = color.getAlpha(); video::SColor dst_c; @@ -1994,6 +2022,27 @@ static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, } } +/* + Apply color to destination +*/ +static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, + const video::SColor &color) +{ + video::SColor dst_c; + + for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++) + for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { + dst_c = dst->getPixel(x, y); + dst_c.set( + dst_c.getAlpha(), + (dst_c.getRed() * color.getRed()) / 255, + (dst_c.getGreen() * color.getGreen()) / 255, + (dst_c.getBlue() * color.getBlue()) / 255 + ); + dst->setPixel(x, y, dst_c); + } +} + /* Apply mask to destination */ -- cgit v1.2.3 From f2aa2c6a986dec47856c49ae5f54fbf3c688e027 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Tue, 31 Jan 2017 19:49:01 +0000 Subject: Add ItemStack key-value meta storage --- build/android/jni/Android.mk | 2 + doc/lua_api.txt | 45 ++++++++++--- src/CMakeLists.txt | 1 + src/content_cao.cpp | 2 +- src/craftdef.cpp | 3 +- src/inventory.cpp | 88 ++++--------------------- src/inventory.h | 14 ++-- src/itemstackmetadata.cpp | 43 ++++++++++++ src/itemstackmetadata.h | 35 ++++++++++ src/metadata.cpp | 2 + src/metadata.h | 38 +++++------ src/script/common/c_content.cpp | 31 +++++++-- src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_env.cpp | 2 +- src/script/lua_api/l_item.cpp | 38 +++++++++-- src/script/lua_api/l_item.h | 5 ++ src/script/lua_api/l_itemstackmeta.cpp | 115 +++++++++++++++++++++++++++++++++ src/script/lua_api/l_itemstackmeta.h | 59 +++++++++++++++++ src/script/scripting_game.cpp | 2 + src/util/serialize.cpp | 49 ++++++++++++++ src/util/serialize.h | 7 ++ 21 files changed, 459 insertions(+), 123 deletions(-) create mode 100644 src/itemstackmetadata.cpp create mode 100644 src/itemstackmetadata.h create mode 100644 src/script/lua_api/l_itemstackmeta.cpp create mode 100644 src/script/lua_api/l_itemstackmeta.h (limited to 'doc/lua_api.txt') diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index 70a3d29c0..47f37cfa5 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -164,6 +164,7 @@ LOCAL_SRC_FILES := \ jni/src/inventory.cpp \ jni/src/inventorymanager.cpp \ jni/src/itemdef.cpp \ + jni/src/itemstackmetadata.cpp \ jni/src/keycode.cpp \ jni/src/light.cpp \ jni/src/localplayer.cpp \ @@ -305,6 +306,7 @@ LOCAL_SRC_FILES += \ jni/src/script/lua_api/l_env.cpp \ jni/src/script/lua_api/l_inventory.cpp \ jni/src/script/lua_api/l_item.cpp \ + jni/src/script/lua_api/l_itemstackmeta.cpp\ jni/src/script/lua_api/l_mainmenu.cpp \ jni/src/script/lua_api/l_mapgen.cpp \ jni/src/script/lua_api/l_metadata.cpp \ diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 219882f46..2f5e3706c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1411,7 +1411,7 @@ the entity itself. * `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 +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: @@ -1427,9 +1427,9 @@ Node Metadata ------------- The instance of a node in the world normally only contains the three values mentioned in "Nodes". However, it is possible to insert extra data into a -node. It is called "node metadata"; See "`NodeMetaRef`". +node. It is called "node metadata"; See `NodeMetaRef`. -Metadata contains two things: +Node metadata contains two things: * A key-value store * An inventory @@ -1467,6 +1467,18 @@ Example stuff: } }) +Item Metadata +------------- +Item stacks can store metadata too. See `ItemStackMetaRef`. + +Item metadata only contains a key-value store. + +Example stuff: + + local meta = stack:get_meta() + meta:set_string("key", "value") + print(dump(meta:to_table())) + Formspec -------- Formspec defines a menu. Currently not much else than inventories are @@ -2774,9 +2786,8 @@ These functions return the leftover itemstack. Class reference --------------- -### `NodeMetaRef` -Node metadata: reference extra data and functionality stored in a node. -Can be gotten via `minetest.get_meta(pos)`. +### `MetaDataRef` +See `NodeMetaRef` and `ItemStackMetaRef`. #### Methods * `set_string(name, value)` @@ -2785,13 +2796,29 @@ Can be gotten via `minetest.get_meta(pos)`. * `get_int(name)` * `set_float(name, value)` * `get_float(name)` -* `get_inventory()`: returns `InvRef` -* `to_table()`: returns `nil` or `{fields = {...}, inventory = {list1 = {}, ...}}` +* `to_table()`: returns `nil` or a table with keys: + * `fields`: key-value storage + * `inventory`: `{list1 = {}, ...}}` (NodeMetaRef only) * `from_table(nil or {})` * Any non-table value will clear the metadata - * See "Node Metadata" + * See "Node Metadata" for an example * returns `true` on success +### `NodeMetaRef` +Node metadata: reference extra data and functionality stored in a node. +Can be gotten via `minetest.get_meta(pos)`. + +#### Methods +* All methods in MetaDataRef +* `get_inventory()`: returns `InvRef` + +### `ItemStackMetaRef` +ItemStack metadata: reference extra data and functionality stored in a stack. +Can be gotten via `item:get_meta()`. + +#### Methods +* All methods in MetaDataRef + ### `NodeTimerRef` Node Timers: a high resolution persistent per-node timer. Can be gotten via `minetest.get_node_timer(pos)`. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index afb591a04..30e6c85e4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -415,6 +415,7 @@ set(common_SRCS inventory.cpp inventorymanager.cpp itemdef.cpp + itemstackmetadata.cpp light.cpp log.cpp map.cpp diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 5dc3866cf..e0b1c4cd2 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -938,7 +938,7 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, if(m_prop.textures.size() >= 1){ infostream<<"textures[0]: "<idef(); - ItemStack item(m_prop.textures[0], 1, 0, "", idef); + ItemStack item(m_prop.textures[0], 1, 0, idef); m_wield_meshnode = new WieldMeshSceneNode( smgr->getRootSceneNode(), smgr, -1); diff --git a/src/craftdef.cpp b/src/craftdef.cpp index 45d3018a7..286d1eada 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -139,7 +139,7 @@ static std::vector craftGetItems( for (std::vector::size_type i = 0; i < items.size(); i++) { result.push_back(ItemStack(std::string(items[i]), (u16)1, - (u16)0, "", gamedef->getItemDefManager())); + (u16)0, gamedef->getItemDefManager())); } return result; } @@ -1126,4 +1126,3 @@ IWritableCraftDefManager* createCraftDefManager() { return new CCraftDefManager(); } - diff --git a/src/inventory.cpp b/src/inventory.cpp index cb8faecbc..6d5b49916 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -45,82 +45,16 @@ static content_t content_translate_from_19_to_internal(content_t c_from) return c_from; } -// If the string contains spaces, quotes or control characters, encodes as JSON. -// Else returns the string unmodified. -static std::string serializeJsonStringIfNeeded(const std::string &s) -{ - for(size_t i = 0; i < s.size(); ++i) - { - if(s[i] <= 0x1f || s[i] >= 0x7f || s[i] == ' ' || s[i] == '\"') - return serializeJsonString(s); - } - return s; -} - -// Parses a string serialized by serializeJsonStringIfNeeded. -static std::string deSerializeJsonStringIfNeeded(std::istream &is) -{ - std::ostringstream tmp_os; - bool expect_initial_quote = true; - bool is_json = false; - bool was_backslash = false; - for(;;) - { - char c = is.get(); - if(is.eof()) - break; - if(expect_initial_quote && c == '"') - { - tmp_os << c; - is_json = true; - } - else if(is_json) - { - tmp_os << c; - if(was_backslash) - was_backslash = false; - else if(c == '\\') - was_backslash = true; - else if(c == '"') - break; // Found end of string - } - else - { - if(c == ' ') - { - // Found end of word - is.unget(); - break; - } - else - { - tmp_os << c; - } - } - expect_initial_quote = false; - } - if(is_json) - { - std::istringstream tmp_is(tmp_os.str(), std::ios::binary); - return deSerializeJsonString(tmp_is); - } - else - return tmp_os.str(); -} - - -ItemStack::ItemStack(std::string name_, u16 count_, - u16 wear_, std::string metadata_, - IItemDefManager *itemdef) +ItemStack::ItemStack(const std::string &name_, u16 count_, + u16 wear_, IItemDefManager *itemdef) { name = itemdef->getAlias(name_); count = count_; wear = wear_; - metadata = metadata_; - if(name.empty() || count == 0) + if (name.empty() || count == 0) clear(); - else if(itemdef->get(name).type == ITEM_TOOL) + else if (itemdef->get(name).type == ITEM_TOOL) count = 1; } @@ -137,7 +71,7 @@ void ItemStack::serialize(std::ostream &os) const parts = 2; if(wear != 0) parts = 3; - if(metadata != "") + if (!metadata.empty()) parts = 4; os<= 3) os<<" "<= 4) - os<<" "<= 4) { + os << " "; + metadata.serialize(os); + } } void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) @@ -289,7 +225,7 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) wear = stoi(wear_str); // Read metadata - metadata = deSerializeJsonStringIfNeeded(is); + metadata.deSerialize(is); // In case fields are added after metadata, skip space here: //std::getline(is, tmp, ' '); @@ -335,7 +271,7 @@ ItemStack ItemStack::addItem(const ItemStack &newitem_, *this = newitem; newitem.clear(); } - // If item name or metadata differs, bail out + // If item name or metadata differs, bail out else if (name != newitem.name || metadata != newitem.metadata) { @@ -375,7 +311,7 @@ bool ItemStack::itemFits(const ItemStack &newitem_, { newitem.clear(); } - // If item name or metadata differs, bail out + // If item name or metadata differs, bail out else if (name != newitem.name || metadata != newitem.metadata) { diff --git a/src/inventory.h b/src/inventory.h index 7d7e58d61..fe1639728 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "debug.h" #include "itemdef.h" #include "irrlichttypes.h" +#include "itemstackmetadata.h" #include #include #include @@ -32,10 +33,10 @@ struct ToolCapabilities; struct ItemStack { - ItemStack(): name(""), count(0), wear(0), metadata("") {} - ItemStack(std::string name_, u16 count_, - u16 wear, std::string metadata_, - IItemDefManager *itemdef); + ItemStack(): name(""), count(0), wear(0) {} + ItemStack(const std::string &name_, u16 count_, + u16 wear, IItemDefManager *itemdef); + ~ItemStack() {} // Serialization @@ -61,7 +62,7 @@ struct ItemStack name = ""; count = 0; wear = 0; - metadata = ""; + metadata.clear(); } void add(u16 n) @@ -166,7 +167,7 @@ struct ItemStack std::string name; u16 count; u16 wear; - std::string metadata; + ItemStackMetadata metadata; }; class InventoryList @@ -313,4 +314,3 @@ private: }; #endif - diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp new file mode 100644 index 000000000..c3d602245 --- /dev/null +++ b/src/itemstackmetadata.cpp @@ -0,0 +1,43 @@ +#include "itemstackmetadata.h" +#include "util/serialize.h" +#include "util/strfnd.h" + +#define DESERIALIZE_START '\x01' +#define DESERIALIZE_KV_DELIM '\x02' +#define DESERIALIZE_PAIR_DELIM '\x03' +#define DESERIALIZE_START_STR "\x01" +#define DESERIALIZE_KV_DELIM_STR "\x02" +#define DESERIALIZE_PAIR_DELIM_STR "\x03" + +void ItemStackMetadata::serialize(std::ostream &os) const +{ + std::ostringstream os2; + os2 << DESERIALIZE_START; + for (StringMap::const_iterator + it = m_stringvars.begin(); + it != m_stringvars.end(); ++it) { + os2 << it->first << DESERIALIZE_KV_DELIM + << it->second << DESERIALIZE_PAIR_DELIM; + } + os << serializeJsonStringIfNeeded(os2.str()); +} + +void ItemStackMetadata::deSerialize(std::istream &is) +{ + std::string in = deSerializeJsonStringIfNeeded(is); + + m_stringvars.clear(); + + if (!in.empty() && in[0] == DESERIALIZE_START) { + Strfnd fnd(in); + fnd.to(1); + while (!fnd.at_end()) { + std::string name = fnd.next(DESERIALIZE_KV_DELIM_STR); + std::string var = fnd.next(DESERIALIZE_PAIR_DELIM_STR); + m_stringvars[name] = var; + } + } else { + // BACKWARDS COMPATIBILITY + m_stringvars[""] = in; + } +} diff --git a/src/itemstackmetadata.h b/src/itemstackmetadata.h new file mode 100644 index 000000000..c56c58fd2 --- /dev/null +++ b/src/itemstackmetadata.h @@ -0,0 +1,35 @@ +/* +Minetest +Copyright (C) 2010-2013 rubenwardy + +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 ITEMSTACKMETADATA_HEADER +#define ITEMSTACKMETADATA_HEADER + +#include "metadata.h" + +class Inventory; +class IItemDefManager; + +class ItemStackMetadata : public Metadata +{ +public: + void serialize(std::ostream &os) const; + void deSerialize(std::istream &is); +}; + +#endif diff --git a/src/metadata.cpp b/src/metadata.cpp index 96453d710..3cc45f919 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "log.h" #include +#include "constants.h" // MAP_BLOCKSIZE +#include /* Metadata diff --git a/src/metadata.h b/src/metadata.h index a629c0615..4bb3c2ee7 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -25,35 +25,37 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "util/string.h" -/* - NodeMetadata stores arbitary amounts of data for special blocks. - Used for furnaces, chests and signs. - - There are two interaction methods: inventory menu and text input. - Only one can be used for a single metadata, thus only inventory OR - text input should exist in a metadata. -*/ - -class Inventory; -class IItemDefManager; - class Metadata { public: - void clear(); - bool empty() const; + virtual ~Metadata() {} + + virtual void clear(); + virtual bool empty() const; - // Generic key/value store + bool operator==(const Metadata &other) const; + inline bool operator!=(const Metadata &other) const + { + return !(*this == other); + } + + // + // Key-value related + // + + size_t size() const; + bool contains(const std::string &name) const; const std::string &getString(const std::string &name, u16 recursion = 0) const; void setString(const std::string &name, const std::string &var); - // Support variable names in values - const std::string &resolveString(const std::string &str, u16 recursion = 0) const; const StringMap &getStrings() const { return m_stringvars; } -private: + // Add support for variable names in values + const std::string &resolveString(const std::string &str, u16 recursion = 0) const; +protected: StringMap m_stringvars; + }; #endif diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index ebc951295..8925b51f4 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -824,11 +824,32 @@ ItemStack read_item(lua_State* L, int index,Server* srv) std::string name = getstringfield_default(L, index, "name", ""); int count = getintfield_default(L, index, "count", 1); int wear = getintfield_default(L, index, "wear", 0); - std::string metadata = getstringfield_default(L, index, "metadata", ""); - return ItemStack(name, count, wear, metadata, idef); - } - else - { + + ItemStack istack(name, count, wear, idef); + + lua_getfield(L, index, "metadata"); + + // Support old metadata format by checking type + int fieldstable = lua_gettop(L); + if (lua_istable(L, fieldstable)) { + lua_pushnil(L); + while (lua_next(L, fieldstable) != 0) { + // key at index -2 and value at index -1 + std::string key = lua_tostring(L, -2); + size_t value_len; + const char *value_cs = lua_tolstring(L, -1, &value_len); + std::string value(value_cs, value_len); + istack.metadata.setString(name, value); + lua_pop(L, 1); // removes value, keeps key for next iteration + } + } else { + // BACKWARDS COMPATIBLITY + std::string value = getstringfield_default(L, index, "metadata", ""); + istack.metadata.setString("", value); + } + + return istack; + } else { throw LuaError("Expecting itemstack, itemstring, table or nil"); } } diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index efccce515..070234eba 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -5,6 +5,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_item.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_itemstackmeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mapgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_metadata.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_nodemeta.cpp diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 3d9db7917..2722e35a4 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -284,7 +284,7 @@ int ModApiEnvMod::l_place_node(lua_State *L) return 1; } // Create item to place - ItemStack item(ndef->get(n).name, 1, 0, "", idef); + ItemStack item(ndef->get(n).name, 1, 0, idef); // Make pointed position PointedThing pointed; pointed.type = POINTEDTHING_NODE; diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index ff0baea14..f0293bed8 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "lua_api/l_item.h" +#include "lua_api/l_itemstackmeta.h" #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" @@ -137,16 +138,28 @@ int LuaItemStack::l_set_wear(lua_State *L) return 1; } +// get_meta(self) -> string +int LuaItemStack::l_get_meta(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaItemStack *o = checkobject(L, 1); + ItemStackMetaRef::create(L, &o->m_stack); + return 1; +} + +// DEPRECATED // get_metadata(self) -> string int LuaItemStack::l_get_metadata(lua_State *L) { NO_MAP_LOCK_REQUIRED; LuaItemStack *o = checkobject(L, 1); ItemStack &item = o->m_stack; - lua_pushlstring(L, item.metadata.c_str(), item.metadata.size()); + const std::string &value = item.metadata.getString(""); + lua_pushlstring(L, value.c_str(), value.size()); return 1; } +// DEPRECATED // set_metadata(self, string) int LuaItemStack::l_set_metadata(lua_State *L) { @@ -156,7 +169,7 @@ int LuaItemStack::l_set_metadata(lua_State *L) size_t len = 0; const char *ptr = luaL_checklstring(L, 2, &len); - item.metadata.assign(ptr, len); + item.metadata.setString("", std::string(ptr, len)); lua_pushboolean(L, true); return 1; @@ -211,8 +224,24 @@ int LuaItemStack::l_to_table(lua_State *L) lua_setfield(L, -2, "count"); lua_pushinteger(L, item.wear); lua_setfield(L, -2, "wear"); - lua_pushlstring(L, item.metadata.c_str(), item.metadata.size()); - lua_setfield(L, -2, "metadata"); + + if (item.metadata.size() == 1 && item.metadata.contains("")) { + const std::string &value = item.metadata.getString(""); + lua_pushlstring(L, value.c_str(), value.size()); + lua_setfield(L, -2, "metadata"); + } else { + lua_newtable(L); + const StringMap &fields = item.metadata.getStrings(); + for (StringMap::const_iterator it = fields.begin(); + it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + lua_pushlstring(L, name.c_str(), name.size()); + lua_pushlstring(L, value.c_str(), value.size()); + lua_settable(L, -3); + } + lua_setfield(L, -2, "metadata"); + } } return 1; } @@ -443,6 +472,7 @@ const luaL_reg LuaItemStack::methods[] = { luamethod(LuaItemStack, set_count), luamethod(LuaItemStack, get_wear), luamethod(LuaItemStack, set_wear), + luamethod(LuaItemStack, get_meta), luamethod(LuaItemStack, get_metadata), luamethod(LuaItemStack, set_metadata), luamethod(LuaItemStack, clear), diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index be919b701..1ba5d79e0 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -56,9 +56,14 @@ private: // set_wear(self, number) static int l_set_wear(lua_State *L); + // get_meta(self) -> string + static int l_get_meta(lua_State *L); + + // DEPRECATED // get_metadata(self) -> string static int l_get_metadata(lua_State *L); + // DEPRECATED // set_metadata(self, string) static int l_set_metadata(lua_State *L); diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp new file mode 100644 index 000000000..304a7cdf3 --- /dev/null +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -0,0 +1,115 @@ +/* +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 "lua_api/l_itemstackmeta.h" +#include "lua_api/l_internal.h" +#include "common/c_content.h" + +/* + NodeMetaRef +*/ +ItemStackMetaRef* ItemStackMetaRef::checkobject(lua_State *L, int narg) +{ + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if (!ud) + luaL_typerror(L, narg, className); + + return *(ItemStackMetaRef**)ud; // unbox pointer +} + +Metadata* ItemStackMetaRef::getmeta(bool auto_create) +{ + return &istack->metadata; +} + +void ItemStackMetaRef::clearMeta() +{ + istack->metadata.clear(); +} + +void ItemStackMetaRef::reportMetadataChange() +{ + // TODO +} + +// Exported functions + +// garbage collector +int ItemStackMetaRef::gc_object(lua_State *L) { + ItemStackMetaRef *o = *(ItemStackMetaRef **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + +// Creates an NodeMetaRef and leaves it on top of stack +// Not callable from Lua; all references are created on the C side. +void ItemStackMetaRef::create(lua_State *L, ItemStack *istack) +{ + ItemStackMetaRef *o = new ItemStackMetaRef(istack); + //infostream<<"NodeMetaRef::create: o="< + +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_ITEMSTACKMETA_H_ +#define L_ITEMSTACKMETA_H_ + +#include "lua_api/l_base.h" +#include "lua_api/l_metadata.h" +#include "irrlichttypes_bloated.h" +#include "inventory.h" + +class ItemStackMetaRef : public MetaDataRef +{ +private: + ItemStack *istack; + + static const char className[]; + static const luaL_reg methods[]; + + static ItemStackMetaRef *checkobject(lua_State *L, int narg); + + virtual Metadata* getmeta(bool auto_create); + + virtual void clearMeta(); + + virtual void reportMetadataChange(); + + // Exported functions + + // garbage collector + static int gc_object(lua_State *L); +public: + ItemStackMetaRef(ItemStack *istack): istack(istack) {} + ~ItemStackMetaRef() {} + + // Creates an ItemStackMetaRef and leaves it on top of stack + // Not callable from Lua; all references are created on the C side. + static void create(lua_State *L, ItemStack *istack); + + static void Register(lua_State *L); +}; + +#endif diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp index e313d55f8..7becef6dc 100644 --- a/src/script/scripting_game.cpp +++ b/src/script/scripting_game.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #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" @@ -94,6 +95,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) // Register reference classes (userdata) InvRef::Register(L); + ItemStackMetaRef::Register(L); LuaAreaStore::Register(L); LuaItemStack::Register(L); LuaPerlinNoise::Register(L); diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 99cb990f1..61d369bc4 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -354,6 +354,55 @@ std::string deSerializeJsonString(std::istream &is) return os.str(); } +std::string serializeJsonStringIfNeeded(const std::string &s) +{ + for (size_t i = 0; i < s.size(); ++i) { + if (s[i] <= 0x1f || s[i] >= 0x7f || s[i] == ' ' || s[i] == '\"') + return serializeJsonString(s); + } + return s; +} + +std::string deSerializeJsonStringIfNeeded(std::istream &is) +{ + std::ostringstream tmp_os; + bool expect_initial_quote = true; + bool is_json = false; + bool was_backslash = false; + for (;;) { + char c = is.get(); + if (is.eof()) + break; + + if (expect_initial_quote && c == '"') { + tmp_os << c; + is_json = true; + } else if(is_json) { + tmp_os << c; + if (was_backslash) + was_backslash = false; + else if (c == '\\') + was_backslash = true; + else if (c == '"') + break; // Found end of string + } else { + if (c == ' ') { + // Found end of word + is.unget(); + break; + } else { + tmp_os << c; + } + } + expect_initial_quote = false; + } + if (is_json) { + std::istringstream tmp_is(tmp_os.str(), std::ios::binary); + return deSerializeJsonString(tmp_is); + } else + return tmp_os.str(); +} + //// //// String/Struct conversions //// diff --git a/src/util/serialize.h b/src/util/serialize.h index 36324a675..e22434191 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -405,6 +405,13 @@ std::string serializeJsonString(const std::string &plain); // Reads a string encoded in JSON format std::string deSerializeJsonString(std::istream &is); +// If the string contains spaces, quotes or control characters, encodes as JSON. +// Else returns the string unmodified. +std::string serializeJsonStringIfNeeded(const std::string &s); + +// Parses a string serialized by serializeJsonStringIfNeeded. +std::string deSerializeJsonStringIfNeeded(std::istream &is); + // Creates a string consisting of the hexadecimal representation of `data` std::string serializeHexString(const std::string &data, bool insert_spaces=false); -- cgit v1.2.3 From f2f9a923515386d787a245fac52f78e815b3a839 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Fri, 3 Feb 2017 22:28:09 +0000 Subject: Add per-stack descriptions using ItemStack Metadata --- doc/lua_api.txt | 4 ++++ src/guiFormSpecMenu.cpp | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 2f5e3706c..dd20ae904 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1473,6 +1473,10 @@ Item stacks can store metadata too. See `ItemStackMetaRef`. Item metadata only contains a key-value store. +Some of the values in the key-value store are handled specially: + +* `description`: Set the itemstack's description. Defaults to idef.description + Example stuff: local meta = stack:get_meta() diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index 45b0e9c11..67b3a9ad0 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -2303,7 +2303,12 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase, // Draw tooltip std::wstring tooltip_text = L""; if (hovering && !m_selected_item) { - tooltip_text = utf8_to_wide(item.getDefinition(m_client->idef()).description); + const std::string &desc = item.metadata.getString("description"); + if (desc.empty()) + tooltip_text = + utf8_to_wide(item.getDefinition(m_client->idef()).description); + else + tooltip_text = utf8_to_wide(desc); } if (tooltip_text != L"") { std::vector tt_rows = str_split(tooltip_text, L'\n'); -- cgit v1.2.3 From ef6feca501fcf0d5a1fd2021f1d4df96a4533f65 Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Wed, 8 Feb 2017 00:15:55 +0100 Subject: Add ModMetadata API (#5131) * mod can create a ModMetadata object where store its values and retrieve it. * Modmetadata object can only be fetched at mod loading * Save when modified using same time as map interval or at server stop * add helper function to get mod storage path * ModMetadata has exactly same calls than all every other Metadata --- doc/lua_api.txt | 10 ++- src/metadata.cpp | 20 +++++- src/metadata.h | 2 +- src/mods.cpp | 80 ++++++++++++++++++++- src/mods.h | 21 ++++++ src/remoteplayer.cpp | 2 +- src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_storage.cpp | 143 ++++++++++++++++++++++++++++++++++++++ src/script/lua_api/l_storage.h | 62 +++++++++++++++++ src/script/scripting_game.cpp | 3 + src/server.cpp | 43 +++++++++++- src/server.h | 9 ++- 12 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 src/script/lua_api/l_storage.cpp create mode 100644 src/script/lua_api/l_storage.h (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index dd20ae904..4774e8a5a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2629,6 +2629,11 @@ These functions return the leftover itemstack. * `HTTPApiTable.fetch_async_get(handle)`: returns HTTPRequestResult * Return response data for given asynchronous HTTP request +### Storage API: +* `minetest.get_mod_storage()`: + * returns reference to mod private `StorageRef` + * must be called during mod load time + ### Misc. * `minetest.get_connected_players()`: returns list of `ObjectRefs` * `minetest.player_exists(name)`: boolean, whether player exists (regardless of online status) @@ -2791,7 +2796,7 @@ Class reference --------------- ### `MetaDataRef` -See `NodeMetaRef` and `ItemStackMetaRef`. +See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`. #### Methods * `set_string(name, value)` @@ -2845,6 +2850,9 @@ Can be gotten via `minetest.get_node_timer(pos)`. * `is_started()`: returns boolean state of timer * returns `true` if timer is started, otherwise `false` +### `StorageRef` +This is basically a reference to a C++ `ModMetadata` + ### `ObjectRef` Moving things in the game are generally these. diff --git a/src/metadata.cpp b/src/metadata.cpp index 3cc45f919..2ce9af5af 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -76,13 +76,27 @@ const std::string &Metadata::getString(const std::string &name, return resolveString(it->second, recursion); } -void Metadata::setString(const std::string &name, const std::string &var) +/** + * Sets var to name key in the metadata storage + * + * @param name + * @param var + * @return true if key-value pair is created or changed + */ +bool Metadata::setString(const std::string &name, const std::string &var) { if (var.empty()) { m_stringvars.erase(name); - } else { - m_stringvars[name] = var; + return true; + } + + StringMap::iterator it = m_stringvars.find(name); + if (it != m_stringvars.end() && it->second == var) { + return false; } + + m_stringvars[name] = var; + return true; } const std::string &Metadata::resolveString(const std::string &str, diff --git a/src/metadata.h b/src/metadata.h index 4bb3c2ee7..a8270b4c4 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -46,7 +46,7 @@ public: size_t size() const; bool contains(const std::string &name) const; const std::string &getString(const std::string &name, u16 recursion = 0) const; - void setString(const std::string &name, const std::string &var); + virtual bool setString(const std::string &name, const std::string &var); const StringMap &getStrings() const { return m_stringvars; diff --git a/src/mods.cpp b/src/mods.cpp index 1b1bdb07b..bae9a42d3 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -21,13 +21,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "mods.h" #include "filesys.h" -#include "util/strfnd.h" #include "log.h" #include "subgame.h" #include "settings.h" -#include "util/strfnd.h" #include "convert_json.h" -#include "exceptions.h" static bool parseDependsLine(std::istream &is, std::string &dep, std::set &symbols) @@ -356,3 +353,80 @@ Json::Value getModstoreUrl(std::string url) } #endif + +ModMetadata::ModMetadata(const std::string &mod_name): + m_mod_name(mod_name), + m_modified(false) +{ + m_stringvars.clear(); +} + +void ModMetadata::clear() +{ + Metadata::clear(); + m_modified = true; +} + +bool ModMetadata::save(const std::string &root_path) +{ + Json::Value json; + for (StringMap::const_iterator it = m_stringvars.begin(); + it != m_stringvars.end(); ++it) { + json[it->first] = it->second; + } + + if (!fs::PathExists(root_path)) { + if (!fs::CreateAllDirs(root_path)) { + errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" + << root_path << "' tree cannot be created." << std::endl; + return false; + } + } else if (!fs::IsDir(root_path)) { + errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" + << root_path << "' is not a directory." << std::endl; + return false; + } + + bool w_ok = fs::safeWriteToFile(root_path + DIR_DELIM + m_mod_name, + Json::FastWriter().write(json)); + + if (w_ok) { + m_modified = false; + } else { + errorstream << "ModMetadata[" << m_mod_name << "]: failed write file." << std::endl; + } + return w_ok; +} + +bool ModMetadata::load(const std::string &root_path) +{ + m_stringvars.clear(); + + std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(), std::ios_base::binary); + if (!is.good()) { + return false; + } + + Json::Reader reader; + Json::Value root; + if (!reader.parse(is, root)) { + errorstream << "ModMetadata[" << m_mod_name << "]: failed read data " + "(Json decoding failure)." << std::endl; + return false; + } + + const Json::Value::Members attr_list = root.getMemberNames(); + for (Json::Value::Members::const_iterator it = attr_list.begin(); + it != attr_list.end(); ++it) { + Json::Value attr_value = root[*it]; + m_stringvars[*it] = attr_value.asString(); + } + + return true; +} + +bool ModMetadata::setString(const std::string &name, const std::string &var) +{ + m_modified = Metadata::setString(name, var); + return m_modified; +} diff --git a/src/mods.h b/src/mods.h index af7777d18..61af5e5d1 100644 --- a/src/mods.h +++ b/src/mods.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "config.h" +#include "metadata.h" #define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" @@ -205,4 +206,24 @@ struct ModStoreModDetails { bool valid; }; +class ModMetadata: public Metadata +{ +public: + ModMetadata(const std::string &mod_name); + ~ModMetadata() {} + + virtual void clear(); + + bool save(const std::string &root_path); + bool load(const std::string &root_path); + + bool isModified() const { return m_modified; } + const std::string &getModName() const { return m_mod_name; } + + virtual bool setString(const std::string &name, const std::string &var); +private: + std::string m_mod_name; + bool m_modified; +}; + #endif diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 6853ad6d9..0a4591410 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -174,7 +174,7 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, const Json::Value::Members attr_list = attr_root.getMemberNames(); for (Json::Value::Members::const_iterator it = attr_list.begin(); - it != attr_list.end(); ++it) { + it != attr_list.end(); ++it) { Json::Value attr_value = attr_root[*it]; sao->setExtendedAttribute(*it, attr_value.asString()); } diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 070234eba..e82560696 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -15,6 +15,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_particles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_rollback.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_server.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_util.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_vmanip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_settings.cpp diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp new file mode 100644 index 000000000..42928255f --- /dev/null +++ b/src/script/lua_api/l_storage.cpp @@ -0,0 +1,143 @@ +/* +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 "lua_api/l_storage.h" +#include "l_internal.h" +#include "mods.h" +#include "server.h" + +int ModApiStorage::l_get_mod_storage(lua_State *L) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) { + return 0; + } + + std::string mod_name = lua_tostring(L, -1); + + ModMetadata *store = new ModMetadata(mod_name); + // For server side + if (Server *server = getServer(L)) { + store->load(server->getModStoragePath()); + server->registerModStorage(store); + } else { + assert(false); // this should not happen + } + + StorageRef::create(L, store); + int object = lua_gettop(L); + + lua_pushvalue(L, object); + return 1; +} + +void ModApiStorage::Initialize(lua_State *L, int top) +{ + API_FCT(get_mod_storage); +} + +StorageRef::StorageRef(ModMetadata *object): + m_object(object) +{ +} + +void StorageRef::create(lua_State *L, ModMetadata *object) +{ + StorageRef *o = new StorageRef(object); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + +int StorageRef::gc_object(lua_State *L) +{ + StorageRef *o = *(StorageRef **)(lua_touserdata(L, 1)); + // Server side + if (Server *server = getServer(L)) + server->unregisterModStorage(getobject(o)->getModName()); + delete o; + return 0; +} + +void StorageRef::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from Lua getmetatable() + + lua_pushliteral(L, "metadata_class"); + lua_pushlstring(L, className, strlen(className)); + lua_settable(L, metatable); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); // drop metatable + + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable +} + +StorageRef* StorageRef::checkobject(lua_State *L, int narg) +{ + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if (!ud) luaL_typerror(L, narg, className); + return *(StorageRef**)ud; // unbox pointer +} + +ModMetadata* StorageRef::getobject(StorageRef *ref) +{ + ModMetadata *co = ref->m_object; + return co; +} + +Metadata* StorageRef::getmeta(bool auto_create) +{ + return m_object; +} + +void StorageRef::clearMeta() +{ + m_object->clear(); +} + +const char StorageRef::className[] = "StorageRef"; +const luaL_reg StorageRef::methods[] = { + luamethod(MetaDataRef, get_string), + luamethod(MetaDataRef, set_string), + luamethod(MetaDataRef, get_int), + luamethod(MetaDataRef, set_int), + luamethod(MetaDataRef, get_float), + luamethod(MetaDataRef, set_float), + luamethod(MetaDataRef, to_table), + luamethod(MetaDataRef, from_table), + {0,0} +}; diff --git a/src/script/lua_api/l_storage.h b/src/script/lua_api/l_storage.h new file mode 100644 index 000000000..fde2828ad --- /dev/null +++ b/src/script/lua_api/l_storage.h @@ -0,0 +1,62 @@ +/* +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_STORAGE_H__ +#define __L_STORAGE_H__ + +#include "lua_api/l_base.h" +#include "l_metadata.h" + +class ModMetadata; + +class ModApiStorage: public ModApiBase +{ +protected: + static int l_get_mod_storage(lua_State *L); +public: + static void Initialize(lua_State *L, int top); + +}; + +class StorageRef: public MetaDataRef +{ +private: + ModMetadata *m_object; + + static const char className[]; + static const luaL_reg methods[]; + + virtual Metadata* getmeta(bool auto_create); + virtual void clearMeta(); + + // garbage collector + static int gc_object(lua_State *L); +public: + StorageRef(ModMetadata *object); + ~StorageRef() {} + + static void Register(lua_State *L); + static void create(lua_State *L, ModMetadata *object); + + static StorageRef *checkobject(lua_State *L, int narg); + static ModMetadata* getobject(StorageRef *ref); +}; + +#endif /* __L_STORAGE_H__ */ diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp index 7becef6dc..4da752263 100644 --- a/src/script/scripting_game.cpp +++ b/src/script/scripting_game.cpp @@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #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" @@ -92,6 +93,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) ModApiServer::Initialize(L, top); ModApiUtil::Initialize(L, top); ModApiHttp::Initialize(L, top); + ModApiStorage::Initialize(L, top); // Register reference classes (userdata) InvRef::Register(L); @@ -108,6 +110,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) NodeTimerRef::Register(L); ObjectRef::Register(L); LuaSettings::Register(L); + StorageRef::Register(L); } void log_deprecated(const std::string &message) diff --git a/src/server.cpp b/src/server.cpp index e5714ac03..8b9f46f85 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -178,8 +178,8 @@ Server::Server( m_admin_chat(iface), m_ignore_map_edit_events(false), m_ignore_map_edit_events_peer_id(0), - m_next_sound_id(0) - + m_next_sound_id(0), + m_mod_storage_save_timer(10.0f) { m_liquid_transform_timer = 0.0; m_liquid_transform_every = 1.0; @@ -788,6 +788,18 @@ void Server::AsyncRunStep(bool initial_step) << "packet size is " << pktSize << std::endl; } m_clients.unlock(); + + m_mod_storage_save_timer -= dtime; + if (m_mod_storage_save_timer <= 0.0f) { + infostream << "Saving registered mod storages." << std::endl; + m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); + for (UNORDERED_MAP::const_iterator + it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { + if (it->second->isModified()) { + it->second->save(getModStoragePath()); + } + } + } } /* @@ -3404,6 +3416,11 @@ std::string Server::getBuiltinLuaPath() return porting::path_share + DIR_DELIM + "builtin"; } +std::string Server::getModStoragePath() const +{ + return m_path_world + DIR_DELIM + "mod_storage"; +} + v3f Server::findSpawnPos() { ServerMap &map = m_env->getServerMap(); @@ -3525,6 +3542,28 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version return playersao; } +bool Server::registerModStorage(ModMetadata *storage) +{ + if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { + errorstream << "Unable to register same mod storage twice. Storage name: " + << storage->getModName() << std::endl; + return false; + } + + m_mod_storages[storage->getModName()] = storage; + return true; +} + +void Server::unregisterModStorage(const std::string &name) +{ + UNORDERED_MAP::const_iterator it = m_mod_storages.find(name); + if (it != m_mod_storages.end()) { + // Save unconditionaly on unregistration + it->second->save(getModStoragePath()); + m_mod_storages.erase(name); + } +} + void dedicated_server_loop(Server &server, bool &kill) { DSTACK(FUNCTION_NAME); diff --git a/src/server.h b/src/server.h index 8f553ce38..3eee67b78 100644 --- a/src/server.h +++ b/src/server.h @@ -299,7 +299,8 @@ public: const ModSpec* getModSpec(const std::string &modname) const; void getModNames(std::vector &modlist); std::string getBuiltinLuaPath(); - inline std::string getWorldPath() const { return m_path_world; } + inline const std::string &getWorldPath() const { return m_path_world; } + std::string getModStoragePath() const; inline bool isSingleplayer() { return m_simple_singleplayer_mode; } @@ -360,6 +361,9 @@ public: void SendInventory(PlayerSAO* playerSAO); void SendMovePlayer(u16 peer_id); + bool registerModStorage(ModMetadata *storage); + void unregisterModStorage(const std::string &name); + // Bind address Address m_bind_addr; @@ -650,6 +654,9 @@ private: // value = "" (visible to all players) or player name std::map m_detached_inventories_player; + UNORDERED_MAP m_mod_storages; + float m_mod_storage_save_timer; + DISABLE_CLASS_COPY(Server); }; -- cgit v1.2.3 From 74b670a7930736fb4f43dcb5c9e0a366bf23cad4 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Fri, 10 Feb 2017 06:59:38 +0000 Subject: Correct lua_api.txt docs related to meta (#5198) --- doc/lua_api.txt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 4774e8a5a..0245ee7bd 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2815,7 +2815,7 @@ See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`. ### `NodeMetaRef` Node metadata: reference extra data and functionality stored in a node. -Can be gotten via `minetest.get_meta(pos)`. +Can be obtained via `minetest.get_meta(pos)`. #### Methods * All methods in MetaDataRef @@ -2823,7 +2823,14 @@ Can be gotten via `minetest.get_meta(pos)`. ### `ItemStackMetaRef` ItemStack metadata: reference extra data and functionality stored in a stack. -Can be gotten via `item:get_meta()`. +Can be obtained via `item:get_meta()`. + +#### Methods +* All methods in MetaDataRef + +### `StorageRef` +Mod metadata: per mod metadata, saved automatically. +Can be obtained via `minetest.get_mod_storage()` during load time. #### Methods * All methods in MetaDataRef @@ -2850,9 +2857,6 @@ Can be gotten via `minetest.get_node_timer(pos)`. * `is_started()`: returns boolean state of timer * returns `true` if timer is started, otherwise `false` -### `StorageRef` -This is basically a reference to a C++ `ModMetadata` - ### `ObjectRef` Moving things in the game are generally these. @@ -3100,8 +3104,9 @@ an itemstring, a table or `nil`. * `set_count(count)`: Returns boolean whether item was cleared * `get_wear()`: Returns tool wear (`0`-`65535`), `0` for non-tools. * `set_wear(wear)`: Returns boolean whether item was cleared -* `get_metadata()`: Returns metadata (a string attached to an item stack). -* `set_metadata(metadata)`: Returns true. +* `get_meta()`: Returns ItemStackMetaRef. See section for more details +* `get_metadata()`: (DEPRECATED) Returns metadata (a string attached to an item stack). +* `set_metadata(metadata)`: (DEPRECATED) Returns true. * `clear()`: removes all items from the stack, making it empty. * `replace(item)`: replace the contents of this stack. * `item` can also be an itemstring or table. -- cgit v1.2.3 From 01b2d2c66c9095341554400ba67505b137ec1d86 Mon Sep 17 00:00:00 2001 From: red-001 Date: Sun, 19 Feb 2017 16:11:53 +0000 Subject: Fix the documentation for `minetest.is_yes` (#5276) --- doc/lua_api.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 0245ee7bd..9c96c41db 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1914,7 +1914,7 @@ Helper functions * `minetest.formspec_escape(string)`: returns a string * escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs * `minetest.is_yes(arg)` - * returns whether `arg` can be interpreted as yes + * returns true if passed 'y', 'yes', 'true' or a number that isn't zero. * `minetest.get_us_time()` * returns time with microsecond precision. May not return wall time. * `table.copy(table)`: returns a table -- cgit v1.2.3 From 6de83a2756316653ed67b23bbeb0fb43aa2a68c6 Mon Sep 17 00:00:00 2001 From: adelcoding1 Date: Sat, 4 Mar 2017 10:46:55 +0100 Subject: FormSpec: Add position and anchor elements (#5284) --- doc/lua_api.txt | 10 +++++ src/guiFormSpecMenu.cpp | 101 ++++++++++++++++++++++++++++++++++++++++++++---- src/guiFormSpecMenu.h | 6 +++ 3 files changed, 109 insertions(+), 8 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9c96c41db..7b956dc74 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1522,6 +1522,16 @@ examples. * `fixed_size`: `true`/`false` (optional) * deprecated: `invsize[,;]` +#### `position[,]` +* Define the position of the formspec +* A value between 0.0 and 1.0 represents a position inside the screen +* The default value is the center of the screen (0.5, 0.5) + +#### `anchor[,]` +* Define the anchor of the formspec +* A value between 0.0 and 1.0 represents an anchor inside the formspec +* The default value is the center of the formspec (0.5, 0.5) + #### `container[,]` * Start of a container block, moves all physical elements in the container by (X, Y) * Must have matching container_end diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index 67b3a9ad0..ae3fad7c6 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -1706,6 +1706,74 @@ bool GUIFormSpecMenu::parseSizeDirect(parserData* data, std::string element) return true; } +bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &element) +{ + if (element.empty()) + return false; + + std::vector parts = split(element, '['); + + if (parts.size() != 2) + return false; + + std::string type = trim(parts[0]); + std::string description = trim(parts[1]); + + if (type != "position") + return false; + + parsePosition(data, description); + + return true; +} + +void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element) +{ + std::vector parts = split(element, ','); + + if (parts.size() == 2) { + data->offset.X = stof(parts[0]); + data->offset.Y = stof(parts[1]); + return; + } + + errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl; +} + +bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element) +{ + if (element.empty()) + return false; + + std::vector parts = split(element, '['); + + if (parts.size() != 2) + return false; + + std::string type = trim(parts[0]); + std::string description = trim(parts[1]); + + if (type != "anchor") + return false; + + parseAnchor(data, description); + + return true; +} + +void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element) +{ + std::vector parts = split(element, ','); + + if (parts.size() == 2) { + data->anchor.X = stof(parts[0]); + data->anchor.Y = stof(parts[1]); + return; + } + + errorstream << "Invalid anchor element (" << parts.size() << "): '" << element << "'" << std::endl; +} + void GUIFormSpecMenu::parseElement(parserData* data, std::string element) { //some prechecks @@ -1917,6 +1985,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.size= v2s32(100,100); mydata.screensize = screensize; + mydata.offset = v2f32(0.5f, 0.5f); + mydata.anchor = v2f32(0.5f, 0.5f); // Base position of contents of form mydata.basepos = getBasePos(); @@ -1983,6 +2053,21 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } + /* "position" element is always after "size" element if it used */ + for (; i< elements.size(); i++) { + if (!parsePositionDirect(&mydata, elements[i])) { + break; + } + } + + /* "anchor" element is always after "position" (or "size" element) if it used */ + for (; i< elements.size(); i++) { + if (!parseAnchorDirect(&mydata, elements[i])) { + break; + } + } + + if (mydata.explicit_size) { // compute scaling for specified form size if (m_lock) { @@ -2066,10 +2151,10 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0 ); DesiredRect = mydata.rect = core::rect( - mydata.screensize.X/2 - mydata.size.X/2 + offset.X, - mydata.screensize.Y/2 - mydata.size.Y/2 + offset.Y, - mydata.screensize.X/2 + mydata.size.X/2 + offset.X, - mydata.screensize.Y/2 + mydata.size.Y/2 + offset.Y + (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X, + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y, + (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X, + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y ); } else { // Non-size[] form must consist only of text fields and @@ -2078,10 +2163,10 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_font = g_fontengine->getFont(); m_btn_height = font_line_height(m_font) * 0.875; DesiredRect = core::rect( - mydata.screensize.X/2 - 580/2, - mydata.screensize.Y/2 - 300/2, - mydata.screensize.X/2 + 580/2, - mydata.screensize.Y/2 + 300/2 + (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0), + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0), + (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0), + (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0) ); } recalculateAbsolutePosition(false); diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index 2ab7db4f1..bbab9c164 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -447,6 +447,8 @@ private: bool explicit_size; v2f invsize; v2s32 size; + v2f32 offset; + v2f32 anchor; core::rect rect; v2s32 basepos; v2u32 screensize; @@ -502,6 +504,10 @@ private: bool parseVersionDirect(std::string data); bool parseSizeDirect(parserData* data, std::string element); void parseScrollBar(parserData* data, std::string element); + bool parsePositionDirect(parserData *data, const std::string &element); + void parsePosition(parserData *data, const std::string &element); + bool parseAnchorDirect(parserData *data, const std::string &element); + void parseAnchor(parserData *data, const std::string &element); void tryClose(); -- cgit v1.2.3 From c9ac722ea9ab3783bf59e7cb991bfb3a91211490 Mon Sep 17 00:00:00 2001 From: zaoqi Date: Sun, 5 Mar 2017 01:36:37 +0800 Subject: Add minetest.spawn_falling_node(pos) (#5339) * Add minetest.spawn_falling_node(pos) * lua_api.txt: Add minetest.spawn_falling_node(pos) * Update minetest.spawn_falling_node(pos) --- builtin/game/falling.lua | 14 ++++++++++++++ doc/lua_api.txt | 3 +++ 2 files changed, 17 insertions(+) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 5ef5289be..764909361 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -118,6 +118,20 @@ local function spawn_falling_node(p, node) end end +function core.spawn_falling_node(pos) + local node = core.get_node(pos) + if node.name == "air" or node.name == "ignore" then + return false + end + local obj = core.add_entity(pos, "__builtin:falling_node") + if obj then + obj:get_luaentity():set_node(node) + core.remove_node(pos) + return true + end + return false +end + local function drop_attached_node(p) local nn = core.get_node(p).name core.remove_node(p) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 7b956dc74..23aac90d9 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2219,6 +2219,9 @@ and `minetest.auth_reload` call the authetification handler. * Returns `true` if successful, `false` on failure (e.g. protected location) * `minetest.punch_node(pos)` * Punch node with the same effects that a player would cause +* `minetest.spawn_falling_node(pos)` + * Change node into falling node + * Returns `true` if successful, `false` on failure * `minetest.find_nodes_with_meta(pos1, pos2)` * Get a table of positions of nodes that have metadata within a region {pos1, pos2} -- cgit v1.2.3 From ab371cc93491baf0973ecc94b96c3a1fdb4abfd5 Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Sat, 10 Dec 2016 19:02:44 +0100 Subject: Light calculation: New bulk node lighting code This commit introduces a new bulk node lighting algorithm to minimize lighting bugs during l-system tree generation, schematic placement and non-mapgen-object lua voxelmanip light calculation. If the block above the changed area is not loaded, it gets loaded to avoid lighting bugs. Light is updated as soon as write_to_map is called on a voxel manipulator, therefore update_map does nothing. --- doc/lua_api.txt | 9 +- src/map.cpp | 565 ---------------------------------------- src/map.h | 16 -- src/mg_schematic.cpp | 10 +- src/mg_schematic.h | 3 +- src/script/lua_api/l_mapgen.cpp | 4 +- src/script/lua_api/l_vmanip.cpp | 46 ++-- src/treegen.cpp | 7 +- src/voxelalgorithms.cpp | 371 +++++++++++++++++++++++++- src/voxelalgorithms.h | 13 + 10 files changed, 408 insertions(+), 636 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 23aac90d9..484a5848c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3282,9 +3282,6 @@ format as produced by get_data() et al. and is *not required* to be a table retr Once the internal VoxelManip state has been modified to your liking, the changes can be committed back to the map by calling `VoxelManip:write_to_map()`. -Finally, a call to `VoxelManip:update_map()` is required to re-calculate lighting and set the blocks -as being modified so that connected clients are sent the updated parts of map. - ##### Flat array format Let @@ -3349,8 +3346,6 @@ but with a few differences: will also update the Mapgen VoxelManip object's internal state active on the current thread. * After modifying the Mapgen VoxelManip object's internal buffer, it may be necessary to update lighting information using either: `VoxelManip:calc_lighting()` or `VoxelManip:set_lighting()`. -* `VoxelManip:update_map()` does not need to be called after `write_to_map()`. The map update is performed - automatically after all on_generated callbacks have been run for that generated block. ##### Other API functions operating on a VoxelManip If any VoxelManip contents were set to a liquid node, `VoxelManip:update_liquids()` must be called @@ -3393,9 +3388,7 @@ will place the schematic inside of the VoxelManip. * returns raw node data in the form of an array of node content IDs * if the param `buffer` is present, this table will be used to store the result instead * `set_data(data)`: Sets the data contents of the `VoxelManip` object -* `update_map()`: Update map after writing chunk back to map. - * To be used only by `VoxelManip` objects created by the mod itself; - not a `VoxelManip` that was retrieved from `minetest.get_mapgen_object` +* `update_map()`: Does nothing, kept for compatibility. * `set_lighting(light, [p1, p2])`: Set the lighting within the `VoxelManip` to a uniform value * `light` is a table, `{day=<0...15>, night=<0...15>}` * To be used only by a `VoxelManip` object from `minetest.get_mapgen_object` diff --git a/src/map.cpp b/src/map.cpp index a415bda96..a1502befa 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -235,571 +235,6 @@ void Map::setNode(v3s16 p, MapNode & n) block->setNodeNoCheck(relpos, n); } -/* - Goes recursively through the neighbours of the node. - - Alters only transparent nodes. - - If the lighting of the neighbour is lower than the lighting of - the node was (before changing it to 0 at the step before), the - lighting of the neighbour is set to 0 and then the same stuff - repeats for the neighbour. - - The ending nodes of the routine are stored in light_sources. - This is useful when a light is removed. In such case, this - routine can be called for the light node and then again for - light_sources to re-light the area without the removed light. - - values of from_nodes are lighting values. -*/ -void Map::unspreadLight(enum LightBank bank, - std::map & from_nodes, - std::set & light_sources, - std::map & modified_blocks) -{ - v3s16 dirs[6] = { - v3s16(0,0,1), // back - v3s16(0,1,0), // top - v3s16(1,0,0), // right - v3s16(0,0,-1), // front - v3s16(0,-1,0), // bottom - v3s16(-1,0,0), // left - }; - - if(from_nodes.empty()) - return; - - u32 blockchangecount = 0; - - std::map unlighted_nodes; - - /* - Initialize block cache - */ - v3s16 blockpos_last; - MapBlock *block = NULL; - // Cache this a bit, too - bool block_checked_in_modified = false; - - for(std::map::iterator j = from_nodes.begin(); - j != from_nodes.end(); ++j) - { - v3s16 pos = j->first; - v3s16 blockpos = getNodeBlockPos(pos); - - // Only fetch a new block if the block position has changed - try{ - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) - { - continue; - } - - if(block->isDummy()) - continue; - - // Calculate relative position in block - //v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE; - - // Get node straight from the block - //MapNode n = block->getNode(relpos); - - u8 oldlight = j->second; - - // Loop through 6 neighbors - for(u16 i=0; i<6; i++) - { - // Get the position of the neighbor node - v3s16 n2pos = pos + dirs[i]; - - // Get the block where the node is located - v3s16 blockpos, relpos; - getNodeBlockPosWithOffset(n2pos, blockpos, relpos); - - // Only fetch a new block if the block position has changed - try { - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) { - continue; - } - - // Get node straight from the block - bool is_valid_position; - MapNode n2 = block->getNode(relpos, &is_valid_position); - if (!is_valid_position) - continue; - - bool changed = false; - - //TODO: Optimize output by optimizing light_sources? - - /* - If the neighbor is dimmer than what was specified - as oldlight (the light of the previous node) - */ - if(n2.getLight(bank, m_nodedef) < oldlight) - { - /* - And the neighbor is transparent and it has some light - */ - if(m_nodedef->get(n2).light_propagates - && n2.getLight(bank, m_nodedef) != 0) - { - /* - Set light to 0 and add to queue - */ - - u8 current_light = n2.getLight(bank, m_nodedef); - n2.setLight(bank, 0, m_nodedef); - block->setNode(relpos, n2); - - unlighted_nodes[n2pos] = current_light; - changed = true; - - /* - Remove from light_sources if it is there - NOTE: This doesn't happen nearly at all - */ - /*if(light_sources.find(n2pos)) - { - infostream<<"Removed from light_sources"< & from_nodes, - std::map & modified_blocks) -{ - const v3s16 dirs[6] = { - v3s16(0,0,1), // back - v3s16(0,1,0), // top - v3s16(1,0,0), // right - v3s16(0,0,-1), // front - v3s16(0,-1,0), // bottom - v3s16(-1,0,0), // left - }; - - if(from_nodes.empty()) - return; - - u32 blockchangecount = 0; - - std::set lighted_nodes; - - /* - Initialize block cache - */ - v3s16 blockpos_last; - MapBlock *block = NULL; - // Cache this a bit, too - bool block_checked_in_modified = false; - - for(std::set::iterator j = from_nodes.begin(); - j != from_nodes.end(); ++j) - { - v3s16 pos = *j; - v3s16 blockpos, relpos; - - getNodeBlockPosWithOffset(pos, blockpos, relpos); - - // Only fetch a new block if the block position has changed - try { - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) { - continue; - } - - if(block->isDummy()) - continue; - - // Get node straight from the block - bool is_valid_position; - MapNode n = block->getNode(relpos, &is_valid_position); - - u8 oldlight = is_valid_position ? n.getLight(bank, m_nodedef) : 0; - u8 newlight = diminish_light(oldlight); - - // Loop through 6 neighbors - for(u16 i=0; i<6; i++){ - // Get the position of the neighbor node - v3s16 n2pos = pos + dirs[i]; - - // Get the block where the node is located - v3s16 blockpos, relpos; - getNodeBlockPosWithOffset(n2pos, blockpos, relpos); - - // Only fetch a new block if the block position has changed - try { - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) { - continue; - } - - // Get node straight from the block - MapNode n2 = block->getNode(relpos, &is_valid_position); - if (!is_valid_position) - continue; - - bool changed = false; - /* - If the neighbor is brighter than the current node, - add to list (it will light up this node on its turn) - */ - if(n2.getLight(bank, m_nodedef) > undiminish_light(oldlight)) - { - lighted_nodes.insert(n2pos); - changed = true; - } - /* - If the neighbor is dimmer than how much light this node - would spread on it, add to list - */ - if(n2.getLight(bank, m_nodedef) < newlight) - { - if(m_nodedef->get(n2).light_propagates) - { - n2.setLight(bank, newlight, m_nodedef); - block->setNode(relpos, n2); - lighted_nodes.insert(n2pos); - changed = true; - } - } - - // Add to modified_blocks - if(changed == true && block_checked_in_modified == false) - { - // If the block is not found in modified_blocks, add. - if(modified_blocks.find(blockpos) == modified_blocks.end()) - { - modified_blocks[blockpos] = block; - } - block_checked_in_modified = true; - } - } - } - - /*infostream<<"spreadLight(): Changed block " - < & a_blocks, - std::map & modified_blocks) -{ - /*m_dout<<"Map::updateLighting(): " - < blocks_to_update; - - std::set light_sources; - - std::map unlight_from; - - int num_bottom_invalid = 0; - - { - //TimeTaker t("first stuff"); - - for(std::map::iterator i = a_blocks.begin(); - i != a_blocks.end(); ++i) - { - MapBlock *block = i->second; - - for(;;) - { - // Don't bother with dummy blocks. - if(block->isDummy()) - break; - - v3s16 pos = block->getPos(); - v3s16 posnodes = block->getPosRelative(); - modified_blocks[pos] = block; - //blocks_to_update[pos] = block; - - /* - Clear all light from block - */ - for(s16 z=0; zgetNode(p, &is_valid_position); - if (!is_valid_position) { - /* This would happen when dealing with a - dummy block. - */ - infostream<<"updateLighting(): InvalidPositionException" - <setNode(p, n); - - // If node sources light, add to list - u8 source = m_nodedef->get(n).light_source; - if(source != 0) - light_sources.insert(p + posnodes); - - // Collect borders for unlighting - if((x==0 || x == MAP_BLOCKSIZE-1 - || y==0 || y == MAP_BLOCKSIZE-1 - || z==0 || z == MAP_BLOCKSIZE-1) - && oldlight != 0) - { - v3s16 p_map = p + posnodes; - unlight_from[p_map] = oldlight; - } - - - } - - if(bank == LIGHTBANK_DAY) - { - bool bottom_valid = block->propagateSunlight(light_sources); - - if(!bottom_valid) - num_bottom_invalid++; - - // If bottom is valid, we're done. - if(bottom_valid) - break; - } - else if(bank == LIGHTBANK_NIGHT) - { - // For night lighting, sunlight is not propagated - break; - } - else - { - assert("Invalid lighting bank" == NULL); - } - - /*infostream<<"Bottom for sunlight-propagated block (" - <get("")) - { - core::map::Iterator i; - i = blocks_to_update.getIterator(); - for(; i.atEnd() == false; i++) - { - MapBlock *block = i.getNode()->getValue(); - v3s16 p = block->getPos(); - block->setLightingExpired(false); - } - return; - } -#endif - -#if 1 - { - //TimeTaker timer("unspreadLight"); - unspreadLight(bank, unlight_from, light_sources, modified_blocks); - } - - /*if(debug) - { - u32 diff = modified_blocks.size() - count_was; - count_was = modified_blocks.size(); - infostream<<"unspreadLight modified "<::Iterator i; - i = blocks_to_update.getIterator(); - for(; i.atEnd() == false; i++) - { - MapBlock *block = i.getNode()->getValue(); - v3s16 p = block->getPos(); - - // Add all surrounding blocks - vmanip.initialEmerge(p - v3s16(1,1,1), p + v3s16(1,1,1)); - - /* - Add all surrounding blocks that have up-to-date lighting - NOTE: This doesn't quite do the job (not everything - appropriate is lighted) - */ - /*for(s16 z=-1; z<=1; z++) - for(s16 y=-1; y<=1; y++) - for(s16 x=-1; x<=1; x++) - { - v3s16 p2 = p + v3s16(x,y,z); - MapBlock *block = getBlockNoCreateNoEx(p2); - if(block == NULL) - continue; - if(block->isDummy()) - continue; - if(block->getLightingExpired()) - continue; - vmanip.initialEmerge(p2, p2); - }*/ - - // Lighting of block will be updated completely - block->setLightingExpired(false); - } - } - - { - //TimeTaker timer("unSpreadLight"); - vmanip.unspreadLight(bank, unlight_from, light_sources, nodemgr); - } - { - //TimeTaker timer("spreadLight"); - vmanip.spreadLight(bank, light_sources, nodemgr); - } - { - //TimeTaker timer("blitBack"); - vmanip.blitBack(modified_blocks); - } - /*infostream<<"emerge_time="< & a_blocks, - std::map & modified_blocks) -{ - updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks); - updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks); - - /* - Update information about whether day and night light differ - */ - for(std::map::iterator - i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) - { - MapBlock *block = i->second; - block->expireDayNightDiff(); - } -} - void Map::addNodeAndUpdate(v3s16 p, MapNode n, std::map &modified_blocks, bool remove_metadata) diff --git a/src/map.h b/src/map.h index 19c94ee80..c4181a49f 100644 --- a/src/map.h +++ b/src/map.h @@ -208,22 +208,6 @@ public: // position is valid, otherwise false MapNode getNodeNoEx(v3s16 p, bool *is_valid_position = NULL); - void unspreadLight(enum LightBank bank, - std::map & from_nodes, - std::set & light_sources, - std::map & modified_blocks); - - void spreadLight(enum LightBank bank, - std::set & from_nodes, - std::map & modified_blocks); - - void updateLighting(enum LightBank bank, - std::map & a_blocks, - std::map & modified_blocks); - - void updateLighting(std::map & a_blocks, - std::map & modified_blocks); - /* These handle lighting but not faces. */ diff --git a/src/mg_schematic.cpp b/src/mg_schematic.cpp index 3d08d86fa..92e138df4 100644 --- a/src/mg_schematic.cpp +++ b/src/mg_schematic.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "serialization.h" #include "filesys.h" +#include "voxelalgorithms.h" /////////////////////////////////////////////////////////////////////////////// @@ -202,7 +203,7 @@ bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1))); } -void Schematic::placeOnMap(Map *map, v3s16 p, u32 flags, +void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place) { std::map lighting_modified_blocks; @@ -238,15 +239,10 @@ void Schematic::placeOnMap(Map *map, v3s16 p, u32 flags, blitToVManip(&vm, p, rot, force_place); - vm.blitBackAll(&modified_blocks); + voxalgo::blit_back_with_light(map, &vm, &modified_blocks); //// Carry out post-map-modification actions - //// Update lighting - // TODO: Optimize this by using Mapgen::calcLighting() instead - lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end()); - map->updateLighting(lighting_modified_blocks, modified_blocks); - //// Create & dispatch map modification events to observers MapEditEvent event; event.type = MEET_OTHER; diff --git a/src/mg_schematic.h b/src/mg_schematic.h index 1d46e6ac4..2f60c843b 100644 --- a/src/mg_schematic.h +++ b/src/mg_schematic.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" class Map; +class ServerMap; class Mapgen; class MMVManip; class PseudoRandom; @@ -108,7 +109,7 @@ public: void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place); bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place); - void placeOnMap(Map *map, v3s16 p, u32 flags, Rotation rot, bool force_place); + void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place); void applyProbabilities(v3s16 p0, std::vector > *plist, diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index bc1c32f03..0bc9e25f7 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -1357,7 +1357,9 @@ int ModApiMapgen::l_place_schematic(lua_State *L) { MAP_LOCK_REQUIRED; - Map *map = &(getEnv(L)->getMap()); + GET_ENV_PTR; + + ServerMap *map = &(env->getServerMap()); SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; //// Read position diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index bdf720f0a..5f129d2af 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "server.h" #include "mapgen.h" +#include "voxelalgorithms.h" // garbage collector int LuaVoxelManip::gc_object(lua_State *L) @@ -109,10 +110,24 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); - MMVManip *vm = o->vm; + GET_ENV_PTR; + ServerMap *map = &(env->getServerMap()); + if (o->is_mapgen_vm) { + o->vm->blitBackAll(&(o->modified_blocks)); + } else { + voxalgo::blit_back_with_light(map, o->vm, + &(o->modified_blocks)); + } - vm->blitBackAll(&o->modified_blocks); + MapEditEvent event; + event.type = MEET_OTHER; + for (std::map::iterator it = o->modified_blocks.begin(); + it != o->modified_blocks.end(); ++it) + event.modified_blocks.insert(it->first); + map->dispatchEvent(&event); + + o->modified_blocks.clear(); return 0; } @@ -322,33 +337,6 @@ int LuaVoxelManip::l_set_param2_data(lua_State *L) int LuaVoxelManip::l_update_map(lua_State *L) { - GET_ENV_PTR; - - LuaVoxelManip *o = checkobject(L, 1); - if (o->is_mapgen_vm) - return 0; - - Map *map = &(env->getMap()); - - // TODO: Optimize this by using Mapgen::calcLighting() instead - std::map lighting_mblocks; - std::map *mblocks = &o->modified_blocks; - - lighting_mblocks.insert(mblocks->begin(), mblocks->end()); - - map->updateLighting(lighting_mblocks, *mblocks); - - MapEditEvent event; - event.type = MEET_OTHER; - for (std::map::iterator - it = mblocks->begin(); - it != mblocks->end(); ++it) - event.modified_blocks.insert(it->first); - - map->dispatchEvent(&event); - - mblocks->clear(); - return 0; } diff --git a/src/treegen.cpp b/src/treegen.cpp index 4df574f34..505954e8e 100644 --- a/src/treegen.cpp +++ b/src/treegen.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverenvironment.h" #include "nodedef.h" #include "treegen.h" +#include "voxelalgorithms.h" namespace treegen { @@ -125,12 +126,8 @@ treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, if (e != SUCCESS) return e; - vmanip.blitBackAll(&modified_blocks); + voxalgo::blit_back_with_light(map, &vmanip, &modified_blocks); - // update lighting - std::map lighting_modified_blocks; - lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end()); - map->updateLighting(lighting_modified_blocks, modified_blocks); // Send a MEET_OTHER event MapEditEvent event; event.type = MEET_OTHER; diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index 3c32bc125..411369bd4 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -542,6 +542,21 @@ void spread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, } } +struct SunlightPropagationUnit{ + v2s16 relative_pos; + bool is_sunlit; + + SunlightPropagationUnit(v2s16 relpos, bool sunlit): + relative_pos(relpos), + is_sunlit(sunlit) + {} +}; + +struct SunlightPropagationData{ + std::vector data; + v3s16 target_block; +}; + /*! * Returns true if the node gets sunlight from the * node above it. @@ -753,7 +768,7 @@ void update_lighting_nodes(Map *map, for (u8 i = 0; i <= LIGHT_SUN; i++) { const std::vector &lights = light_sources.lights[i]; for (std::vector::const_iterator it = lights.begin(); - it < lights.end(); it++) { + it < lights.end(); ++it) { MapNode n = it->block->getNodeNoCheck(it->rel_position, &is_valid_position); n.setLight(bank, i, ndef); @@ -817,8 +832,10 @@ void update_block_border_lighting(Map *map, MapBlock *block, bool is_valid_position; for (s32 i = 0; i < 2; i++) { LightBank bank = banks[i]; - UnlightQueue disappearing_lights(256); - ReLightQueue light_sources(256); + // Since invalid light is not common, do not allocate + // memory if not needed. + UnlightQueue disappearing_lights(0); + ReLightQueue light_sources(0); // Get incorrect lights for (direction d = 0; d < 6; d++) { // For each direction @@ -873,7 +890,7 @@ void update_block_border_lighting(Map *map, MapBlock *block, for (u8 i = 0; i <= LIGHT_SUN; i++) { const std::vector &lights = light_sources.lights[i]; for (std::vector::const_iterator it = lights.begin(); - it < lights.end(); it++) { + it < lights.end(); ++it) { MapNode n = it->block->getNodeNoCheck(it->rel_position, &is_valid_position); n.setLight(bank, i, ndef); @@ -885,6 +902,352 @@ void update_block_border_lighting(Map *map, MapBlock *block, } } +/*! + * Resets the lighting of the given VoxelManipulator to + * complete darkness and full sunlight. + * Operates in one map sector. + * + * \param offset contains the least x and z node coordinates + * of the map sector. + * \param light incoming sunlight, light[x][z] is true if there + * is sunlight above the voxel manipulator at the given x-z coordinates. + * The array's indices are relative node coordinates in the sector. + * After the procedure returns, this contains outgoing light at + * the bottom of the voxel manipulator. + */ +void fill_with_sunlight(MMVManip *vm, INodeDefManager *ndef, v2s16 offset, + bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) +{ + // Distance in array between two nodes on top of each other. + s16 ystride = vm->m_area.getExtent().X; + // Cache the ignore node. + MapNode ignore = MapNode(CONTENT_IGNORE); + // For each column of nodes: + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + // Position of the column on the map. + v2s16 realpos = offset + v2s16(x, z); + // Array indices in the voxel manipulator + s32 maxindex = vm->m_area.index(realpos.X, vm->m_area.MaxEdge.Y, + realpos.Y); + s32 minindex = vm->m_area.index(realpos.X, vm->m_area.MinEdge.Y, + realpos.Y); + // True if the current node has sunlight. + bool lig = light[z][x]; + // For each node, downwards: + for (s32 i = maxindex; i >= minindex; i -= ystride) { + MapNode *n; + if (vm->m_flags[i] & VOXELFLAG_NO_DATA) + n = &ignore; + else + n = &vm->m_data[i]; + // Ignore IGNORE nodes, these are not generated yet. + if(n->getContent() == CONTENT_IGNORE) + continue; + const ContentFeatures &f = ndef->get(n->getContent()); + if (lig && !f.sunlight_propagates) + // Sunlight is stopped. + lig = false; + // Reset light + n->setLight(LIGHTBANK_DAY, lig ? 15 : 0, f); + n->setLight(LIGHTBANK_NIGHT, 0, f); + } + // Output outgoing light. + light[z][x] = lig; + } +} + +/*! + * Returns incoming sunlight for one map block. + * If block above is not found, it is loaded. + * + * \param pos position of the map block that gets the sunlight. + * \param light incoming sunlight, light[z][x] is true if there + * is sunlight above the block at the given z-x relative + * node coordinates. + */ +void is_sunlight_above_block(ServerMap *map, mapblock_v3 pos, + INodeDefManager *ndef, bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) +{ + mapblock_v3 source_block_pos = pos + v3s16(0, 1, 0); + // Get or load source block. + // It might take a while to load, but correcting incorrect + // sunlight may be even slower. + MapBlock *source_block = map->emergeBlock(source_block_pos, false); + // Trust only generated blocks. + if (source_block == NULL || source_block->isDummy() + || !source_block->isGenerated()) { + // But if there is no block above, then use heuristics + bool sunlight = true; + MapBlock *node_block = map->getBlockNoCreateNoEx(pos); + if (node_block == NULL) + // This should not happen. + sunlight = false; + else + sunlight = !node_block->getIsUnderground(); + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + light[z][x] = sunlight; + } else { + // Dummy boolean, the position is valid. + bool is_valid_position; + // For each column: + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + // Get the bottom block. + MapNode above = source_block->getNodeNoCheck(x, 0, z, + &is_valid_position); + light[z][x] = above.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN; + } + } +} + +/*! + * Propagates sunlight down in a given map block. + * + * \param data contains incoming sunlight and shadow and + * the coordinates of the target block. + * \param unlight propagated shadow is inserted here + * \param relight propagated sunlight is inserted here + * + * \returns true if the block was modified, false otherwise. + */ +bool propagate_block_sunlight(Map *map, INodeDefManager *ndef, + SunlightPropagationData *data, UnlightQueue *unlight, ReLightQueue *relight) +{ + bool modified = false; + // Get the block. + MapBlock *block = map->getBlockNoCreateNoEx(data->target_block); + if (block == NULL || block->isDummy()) { + // The work is done if the block does not contain data. + data->data.clear(); + return false; + } + // Dummy boolean + bool is_valid; + // For each changing column of nodes: + size_t index; + for (index = 0; index < data->data.size(); index++) { + SunlightPropagationUnit it = data->data[index]; + // Relative position of the currently inspected node. + relative_v3 current_pos(it.relative_pos.X, MAP_BLOCKSIZE - 1, + it.relative_pos.Y); + if (it.is_sunlit) { + // Propagate sunlight. + // For each node downwards: + for (; current_pos.Y >= 0; current_pos.Y--) { + MapNode n = block->getNodeNoCheck(current_pos, &is_valid); + const ContentFeatures &f = ndef->get(n); + if (n.getLightRaw(LIGHTBANK_DAY, f) < LIGHT_SUN + && f.sunlight_propagates) { + // This node gets sunlight. + n.setLight(LIGHTBANK_DAY, LIGHT_SUN, f); + block->setNodeNoCheck(current_pos, n); + modified = true; + relight->push(LIGHT_SUN, current_pos, data->target_block, + block, 4); + } else { + // Light already valid, propagation stopped. + break; + } + } + } else { + // Propagate shadow. + // For each node downwards: + for (; current_pos.Y >= 0; current_pos.Y--) { + MapNode n = block->getNodeNoCheck(current_pos, &is_valid); + const ContentFeatures &f = ndef->get(n); + if (n.getLightRaw(LIGHTBANK_DAY, f) == LIGHT_SUN) { + // The sunlight is no longer valid. + n.setLight(LIGHTBANK_DAY, 0, f); + block->setNodeNoCheck(current_pos, n); + modified = true; + unlight->push(LIGHT_SUN, current_pos, data->target_block, + block, 4); + } else { + // Reached shadow, propagation stopped. + break; + } + } + } + if (current_pos.Y >= 0) { + // Propagation stopped, remove from data. + data->data[index] = data->data.back(); + data->data.pop_back(); + index--; + } + } + return modified; +} + +/*! + * Borders of a map block in relative node coordinates. + * The areas do not overlap. + * Compatible with type 'direction'. + */ +const VoxelArea block_pad[] = { + VoxelArea(v3s16(15, 0, 0), v3s16(15, 15, 15)), //X+ + VoxelArea(v3s16(1, 15, 0), v3s16(14, 15, 15)), //Y+ + VoxelArea(v3s16(1, 1, 15), v3s16(14, 14, 15)), //Z+ + VoxelArea(v3s16(1, 1, 0), v3s16(14, 14, 0)), //Z- + VoxelArea(v3s16(1, 0, 0), v3s16(14, 0, 15)), //Y- + VoxelArea(v3s16(0, 0, 0), v3s16(0, 15, 15)) //X- +}; + +void blit_back_with_light(ServerMap *map, MMVManip *vm, + std::map *modified_blocks) +{ + INodeDefManager *ndef = map->getNodeDefManager(); + mapblock_v3 minblock = getNodeBlockPos(vm->m_area.MinEdge); + mapblock_v3 maxblock = getNodeBlockPos(vm->m_area.MaxEdge); + // First queue is for day light, second is for night light. + UnlightQueue unlight[] = { UnlightQueue(256), UnlightQueue(256) }; + ReLightQueue relight[] = { ReLightQueue(256), ReLightQueue(256) }; + // Will hold sunlight data. + bool lights[MAP_BLOCKSIZE][MAP_BLOCKSIZE]; + SunlightPropagationData data; + // Dummy boolean. + bool is_valid; + + // --- STEP 1: reset everything to sunlight + + // For each map block: + for (s16 x = minblock.X; x <= maxblock.X; x++) + for (s16 z = minblock.Z; z <= maxblock.Z; z++) { + // Extract sunlight above. + is_sunlight_above_block(map, v3s16(x, maxblock.Y, z), ndef, lights); + v2s16 offset(x, z); + offset *= MAP_BLOCKSIZE; + // Reset the voxel manipulator. + fill_with_sunlight(vm, ndef, offset, lights); + // Copy sunlight data + data.target_block = v3s16(x, minblock.Y - 1, z); + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + data.data.push_back( + SunlightPropagationUnit(v2s16(x, z), lights[z][x])); + // Propagate sunlight and shadow below the voxel manipulator. + while (!data.data.empty()) { + if (propagate_block_sunlight(map, ndef, &data, &unlight[0], + &relight[0])) + (*modified_blocks)[data.target_block] = + map->getBlockNoCreateNoEx(data.target_block); + // Step downwards. + data.target_block.Y--; + } + } + + // --- STEP 2: Get nodes from borders to unlight + + // In case there are unloaded holes in the voxel manipulator + // unlight each block. + // For each block: + for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) + for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) + for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { + v3s16 blockpos(b_x, b_y, b_z); + MapBlock *block = map->getBlockNoCreateNoEx(blockpos); + if (!block || block->isDummy()) + // Skip not existing blocks. + continue; + v3s16 offset = block->getPosRelative(); + // For each border of the block: + for (direction d = 0; d < 6; d++) { + VoxelArea a = block_pad[d]; + // For each node of the border: + for (s32 x = a.MinEdge.X; x <= a.MaxEdge.X; x++) + for (s32 z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) + for (s32 y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { + v3s16 relpos(x, y, z); + // Get old and new node + MapNode oldnode = block->getNodeNoCheck(x, y, z, &is_valid); + const ContentFeatures &oldf = ndef->get(oldnode); + MapNode newnode = vm->getNodeNoExNoEmerge(relpos + offset); + const ContentFeatures &newf = ndef->get(newnode); + // For each light bank + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + u8 oldlight = oldf.param_type == CPT_LIGHT ? + oldnode.getLightNoChecks(bank, &oldf): + LIGHT_SUN; // no light information, force unlighting + u8 newlight = newf.param_type == CPT_LIGHT ? + newnode.getLightNoChecks(bank, &newf): + newf.light_source; + // If the new node is dimmer, unlight. + if (oldlight > newlight) { + unlight[b].push( + oldlight, relpos, blockpos, block, 6); + } + } // end of banks + } // end of nodes + } // end of borders + } // end of blocks + + // --- STEP 3: All information extracted, overwrite + + vm->blitBackAll(modified_blocks, true); + + // --- STEP 4: Do unlighting + + for (size_t bank = 0; bank < 2; bank++) { + LightBank b = banks[bank]; + unspread_light(map, ndef, b, unlight[bank], relight[bank], + *modified_blocks); + } + + // --- STEP 5: Get all newly inserted light sources + + // For each block: + for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) + for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) + for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { + v3s16 blockpos(b_x, b_y, b_z); + MapBlock *block = map->getBlockNoCreateNoEx(blockpos); + if (!block || block->isDummy()) + // Skip not existing blocks + continue; + // For each node in the block: + for (s32 x = 0; x < MAP_BLOCKSIZE; x++) + for (s32 z = 0; z < MAP_BLOCKSIZE; z++) + for (s32 y = 0; y < MAP_BLOCKSIZE; y++) { + v3s16 relpos(x, y, z); + MapNode node = block->getNodeNoCheck(x, y, z, &is_valid); + const ContentFeatures &f = ndef->get(node); + // For each light bank + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + u8 light = f.param_type == CPT_LIGHT ? + node.getLightNoChecks(bank, &f): + f.light_source; + if (light > 1) + relight[b].push(light, relpos, blockpos, block, 6); + } // end of banks + } // end of nodes + } // end of blocks + + // --- STEP 6: do light spreading + + // For each light bank: + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + // Sunlight is already initialized. + u8 maxlight = (b == 0) ? LIGHT_MAX : LIGHT_SUN; + // Initialize light values for light spreading. + for (u8 i = 0; i <= maxlight; i++) { + const std::vector &lights = relight[b].lights[i]; + for (std::vector::const_iterator it = lights.begin(); + it < lights.end(); ++it) { + MapNode n = it->block->getNodeNoCheck(it->rel_position, + &is_valid); + n.setLight(bank, i, ndef); + it->block->setNodeNoCheck(it->rel_position, n); + } + } + // Spread lights. + spread_light(map, ndef, bank, relight[b], *modified_blocks); + } +} + VoxelLineIterator::VoxelLineIterator( const v3f &start_position, const v3f &line_vector) : diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index bf1638fa3..cdffe86c8 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -26,7 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/cpp11_container.h" class Map; +class ServerMap; class MapBlock; +class MMVManip; namespace voxalgo { @@ -84,6 +86,17 @@ void update_lighting_nodes( void update_block_border_lighting(Map *map, MapBlock *block, std::map &modified_blocks); +/*! + * Copies back nodes from a voxel manipulator + * to the map and updates lighting. + * For server use only. + * + * \param modified_blocks output, contains all map blocks that + * the function modified + */ +void blit_back_with_light(ServerMap *map, MMVManip *vm, + std::map *modified_blocks); + /*! * This class iterates trough voxels that intersect with * a line. The collision detection does not see nodeboxes, -- cgit v1.2.3 From c05dfac9a309c7aef1b908ac0070ca71b587f75d Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 15 Mar 2017 22:07:19 +0100 Subject: lua_api: Document minetest.features (#5311) --- doc/lua_api.txt | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 484a5848c..9b53b87f5 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1943,13 +1943,31 @@ Helper functions * `minetest.get_worldpath()`: returns e.g. `"/home/user/.minetest/world"` * Useful for storing custom data * `minetest.is_singleplayer()` -* `minetest.features` - * Table containing API feature flags: `{foo=true, bar=true}` +* `minetest.features`: Table containing API feature flags + { + glasslike_framed = true, + nodebox_as_selectionbox = true, + chat_send_player_param3 = true, + get_all_craft_recipes_works = true, + use_texture_alpha = true, + -- ^ The transparency channel of textures can be used optionally + no_legacy_abms = true, + -- ^ Tree and grass ABMs are no longer done from C++ + texture_names_parens = true, + -- ^ Texture grouping is possible using parentheses + area_store_custom_ids = true, + -- ^ Unique Area ID for AreaStore:insert_area + add_entity_with_staticdata = true, + -- ^ add_entity supports passing initial staticdata to on_activate + no_chat_message_prediction = true, + -- ^ Chat messages are no longer predicted + } * `minetest.has_feature(arg)`: returns `boolean, missing_features` * `arg`: string or table in format `{foo=true, bar=true}` * `missing_features`: `{foo=true, bar=true}` -* `minetest.get_player_information(player_name)`: returns a table containing - information about player. Example return value: +* `minetest.get_player_information(player_name)`: + * Returns a table containing information about a player + Example return value: { address = "127.0.0.1", -- IP address of client ip_version = 4, -- IPv4 / IPv6 -- cgit v1.2.3 From c4b98deb616fbe204c503d678fb920baa33cbede Mon Sep 17 00:00:00 2001 From: Paramat Date: Mon, 20 Mar 2017 21:22:16 +0000 Subject: Firelike drawtype: Add missing docs for visual_scale in lua_api.txt (#5434) --- doc/lua_api.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9b53b87f5..2ac8f1077 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3841,9 +3841,10 @@ Definition tables drawtype = "normal", -- See "Node drawtypes" visual_scale = 1.0, --[[ - ^ Supported for drawtypes "plantlike", "signlike", "torchlike", "mesh". - ^ For plantlike, the image will start at the bottom of the node; for the - ^ other drawtypes, the image will be centered on the node. + ^ Supported for drawtypes "plantlike", "signlike", "torchlike", + ^ "firelike", "mesh". + ^ For plantlike and firelike, the image will start at the bottom of the + ^ node, for the other drawtypes the image will be centered on the node. ^ Note that positioning for "torchlike" may still change. ]] tiles = {tile definition 1, def2, def3, def4, def5, def6}, --[[ ^ Textures of node; +Y, -Y, +X, -X, +Z, -Z (old field name: tile_images) -- cgit v1.2.3 From 81c3dc32a8e48aee076e4b28cae0037a4ae254d9 Mon Sep 17 00:00:00 2001 From: Diego Martínez Date: Tue, 28 Mar 2017 16:55:39 -0300 Subject: Add functions to strip color information. (#5472) --- builtin/common/misc_helpers.lua | 15 ++++++++++++++- doc/client_lua_api.md | 6 ++++++ doc/lua_api.txt | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index cf76fbde9..f7111680c 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -640,6 +640,8 @@ if INIT == "client" or INIT == "mainmenu" then end end +local ESCAPE_CHAR = string.char(0x1b) + -- Client-sided mods don't have access to getbool if core.setting_getbool and core.setting_getbool("disable_escape_sequences") then @@ -657,7 +659,6 @@ if core.setting_getbool and core.setting_getbool("disable_escape_sequences") the else - local ESCAPE_CHAR = string.char(0x1b) function core.get_color_escape_sequence(color) return ESCAPE_CHAR .. "(c@" .. color .. ")" end @@ -678,3 +679,15 @@ else end end + +function core.strip_foreground_colors(str) + return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", "")) +end + +function core.strip_background_colors(str) + return (str:gsub(ESCAPE_CHAR .. "%(b@[^)]+%)", "")) +end + +function core.strip_colors(str) + return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", "")) +end diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index f7a1094f0..d5ccef5d3 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -818,6 +818,12 @@ The following functions provide escape sequences: * `color` is a ColorString * The escape sequence sets the background of the whole text element to `color`. Only defined for item descriptions and tooltips. +* `color.strip_foreground_colors(str)` + * Removes foreground colors added by `get_color_escape_sequence`. +* `color.strip_background_colors(str)` + * Removes background colors added by `get_background_escape_sequence`. +* `color.strip_colors(str)` + * Removes all color escape sequences. `ColorString` ------------- diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 2ac8f1077..72599cb7c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1872,6 +1872,12 @@ The following functions provide escape sequences: * `color` is a ColorString * The escape sequence sets the background of the whole text element to `color`. Only defined for item descriptions and tooltips. +* `color.strip_foreground_colors(str)` + * Removes foreground colors added by `get_color_escape_sequence`. +* `color.strip_background_colors(str)` + * Removes background colors added by `get_background_escape_sequence`. +* `color.strip_colors(str)` + * Removes all color escape sequences. Spatial Vectors --------------- -- cgit v1.2.3 From ea549bbae3650d246db7b70a2b07485a4b404409 Mon Sep 17 00:00:00 2001 From: paramat Date: Wed, 29 Mar 2017 03:40:30 +0100 Subject: Paramtype2: Add missing type CPT2_GLASSLIKE_LIQUID_LEVEL Add the missing paramtype2 for param2 controlling the liquid level inside the glasslike_framed drawtype. Add missing documentation of the feature to lua_api.txt. Update and improve comments for drawtype enumerations in nodedef.h. --- doc/lua_api.txt | 4 ++++ src/content_mapblock.cpp | 7 ++++-- src/nodedef.h | 52 ++++++++++++++++++++++++++++++------------- src/script/cpp_api/s_node.cpp | 1 + 4 files changed, 47 insertions(+), 17 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 72599cb7c..0ca3767f9 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -658,6 +658,10 @@ node definition: The first five bits of `param2` tells which color is picked from the palette. The palette should have 32 pixels. + paramtype2 == "glasslikeliquidlevel" + ^ Only valid for "glasslike_framed" or "glasslike_framed_optional" drawtypes. + param2 defines 64 levels of internal liquid. + Liquid texture is defined using `special_tiles = {"modname_tilename.png"},` collision_box = { type = "fixed", fixed = { diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index d0cb50db3..d18aff879 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -760,8 +760,11 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() drawAutoLightedCuboid(glass_faces[face]); } - if (param2 > 0 && f->special_tiles[0].texture) { - // Interior volume level is in range 0 .. 63, + // Optionally render internal liquid level defined by param2 + // Liquid is textured with 1 tile defined in nodedef 'special_tiles' + if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && + f->special_tiles[0].texture) { + // Internal liquid level has param2 range 0 .. 63, // convert it to -0.5 .. 0.5 float vlev = (param2 / 63.0) * 2.0 - 1.0; tile = getSpecialTile(*f, n, 0); diff --git a/src/nodedef.h b/src/nodedef.h index 4a9908ecc..da3345d80 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -74,7 +74,9 @@ enum ContentParamType2 // 3 bits of palette index, then facedir CPT2_COLORED_FACEDIR, // 5 bits of palette index, then wallmounted - CPT2_COLORED_WALLMOUNTED + CPT2_COLORED_WALLMOUNTED, + // Glasslike framed drawtype internal liquid level, param2 values 0 to 63 + CPT2_GLASSLIKE_LIQUID_LEVEL, }; enum LiquidType @@ -144,26 +146,46 @@ public: enum NodeDrawType { - NDT_NORMAL, // A basic solid block - NDT_AIRLIKE, // Nothing is drawn - NDT_LIQUID, // Do not draw face towards same kind of flowing/source liquid - NDT_FLOWINGLIQUID, // A very special kind of thing - NDT_GLASSLIKE, // Glass-like, don't draw faces towards other glass - NDT_ALLFACES, // Leaves-like, draw all faces no matter what - NDT_ALLFACES_OPTIONAL, // Fancy -> allfaces, fast -> normal + // A basic solid block + NDT_NORMAL, + // Nothing is drawn + NDT_AIRLIKE, + // Do not draw face towards same kind of flowing/source liquid + NDT_LIQUID, + // A very special kind of thing + NDT_FLOWINGLIQUID, + // Glass-like, don't draw faces towards other glass + NDT_GLASSLIKE, + // Leaves-like, draw all faces no matter what + NDT_ALLFACES, + // Enabled -> ndt_allfaces, disabled -> ndt_normal + NDT_ALLFACES_OPTIONAL, + // Single plane perpendicular to a surface NDT_TORCHLIKE, + // Single plane parallel to a surface NDT_SIGNLIKE, + // 2 vertical planes in a 'X' shape diagonal to XZ axes. + // paramtype2 = "meshoptions" allows various forms, sizes and + // vertical and horizontal random offsets. NDT_PLANTLIKE, + // Fenceposts that connect to neighbouring fenceposts with horizontal bars NDT_FENCELIKE, + // Selects appropriate junction texture to connect like rails to + // neighbouring raillikes. NDT_RAILLIKE, + // Custom Lua-definable structure of multiple cuboids NDT_NODEBOX, - NDT_GLASSLIKE_FRAMED, // Glass-like, draw connected frames and all all - // visible faces - // uses 2 textures, one for frames, second for faces - NDT_FIRELIKE, // Draw faces slightly rotated and only on connecting nodes, - NDT_GLASSLIKE_FRAMED_OPTIONAL, // enabled -> connected, disabled -> Glass-like - // uses 2 textures, one for frames, second for faces - NDT_MESH, // Uses static meshes + // Glass-like, draw connected frames and all visible faces. + // param2 > 0 defines 64 levels of internal liquid + // Uses 3 textures, one for frames, second for faces, + // optional third is a 'special tile' for the liquid. + NDT_GLASSLIKE_FRAMED, + // Draw faces slightly rotated and only on neighbouring nodes + NDT_FIRELIKE, + // Enabled -> ndt_glasslike_framed, disabled -> ndt_glasslike + NDT_GLASSLIKE_FRAMED_OPTIONAL, + // Uses static meshes + NDT_MESH, }; // Mesh options for NDT_PLANTLIKE with CPT2_MESHOPTIONS diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index 23c8f43b9..adad01e45 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -62,6 +62,7 @@ struct EnumString ScriptApiNode::es_ContentParamType2[] = {CPT2_COLOR, "color"}, {CPT2_COLORED_FACEDIR, "colorfacedir"}, {CPT2_COLORED_WALLMOUNTED, "colorwallmounted"}, + {CPT2_GLASSLIKE_LIQUID_LEVEL, "glasslikeliquidlevel"}, {0, NULL}, }; -- cgit v1.2.3 From 26f4a5c2d1e3d825816188fcd63f6d1f6758ae60 Mon Sep 17 00:00:00 2001 From: MarkuBu Date: Sat, 1 Apr 2017 16:50:53 +0200 Subject: First commit for fine pointed (#5485) --- builtin/common/misc_helpers.lua | 32 ++++++++++++++++++++++++++++++++ doc/client_lua_api.md | 4 +++- doc/lua_api.txt | 2 ++ 3 files changed, 37 insertions(+), 1 deletion(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index f7111680c..1c7ff3958 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -691,3 +691,35 @@ end function core.strip_colors(str) return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", "")) end + +-------------------------------------------------------------------------------- +-- Returns the exact coordinate of a pointed surface +-------------------------------------------------------------------------------- +function core.pointed_thing_to_face_pos(placer, pointed_thing) + local eye_offset_first = placer:get_eye_offset() + local node_pos = pointed_thing.under + local camera_pos = placer:get_pos() + local pos_off = vector.multiply( + vector.subtract(pointed_thing.above, node_pos), 0.5) + local look_dir = placer:get_look_dir() + local offset, nc + local oc = {} + + for c, v in pairs(pos_off) do + if v == 0 then + oc[#oc + 1] = c + else + offset = v + nc = c + end + end + local fine_pos = {[nc] = node_pos[nc] + offset} + camera_pos.y = camera_pos.y + 1.625 + eye_offset_first.y / 10 + local f = (node_pos[nc] + offset - camera_pos[nc]) / look_dir[nc] + + for i = 1, #oc do + fine_pos[oc[i]] = camera_pos[oc[i]] + look_dir[oc[i]] * f + end + return fine_pos +end + diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index deb5bfb13..c9ccbeefa 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -752,6 +752,8 @@ Call these functions only at load time! extra arguments and return the result * `fgettext(string, ...)` : returns string * same as fgettext_ne(), but calls minetest.formspec_escape before returning result +* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position + * returns the exact position on the surface of a pointed node ### UI * `minetest.ui.minimap` @@ -827,7 +829,7 @@ The following functions provide escape sequences: * Removes background colors added by `get_background_escape_sequence`. * `color.strip_colors(str)` * Removes all color escape sequences. - + `ColorString` ------------- `#RGB` defines a color in hexadecimal format. diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 0ca3767f9..0d3f00c67 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1939,6 +1939,8 @@ Helper functions * returns time with microsecond precision. May not return wall time. * `table.copy(table)`: returns a table * returns a deep copy of `table` +* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position + * returns the exact position on the surface of a pointed node `minetest` namespace reference ------------------------------ -- cgit v1.2.3 From fb4c730708a4140f05d2161c27e6c58bb0f72a9b Mon Sep 17 00:00:00 2001 From: raymoo Date: Sat, 8 Apr 2017 01:42:59 -0700 Subject: Document that write_json will error on unserializable types. (#5539) Previously it was erroneously documented that it would save them as null. --- doc/client_lua_api.md | 2 +- doc/lua_api.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index d68f90dec..bee53636c 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -712,7 +712,7 @@ Call these functions only at load time! * `minetest.write_json(data[, styled])`: returns a string or `nil` and an error message * Convert a Lua table into a JSON string * styled: Outputs in a human-readable format if this is set, defaults to false - * Unserializable things like functions and userdata are saved as null. + * Unserializable things like functions and userdata will cause an error. * **Warning**: JSON is more strict than the Lua table format. 1. You can only use strings and positive integers of at least one as keys. 2. You can not mix string and integer keys. diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 0d3f00c67..ca1b5d14c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2709,7 +2709,7 @@ These functions return the leftover itemstack. * `minetest.write_json(data[, styled])`: returns a string or `nil` and an error message * Convert a Lua table into a JSON string * styled: Outputs in a human-readable format if this is set, defaults to false - * Unserializable things like functions and userdata are saved as null. + * Unserializable things like functions and userdata will cause an error. * **Warning**: JSON is more strict than the Lua table format. 1. You can only use strings and positive integers of at least one as keys. 2. You can not mix string and integer keys. -- cgit v1.2.3 From 58d83a7bb2f992194c3df304b1dcbb81f98f78c0 Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Fri, 10 Mar 2017 18:25:58 +0100 Subject: Hardware coloring for itemstacks Adds the possibility to colorize item stacks based on their metadata. In the item/node definition you can specify palette (an image file) and color (fallback color if the item has no palette or metadata). Then you can add palette_index to the metadata. Dropped itemstacks with different colors do not merge. --- builtin/game/item_entity.lua | 31 +++++++++---- doc/lua_api.txt | 18 ++++++++ src/camera.cpp | 3 +- src/client/tile.cpp | 69 +++++++++++++++++++++++++++- src/client/tile.h | 15 ++++--- src/content_cao.cpp | 39 +++++++++------- src/hud.cpp | 20 +++++++-- src/itemdef.cpp | 55 +++++++++++++++++++---- src/itemdef.h | 18 +++++++- src/nodedef.cpp | 82 +--------------------------------- src/object_properties.cpp | 2 + src/object_properties.h | 2 + src/script/common/c_content.cpp | 14 +++++- src/script/common/c_content.h | 3 +- src/script/cpp_api/s_entity.cpp | 5 ++- src/script/lua_api/l_itemstackmeta.cpp | 5 +++ src/script/lua_api/l_metadata.cpp | 14 ++++++ src/script/lua_api/l_metadata.h | 3 ++ src/script/lua_api/l_nodemeta.cpp | 5 +++ src/script/lua_api/l_object.cpp | 2 +- src/script/lua_api/l_storage.cpp | 5 +++ src/wieldmesh.cpp | 19 +++++--- src/wieldmesh.h | 18 +++++++- 23 files changed, 308 insertions(+), 139 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index be158c119..7b8247116 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -53,6 +53,8 @@ core.register_entity(":__builtin:item", { if itemtable then itemname = stack:to_table().name end + -- Backwards compatibility: old clients use the texture + -- to get the type of the item local item_texture = nil local item_type = "" if core.registered_items[itemname] then @@ -66,6 +68,7 @@ core.register_entity(":__builtin:item", { visual_size = {x = s, y = s}, collisionbox = {-c, -c, -c, c, c, c}, automatic_rotate = math.pi * 0.5, + wield_item = itemstring, } self.object:set_properties(prop) end, @@ -101,31 +104,39 @@ core.register_entity(":__builtin:item", { self:set_item(self.itemstring) end, + -- moves items from this stack to an other stack try_merge_with = function(self, own_stack, object, obj) + -- other item's stack local stack = ItemStack(obj.itemstring) - if own_stack:get_name() == stack:get_name() and stack:get_free_space() > 0 then + -- only merge if items are the same + if own_stack:get_name() == stack:get_name() and + own_stack:get_meta() == stack:get_meta() and + own_stack:get_wear() == stack:get_wear() and + stack:get_free_space() > 0 then local overflow = false local count = stack:get_count() + own_stack:get_count() local max_count = stack:get_stack_max() if count > max_count then overflow = true + stack:set_count(max_count) count = count - max_count + own_stack:set_count(count) else self.itemstring = '' + stack:set_count(count) end local pos = object:getpos() pos.y = pos.y + (count - stack:get_count()) / max_count * 0.15 object:moveto(pos, false) local s, c - local max_count = stack:get_stack_max() - local name = stack:get_name() if not overflow then - obj.itemstring = name .. " " .. count + obj.itemstring = stack:to_string() s = 0.2 + 0.1 * (count / max_count) c = s object:set_properties({ visual_size = {x = s, y = s}, - collisionbox = {-c, -c, -c, c, c, c} + collisionbox = {-c, -c, -c, c, c, c}, + wield_item = obj.itemstring }) self.object:remove() -- merging succeeded @@ -133,18 +144,20 @@ core.register_entity(":__builtin:item", { else s = 0.4 c = 0.3 + obj.itemstring = stack:to_string() object:set_properties({ visual_size = {x = s, y = s}, - collisionbox = {-c, -c, -c, c, c, c} + collisionbox = {-c, -c, -c, c, c, c}, + wield_item = obj.itemstring }) - obj.itemstring = name .. " " .. max_count s = 0.2 + 0.1 * (count / max_count) c = s + self.itemstring = own_stack:to_string() self.object:set_properties({ visual_size = {x = s, y = s}, - collisionbox = {-c, -c, -c, c, c, c} + collisionbox = {-c, -c, -c, c, c, c}, + wield_item = self.itemstring }) - self.itemstring = name .. " " .. count end end -- merging didn't succeed diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ca1b5d14c..721f5448a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1480,6 +1480,9 @@ Item metadata only contains a key-value store. Some of the values in the key-value store are handled specially: * `description`: Set the itemstack's description. Defaults to idef.description +* `color`: A `ColorString`, which sets the stack's color. +* `palette_index`: If the item has a palette, this is used to get the + current color from the palette. Example stuff: @@ -2855,6 +2858,8 @@ See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`. * Any non-table value will clear the metadata * See "Node Metadata" for an example * returns `true` on success +* `equals(other)` + * returns `true` if this metadata has the same key-value pairs as `other` ### `NodeMetaRef` Node metadata: reference extra data and functionality stored in a node. @@ -3735,6 +3740,19 @@ Definition tables {hard = 1, metal = 1, spikes = 1} inventory_image = "default_tool_steelaxe.png", wield_image = "", + palette = "", + --[[ + ^ An image file containing the palette of a node. + ^ You can set the currently used color as the + ^ "palette_index" field of the item stack metadata. + ^ The palette is always stretched to fit indices + ^ between 0 and 255, to ensure compatibility with + ^ "colorfacedir" and "colorwallmounted" nodes. + ]] + color = "0xFFFFFFFF", + --[[ + ^ The color of the item. The palette overrides this. + ]] wield_scale = {x = 1, y = 1, z = 1}, stack_max = 99, range = 4.0, diff --git a/src/camera.cpp b/src/camera.cpp index d5c337fe8..7e83dadeb 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -501,7 +501,8 @@ void Camera::setDigging(s32 button) void Camera::wield(const ItemStack &item) { - if (item.name != m_wield_item_next.name) { + if (item.name != m_wield_item_next.name || + item.metadata != m_wield_item_next.metadata) { m_wield_item_next = item; if (m_wield_change_timer > 0) m_wield_change_timer = -m_wield_change_timer; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 86ca7d422..0aa06980c 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -341,6 +341,8 @@ public: */ video::ITexture* getTextureForMesh(const std::string &name, u32 *id); + virtual Palette* getPalette(const std::string &name); + // Returns a pointer to the irrlicht device virtual IrrlichtDevice* getDevice() { @@ -377,8 +379,6 @@ public: video::ITexture* generateTextureFromMesh( const TextureFromMeshParams ¶ms); - video::IImage* generateImage(const std::string &name); - video::ITexture* getNormalTexture(const std::string &name); video::SColor getTextureAverageColor(const std::string &name); video::ITexture *getShaderFlagsTexture(bool normamap_present); @@ -401,6 +401,13 @@ private: // if baseimg is NULL, it is created. Otherwise stuff is made on it. bool generateImagePart(std::string part_of_name, video::IImage *& baseimg); + /*! 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. + */ + video::IImage* generateImage(const std::string &name); + // Thread-safe cache of what source images are known (true = known) MutexedMap m_source_image_existence; @@ -419,6 +426,9 @@ private: // but can't be deleted because the ITexture* might still be used std::vector m_texture_trash; + // Maps image file names to loaded palettes. + UNORDERED_MAP m_palettes; + // Cached settings needed for making textures from meshes bool m_setting_trilinear_filter; bool m_setting_bilinear_filter; @@ -682,6 +692,61 @@ video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 * return getTexture(name + "^[applyfiltersformesh", id); } +Palette* TextureSource::getPalette(const std::string &name) +{ + // Only the main thread may load images + sanity_check(thr_is_current_thread(m_main_thread)); + + if (name == "") + return NULL; + + UNORDERED_MAP::iterator it = m_palettes.find(name); + if (it == m_palettes.end()) { + // Create palette + video::IImage *img = generateImage(name); + if (!img) { + warningstream << "TextureSource::getPalette(): palette \"" << name + << "\" could not be loaded." << std::endl; + return NULL; + } + Palette new_palette; + u32 w = img->getDimension().Width; + u32 h = img->getDimension().Height; + // Real area of the image + u32 area = h * w; + if (area == 0) + return NULL; + if (area > 256) { + warningstream << "TextureSource::getPalette(): the specified" + << " palette image \"" << name << "\" is larger than 256" + << " pixels, using the first 256." << std::endl; + area = 256; + } else if (256 % area != 0) + warningstream << "TextureSource::getPalette(): the " + << "specified palette image \"" << name << "\" does not " + << "contain power of two pixels." << std::endl; + // We stretch the palette so it will fit 256 values + // This many param2 values will have the same color + u32 step = 256 / area; + // 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); + return NULL; +} + void TextureSource::processQueue() { /* diff --git a/src/client/tile.h b/src/client/tile.h index d04ab918a..5eec0f2ea 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -33,6 +33,8 @@ class IGameDef; struct TileSpec; struct TileDef; +typedef std::vector Palette; + /* tile.{h,cpp}: Texture handling stuff. */ @@ -106,14 +108,15 @@ public: const std::string &name, u32 *id = NULL)=0; virtual video::ITexture* getTextureForMesh( const std::string &name, u32 *id = NULL) = 0; + /*! + * Returns a palette from the given texture name. + * The pointer is valid until the texture source is + * destructed. + * Should be called from the main thread. + */ + virtual Palette* getPalette(const std::string &name) = 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; diff --git a/src/content_cao.cpp b/src/content_cao.cpp index e0b1c4cd2..84f198b75 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -933,23 +933,30 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, errorstream<<"GenericCAO::addToScene(): Could not load mesh "<= 1){ - infostream<<"textures[0]: "<idef(); - ItemStack item(m_prop.textures[0], 1, 0, idef); - - m_wield_meshnode = new WieldMeshSceneNode( - smgr->getRootSceneNode(), smgr, -1); - m_wield_meshnode->setItem(item, m_client); - - m_wield_meshnode->setScale(v3f(m_prop.visual_size.X/2, - m_prop.visual_size.Y/2, - m_prop.visual_size.X/2)); - u8 li = m_last_light; - m_wield_meshnode->setColor(video::SColor(255,li,li,li)); + ItemStack item; + infostream << "GenericCAO::addToScene(): wielditem" << std::endl; + if (m_prop.wield_item == "") { + // Old format, only textures are specified. + infostream << "textures: " << m_prop.textures.size() << std::endl; + if (m_prop.textures.size() >= 1) { + infostream << "textures[0]: " << m_prop.textures[0] + << std::endl; + IItemDefManager *idef = m_client->idef(); + item = ItemStack(m_prop.textures[0], 1, 0, idef); + } + } else { + infostream << "serialized form: " << m_prop.wield_item << std::endl; + item.deSerialize(m_prop.wield_item, m_client->idef()); } + m_wield_meshnode = new WieldMeshSceneNode(smgr->getRootSceneNode(), + smgr, -1); + m_wield_meshnode->setItem(item, m_client); + + m_wield_meshnode->setScale( + v3f(m_prop.visual_size.X / 2, m_prop.visual_size.Y / 2, + m_prop.visual_size.X / 2)); + u8 li = m_last_light; + m_wield_meshnode->setColor(video::SColor(255, li, li, li)); } else { infostream<<"GenericCAO::addToScene(): \""< #ifdef HAVE_TOUCHSCREENGUI @@ -642,9 +643,10 @@ void drawItemStack(video::IVideoDriver *driver, } const ItemDefinition &def = item.getDefinition(client->idef()); - scene::IMesh* mesh = client->idef()->getWieldMesh(def.name, client); + ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client); - if (mesh) { + if (imesh && imesh->mesh) { + scene::IMesh *mesh = imesh->mesh; driver->clearZBuffer(); s32 delta = 0; if (rotation_kind < IT_ROT_NONE) { @@ -667,16 +669,28 @@ void drawItemStack(video::IVideoDriver *driver, matrix.makeIdentity(); if (enable_animations) { - float timer_f = (float)delta / 5000.0; + float timer_f = (float) delta / 5000.0; matrix.setRotationDegrees(core::vector3df(0, 360 * timer_f, 0)); } driver->setTransform(video::ETS_WORLD, matrix); driver->setViewPort(rect); + video::SColor basecolor = + client->idef()->getItemstackColor(item, client); + u32 mc = mesh->getMeshBufferCount(); for (u32 j = 0; j < mc; ++j) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + // we can modify vertices relatively fast, + // because these meshes are not buffered. + assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER); + video::SColor c = basecolor; + if (imesh->buffer_colors.size() > j) { + std::pair p = imesh->buffer_colors[j]; + c = p.first ? p.second : basecolor; + } + colorizeMeshBuffer(buf, &c); video::SMaterial &material = buf->getMaterial(); material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; material.Lighting = false; diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 627ad1b6c..51d8f1d5d 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -82,6 +82,8 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def) sound_place = def.sound_place; sound_place_failed = def.sound_place_failed; range = def.range; + palette_image = def.palette_image; + color = def.color; return *this; } @@ -104,6 +106,8 @@ void ItemDefinition::reset() description = ""; inventory_image = ""; wield_image = ""; + palette_image = ""; + color = video::SColor(0xFFFFFFFF); wield_scale = v3f(1.0, 1.0, 1.0); stack_max = 99; usable = false; @@ -153,6 +157,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const writeF1000(os, range); os << serializeString(sound_place_failed.name); writeF1000(os, sound_place_failed.gain); + os << serializeString(palette_image); + writeU32(os, color.color); } void ItemDefinition::deSerialize(std::istream &is) @@ -209,6 +215,8 @@ void ItemDefinition::deSerialize(std::istream &is) try { sound_place_failed.name = deSerializeString(is); sound_place_failed.gain = readF1000(is); + palette_image = deSerializeString(is); + color.set(readU32(is)); } catch(SerializationError &e) {}; } @@ -224,11 +232,13 @@ class CItemDefManager: public IWritableItemDefManager struct ClientCached { video::ITexture *inventory_texture; - scene::IMesh *wield_mesh; + ItemMesh wield_mesh; + Palette *palette; ClientCached(): inventory_texture(NULL), - wield_mesh(NULL) + wield_mesh(), + palette(NULL) {} }; #endif @@ -250,8 +260,8 @@ public: i = values.begin(); i != values.end(); ++i) { ClientCached *cc = *i; - if (cc->wield_mesh) - cc->wield_mesh->drop(); + if (cc->wield_mesh.mesh) + cc->wield_mesh.mesh->drop(); delete cc; } @@ -335,8 +345,9 @@ public: ItemStack item = ItemStack(); item.name = def.name; - scene::IMesh *mesh = getItemMesh(client, item); - cc->wield_mesh = mesh; + getItemMesh(client, item, &(cc->wield_mesh)); + + cc->palette = tsrc->getPalette(def.palette_image); // Put in cache m_clientcached.set(name, cc); @@ -390,13 +401,41 @@ public: return cc->inventory_texture; } // Get item wield mesh - virtual scene::IMesh* getWieldMesh(const std::string &name, + virtual ItemMesh* getWieldMesh(const std::string &name, Client *client) const { ClientCached *cc = getClientCached(name, client); if(!cc) return NULL; - return cc->wield_mesh; + return &(cc->wield_mesh); + } + + // Get item palette + virtual Palette* getPalette(const std::string &name, + Client *client) const + { + ClientCached *cc = getClientCached(name, client); + if(!cc) + return NULL; + return cc->palette; + } + + virtual video::SColor getItemstackColor(const ItemStack &stack, + Client *client) const + { + // Look for direct color definition + const std::string &colorstring = stack.metadata.getString("color", 0); + video::SColor directcolor; + if ((colorstring != "") + && parseColorString(colorstring, directcolor, true)) + return directcolor; + // See if there is a palette + Palette *palette = getPalette(stack.name, client); + const std::string &index = stack.metadata.getString("palette_index", 0); + if ((palette != NULL) && (index != "")) + return (*palette)[mystoi(index, 0, 255)]; + // Fallback color + return get(stack.name).color; } #endif void clear() diff --git a/src/itemdef.h b/src/itemdef.h index 01ec4fa2f..2d7ff570d 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -30,6 +30,11 @@ with this program; if not, write to the Free Software Foundation, Inc., class IGameDef; class Client; struct ToolCapabilities; +#ifndef SERVER +#include "client/tile.h" +struct ItemMesh; +struct ItemStack; +#endif /* Base item definition @@ -57,6 +62,8 @@ struct ItemDefinition */ std::string inventory_image; // Optional for nodes, mandatory for tools/craftitems std::string wield_image; // If empty, inventory_image or mesh (only nodes) is used + std::string palette_image; // If specified, the item will be colorized based on this + video::SColor color; // The fallback color of the node. v3f wield_scale; /* @@ -110,8 +117,15 @@ public: virtual video::ITexture* getInventoryTexture(const std::string &name, Client *client) const=0; // Get item wield mesh - virtual scene::IMesh* getWieldMesh(const std::string &name, + virtual ItemMesh* getWieldMesh(const std::string &name, Client *client) const=0; + // Get item palette + virtual Palette* getPalette(const std::string &name, + Client *client) const = 0; + // Returns the base color of an item stack: the color of all + // tiles that do not define their own color. + virtual video::SColor getItemstackColor(const ItemStack &stack, + Client *client) const = 0; #endif virtual void serialize(std::ostream &os, u16 protocol_version)=0; @@ -136,7 +150,7 @@ public: virtual video::ITexture* getInventoryTexture(const std::string &name, Client *client) const=0; // Get item wield mesh - virtual scene::IMesh* getWieldMesh(const std::string &name, + virtual ItemMesh* getWieldMesh(const std::string &name, Client *client) const=0; #endif diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 2745a45e8..558acafd6 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -786,6 +786,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc tiledef_special[j].backface_culling, material_type); } + palette = tsrc->getPalette(palette_name); + if ((drawtype == NDT_MESH) && (mesh != "")) { // Meshnode drawtype // Read the mesh and apply scale @@ -859,9 +861,6 @@ 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); @@ -910,9 +909,6 @@ 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; @@ -1401,78 +1397,6 @@ 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) @@ -1489,12 +1413,10 @@ 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++) { 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); } diff --git a/src/object_properties.cpp b/src/object_properties.cpp index f4e4953ba..a77368151 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -117,6 +117,7 @@ void ObjectProperties::serialize(std::ostream &os) const writeARGB8(os, nametag_color); writeF1000(os, automatic_face_movement_max_rotation_per_sec); os << serializeString(infotext); + os << serializeString(wield_item); // Add stuff only at the bottom. // Never remove anything, because we don't want new versions of this @@ -159,6 +160,7 @@ void ObjectProperties::deSerialize(std::istream &is) nametag_color = readARGB8(is); automatic_face_movement_max_rotation_per_sec = readF1000(is); infotext = deSerializeString(is); + wield_item = deSerializeString(is); }catch(SerializationError &e){} } else diff --git a/src/object_properties.h b/src/object_properties.h index 082d9a529..908757a64 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -52,6 +52,8 @@ struct ObjectProperties video::SColor nametag_color; f32 automatic_face_movement_max_rotation_per_sec; std::string infotext; + //! For dropped items, this contains item information. + std::string wield_item; ObjectProperties(); std::string dump(); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 99e12cd82..bcae874b9 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -58,6 +58,12 @@ ItemDefinition read_item_definition(lua_State* L,int index, getstringfield(L, index, "description", def.description); getstringfield(L, index, "inventory_image", def.inventory_image); getstringfield(L, index, "wield_image", def.wield_image); + getstringfield(L, index, "palette", def.palette_image); + + // Read item color. + lua_getfield(L, index, "color"); + read_color(L, -1, &def.color); + lua_pop(L, 1); lua_getfield(L, index, "wield_scale"); if(lua_istable(L, -1)){ @@ -118,7 +124,7 @@ ItemDefinition read_item_definition(lua_State* L,int index, /******************************************************************************/ void read_object_properties(lua_State *L, int index, - ObjectProperties *prop) + ObjectProperties *prop, IItemDefManager *idef) { if(index < 0) index = lua_gettop(L) + 1 + index; @@ -216,6 +222,10 @@ void read_object_properties(lua_State *L, int index, } lua_pop(L, 1); getstringfield(L, -1, "infotext", prop->infotext); + lua_getfield(L, -1, "wield_item"); + if (!lua_isnil(L, -1)) + prop->wield_item = read_item(L, -1, idef).getItemString(); + lua_pop(L, 1); } /******************************************************************************/ @@ -284,6 +294,8 @@ void push_object_properties(lua_State *L, ObjectProperties *prop) lua_setfield(L, -2, "automatic_face_movement_max_rotation_per_sec"); lua_pushlstring(L, prop->infotext.c_str(), prop->infotext.size()); lua_setfield(L, -2, "infotext"); + lua_pushlstring(L, prop->wield_item.c_str(), prop->wield_item.size()); + lua_setfield(L, -2, "wield_item"); } /******************************************************************************/ diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 10cccbb01..949b136eb 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -89,7 +89,8 @@ void push_tool_capabilities (lua_State *L, ItemDefinition read_item_definition (lua_State *L, int index, ItemDefinition default_def); void read_object_properties (lua_State *L, int index, - ObjectProperties *prop); + ObjectProperties *prop, + IItemDefManager *idef); void push_object_properties (lua_State *L, ObjectProperties *prop); diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index 9e2193970..2e1d277e4 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "object_properties.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "server.h" bool ScriptApiEntity::luaentity_Add(u16 id, const char *name) { @@ -187,11 +188,11 @@ void ScriptApiEntity::luaentity_GetProperties(u16 id, getstringfield(L, -1, "mesh", prop->mesh); // Deprecated: read object properties directly - read_object_properties(L, -1, prop); + read_object_properties(L, -1, prop, getServer()->idef()); // Read initial_properties lua_getfield(L, -1, "initial_properties"); - read_object_properties(L, -1, prop); + read_object_properties(L, -1, prop, getServer()->idef()); lua_pop(L, 1); } diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp index efdd77b51..c37a82116 100644 --- a/src/script/lua_api/l_itemstackmeta.cpp +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -92,6 +92,10 @@ void ItemStackMetaRef::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); + lua_pushliteral(L, "__eq"); + lua_pushcfunction(L, l_equals); + lua_settable(L, metatable); + lua_pop(L, 1); // drop metatable luaL_openlib(L, 0, methods, 0); // fill methodtable @@ -111,5 +115,6 @@ const luaL_Reg ItemStackMetaRef::methods[] = { luamethod(MetaDataRef, set_float), luamethod(MetaDataRef, to_table), luamethod(MetaDataRef, from_table), + luamethod(MetaDataRef, equals), {0,0} }; diff --git a/src/script/lua_api/l_metadata.cpp b/src/script/lua_api/l_metadata.cpp index b54005bac..5f4e984cb 100644 --- a/src/script/lua_api/l_metadata.cpp +++ b/src/script/lua_api/l_metadata.cpp @@ -250,3 +250,17 @@ bool MetaDataRef::handleFromTable(lua_State *L, int table, Metadata *meta) return true; } + +// equals(self, other) +int MetaDataRef::l_equals(lua_State *L) +{ + MetaDataRef *ref1 = checkobject(L, 1); + Metadata *data1 = ref1->getmeta(false); + MetaDataRef *ref2 = checkobject(L, 2); + Metadata *data2 = ref2->getmeta(false); + if (data1 == NULL || data2 == NULL) + lua_pushboolean(L, data1 == data2); + else + lua_pushboolean(L, *data1 == *data2); + return 1; +} diff --git a/src/script/lua_api/l_metadata.h b/src/script/lua_api/l_metadata.h index be31d95ad..a4d8214d3 100644 --- a/src/script/lua_api/l_metadata.h +++ b/src/script/lua_api/l_metadata.h @@ -67,6 +67,9 @@ protected: // from_table(self, table) static int l_from_table(lua_State *L); + + // equals(self, other) + static int l_equals(lua_State *L); }; #endif /* L_NODEMETA_H_ */ diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 55d11fc13..c65d56f14 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -204,6 +204,10 @@ void NodeMetaRef::RegisterCommon(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); + lua_pushliteral(L, "__eq"); + lua_pushcfunction(L, l_equals); + lua_settable(L, metatable); + lua_pop(L, 1); // drop metatable } @@ -225,6 +229,7 @@ const luaL_Reg NodeMetaRef::methodsServer[] = { luamethod(MetaDataRef, to_table), luamethod(MetaDataRef, from_table), luamethod(NodeMetaRef, get_inventory), + luamethod(MetaDataRef, equals), {0,0} }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index f9d2754e7..0699705cb 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -737,7 +737,7 @@ int ObjectRef::l_set_properties(lua_State *L) ObjectProperties *prop = co->accessObjectProperties(); if (!prop) return 0; - read_object_properties(L, 2, prop); + read_object_properties(L, 2, prop, getServer(L)->idef()); co->notifyObjectPropertiesModified(); return 0; } diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index 59906dda5..4c6b2a182 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -98,6 +98,10 @@ void StorageRef::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); + lua_pushliteral(L, "__eq"); + lua_pushcfunction(L, l_equals); + lua_settable(L, metatable); + lua_pop(L, 1); // drop metatable luaL_openlib(L, 0, methods, 0); // fill methodtable @@ -138,5 +142,6 @@ const luaL_Reg StorageRef::methods[] = { luamethod(MetaDataRef, set_float), luamethod(MetaDataRef, to_table), luamethod(MetaDataRef, from_table), + luamethod(MetaDataRef, equals), {0,0} }; diff --git a/src/wieldmesh.cpp b/src/wieldmesh.cpp index 089a67f33..40af0be5f 100644 --- a/src/wieldmesh.cpp +++ b/src/wieldmesh.cpp @@ -318,11 +318,15 @@ 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; } + + // Color-related m_colors.clear(); + video::SColor basecolor = idef->getItemstackColor(item, client); // If wield_image is defined, it overrides everything else if (def.wield_image != "") { setExtruded(def.wield_image, def.wield_scale, tsrc, 1); + m_colors.push_back(basecolor); return; } // Handle nodes @@ -371,7 +375,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) } else { material.setTexture(0, tile->texture); } - m_colors.push_back(tile->color); + m_colors.push_back(tile->has_color ? tile->color : basecolor); material.MaterialType = m_material_type; if (m_enable_shaders) { if (tile->normal_texture) { @@ -389,6 +393,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) } else if (def.inventory_image != "") { setExtruded(def.inventory_image, def.wield_scale, tsrc, 1); + m_colors.push_back(basecolor); return; } @@ -455,7 +460,7 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) m_meshnode->setVisible(true); } -scene::IMesh *getItemMesh(Client *client, const ItemStack &item) +void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) { ITextureSource *tsrc = client->getTextureSource(); IItemDefManager *idef = client->getItemDefManager(); @@ -475,12 +480,13 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item) // If inventory_image is defined, it overrides everything else if (def.inventory_image != "") { mesh = getExtrudedMesh(tsrc, def.inventory_image); - return mesh; + result->mesh = mesh; + result->buffer_colors.push_back( + std::pair(false, video::SColor(0xFFFFFFFF))); } else if (def.type == ITEM_NODE) { if (f.mesh_ptr[0]) { mesh = cloneMesh(f.mesh_ptr[0]); scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); - setMeshColor(mesh, video::SColor (255, 255, 255, 255)); } else if (f.drawtype == NDT_PLANTLIKE) { mesh = getExtrudedMesh(tsrc, tsrc->getTextureName(f.tiles[0].texture_id)); @@ -515,6 +521,8 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item) for (u32 i = 0; i < mc; ++i) { const TileSpec *tile = &(f.tiles[i]); scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + result->buffer_colors.push_back( + std::pair(tile->has_color, tile->color)); colorizeMeshBuffer(buf, &tile->color); video::SMaterial &material = buf->getMaterial(); material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; @@ -532,9 +540,8 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item) rotateMeshXZby(mesh, -45); rotateMeshYZby(mesh, -30); - return mesh; + result->mesh = mesh; } - return NULL; } scene::IMesh * getExtrudedMesh(ITextureSource *tsrc, diff --git a/src/wieldmesh.h b/src/wieldmesh.h index 200587058..94edb1de6 100644 --- a/src/wieldmesh.h +++ b/src/wieldmesh.h @@ -28,6 +28,22 @@ class Client; class ITextureSource; struct TileSpec; +struct ItemMesh +{ + scene::IMesh* mesh; + /*! + * Stores the color of each mesh buffer. + * If the boolean is true, the color is fixed, else + * palettes can modify it. + */ + std::vector > buffer_colors; + + ItemMesh(): + mesh(NULL), + buffer_colors() + {} +}; + /* Wield item scene node, renders the wield mesh of some item */ @@ -79,7 +95,7 @@ private: aabb3f m_bounding_box; }; -scene::IMesh *getItemMesh(Client *client, const ItemStack &item); +void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); scene::IMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename); #endif -- cgit v1.2.3 From 34d32ce55ae4f3f29d7b645075dc8efacb2c96d2 Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sat, 15 Apr 2017 23:19:18 +0200 Subject: Implement delayed server shutdown with cancelation (#4664) --- builtin/game/chatcommands.lua | 16 ++++++--- builtin/game/misc.lua | 5 +++ doc/lua_api.txt | 8 +++-- src/network/serverpackethandler.cpp | 7 ++++ src/script/lua_api/l_server.cpp | 3 +- src/server.cpp | 67 +++++++++++++++++++++++++++++++++++++ src/server.h | 8 ++--- src/util/string.h | 24 +++++++++++++ 8 files changed, 124 insertions(+), 14 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 8df3903d2..25cc06178 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -763,14 +763,20 @@ core.register_chatcommand("days", { core.register_chatcommand("shutdown", { description = "Shutdown server", - params = "[reconnect] [message]", + params = "[delay_in_seconds(0..inf) or -1 for cancel] [reconnect] [message]", privs = {server=true}, func = function(name, param) - core.log("action", name .. " shuts down server") - core.chat_send_all("*** Server shutting down (operator request).") - local reconnect, message = param:match("([^ ]+)(.*)") + local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)") message = message or "" - core.request_shutdown(message:trim(), core.is_yes(reconnect)) + + if delay ~= "" then + delay = tonumber(param) or 0 + else + delay = 0 + core.log("action", name .. " shuts down server") + core.chat_send_all("*** Server shutting down (operator request).") + end + core.request_shutdown(message:trim(), core.is_yes(reconnect), delay) end, }) diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 618d4d97f..a3eb26ac2 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -173,3 +173,8 @@ end function core.close_formspec(player_name, formname) return minetest.show_formspec(player_name, formname, "") end + +function core.cancel_shutdown_requests() + core.request_shutdown("", false, -1) +end + diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 721f5448a..7b967726d 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2576,8 +2576,12 @@ These functions return the leftover itemstack. * Optional: Variable number of arguments that are passed to `func` ### Server -* `minetest.request_shutdown([message],[reconnect])`: request for server shutdown. Will display `message` to clients, - and `reconnect` == true displays a reconnect button. +* `minetest.request_shutdown([message],[reconnect],[delay])`: request for server shutdown. Will display `message` to clients, + `reconnect` == true displays a reconnect button, + `delay` adds an optional delay (in seconds) before shutdown + negative delay cancels the current active shutdown + zero delay triggers an immediate shutdown. +* `minetest.cancel_shutdown_requests()`: cancel current delayed shutdown * `minetest.get_server_status()`: returns server status string * `minetest.get_server_uptime()`: returns the server uptime in seconds diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 27c33a4f6..2e4c5b6be 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -722,6 +722,13 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) m_clients.event(peer_id, CSE_SetClientReady); m_script->on_joinplayer(playersao); + // Send shutdown timer if shutdown has been scheduled + if (m_shutdown_timer > 0.0f) { + std::wstringstream ws; + ws << L"*** Server shutting down in " + << duration_to_string(round(m_shutdown_timer)).c_str() << "."; + SendChatMessage(pkt->getPeerId(), ws.str()); + } } void Server::handleCommand_GotBlocks(NetworkPacket* pkt) diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index b6d44e0ff..343d11b7e 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -33,7 +33,8 @@ int ModApiServer::l_request_shutdown(lua_State *L) NO_MAP_LOCK_REQUIRED; const char *msg = lua_tolstring(L, 1, NULL); bool reconnect = lua_toboolean(L, 2); - getServer(L)->requestShutdown(msg ? msg : "", reconnect); + float seconds_before_shutdown = lua_tonumber(L, 3); + getServer(L)->requestShutdown(msg ? msg : "", reconnect, seconds_before_shutdown); return 0; } diff --git a/src/server.cpp b/src/server.cpp index 7ed8a8bf4..5328b6897 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -177,6 +177,7 @@ Server::Server( m_clients(&m_con), m_shutdown_requested(false), m_shutdown_ask_reconnect(false), + m_shutdown_timer(0.0f), m_admin_chat(iface), m_ignore_map_edit_events(false), m_ignore_map_edit_events_peer_id(0), @@ -1029,6 +1030,39 @@ void Server::AsyncRunStep(bool initial_step) m_env->saveMeta(); } } + + // Timed shutdown + static const float shutdown_msg_times[] = + { + 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 45, 60, 120, 180, 300, 600, 1200, 1800, 3600 + }; + + if (m_shutdown_timer > 0.0f) { + // Automated messages + if (m_shutdown_timer < shutdown_msg_times[ARRLEN(shutdown_msg_times) - 1]) { + for (u16 i = 0; i < ARRLEN(shutdown_msg_times) - 1; i++) { + // If shutdown timer matches an automessage, shot it + if (m_shutdown_timer > shutdown_msg_times[i] && + m_shutdown_timer - dtime < shutdown_msg_times[i]) { + std::wstringstream ws; + + ws << L"*** Server shutting down in " + << duration_to_string(round(m_shutdown_timer - dtime)).c_str() + << "."; + + infostream << wide_to_utf8(ws.str()).c_str() << std::endl; + SendChatMessage(PEER_ID_INEXISTENT, ws.str()); + break; + } + } + } + + m_shutdown_timer -= dtime; + if (m_shutdown_timer < 0.0f) { + m_shutdown_timer = 0.0f; + m_shutdown_requested = true; + } + } } void Server::Receive() @@ -3443,6 +3477,39 @@ v3f Server::findSpawnPos() return nodeposf; } +void Server::requestShutdown(const std::string &msg, bool reconnect, float delay) +{ + if (delay == 0.0f) { + // No delay, shutdown immediately + m_shutdown_requested = true; + } else if (delay < 0.0f && m_shutdown_timer > 0.0f) { + // Negative delay, cancel shutdown if requested + m_shutdown_timer = 0.0f; + m_shutdown_msg = ""; + m_shutdown_ask_reconnect = false; + m_shutdown_requested = false; + std::wstringstream ws; + + ws << L"*** Server shutdown canceled."; + + infostream << wide_to_utf8(ws.str()).c_str() << std::endl; + SendChatMessage(PEER_ID_INEXISTENT, ws.str()); + } else if (delay > 0.0f) { + // Positive delay, delay the shutdown + m_shutdown_timer = delay; + m_shutdown_msg = msg; + m_shutdown_ask_reconnect = reconnect; + std::wstringstream ws; + + ws << L"*** Server shutting down in " + << duration_to_string(round(m_shutdown_timer)).c_str() + << "."; + + infostream << wide_to_utf8(ws.str()).c_str() << std::endl; + SendChatMessage(PEER_ID_INEXISTENT, ws.str()); + } +} + PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version) { bool newplayer = false; diff --git a/src/server.h b/src/server.h index 1df9d0a93..e2445f833 100644 --- a/src/server.h +++ b/src/server.h @@ -226,12 +226,7 @@ public: inline bool getShutdownRequested() const { return m_shutdown_requested; } // request server to shutdown - void requestShutdown(const std::string &msg, bool reconnect) - { - m_shutdown_requested = true; - m_shutdown_msg = msg; - m_shutdown_ask_reconnect = reconnect; - } + void requestShutdown(const std::string &msg, bool reconnect, float delay = 0.0f); // Returns -1 if failed, sound handle on success // Envlock @@ -602,6 +597,7 @@ private: bool m_shutdown_requested; std::string m_shutdown_msg; bool m_shutdown_ask_reconnect; + float m_shutdown_timer; ChatInterface *m_admin_chat; std::string m_admin_nick; diff --git a/src/util/string.h b/src/util/string.h index 572c37150..c155d2f4a 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -614,4 +614,28 @@ inline const char *bool_to_cstr(bool val) return val ? "true" : "false"; } +inline const std::string duration_to_string(int sec) +{ + int min = floor(sec / 60); + sec %= 60; + int hour = floor(min / 60); + min %= 60; + + std::stringstream ss; + if (hour > 0) { + ss << hour << "h "; + } + + if (min > 0) { + ss << min << "m "; + } + + if (sec > 0) { + ss << sec << "s "; + } + + return ss.str(); +} + + #endif -- cgit v1.2.3 From f6da7b3fda5bbb793c7795a228ea6f2602b93fbe Mon Sep 17 00:00:00 2001 From: paramat Date: Wed, 5 Apr 2017 12:18:22 +0100 Subject: Sneak: Add option for old move code Temporary option for the old move code for specific old sneak behaviour. Enabled by setting the added 'new move' physics override to false. By default 'new move' is true. --- doc/lua_api.txt | 6 +- src/content_cao.cpp | 2 + src/content_sao.cpp | 6 +- src/content_sao.h | 1 + src/genericobject.cpp | 3 +- src/genericobject.h | 3 +- src/localplayer.cpp | 309 ++++++++++++++++++++++++++++++++++++++++ src/localplayer.h | 8 ++ src/script/lua_api/l_object.cpp | 21 ++- src/script/lua_api/l_object.h | 2 +- 10 files changed, 349 insertions(+), 12 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 7b967726d..4427e26d8 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3021,7 +3021,11 @@ This is basically a reference to a C++ `ServerActiveObject` * `jump`: multiplier to default jump value (default: `1`) * `gravity`: multiplier to default gravity value (default: `1`) * `sneak`: whether player can sneak (default: `true`) - * `sneak_glitch`: whether player can use the sneak glitch (default: `true`) + * `sneak_glitch`: whether player can use the new move code replications + of the old sneak side-effects: sneak ladders and 2 node sneak jump + when next to a ledge 2 nodes up (default: `true`) + * `new_move`: use new move/sneak code. When `false` the exact old code + is used for the specific old sneak behaviour (default: `true`) * `get_physics_override()`: returns the table given to set_physics_override * `hud_add(hud definition)`: add a HUD element described by HUD def, returns ID number on success diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 84f198b75..ac283da88 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -1652,6 +1652,7 @@ void GenericCAO::processMessage(const std::string &data) // these are sent inverted so we get true when the server sends nothing bool sneak = !readU8(is); bool sneak_glitch = !readU8(is); + bool new_move = !readU8(is); if(m_is_local_player) @@ -1662,6 +1663,7 @@ void GenericCAO::processMessage(const std::string &data) player->physics_override_gravity = override_gravity; player->physics_override_sneak = sneak; player->physics_override_sneak_glitch = sneak_glitch; + player->physics_override_new_move = new_move; } } else if (cmd == GENERIC_CMD_SET_ANIMATION) { // TODO: change frames send as v2s32 value diff --git a/src/content_sao.cpp b/src/content_sao.cpp index bb2387d1a..908365397 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -789,6 +789,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer m_physics_override_gravity(1), m_physics_override_sneak(true), m_physics_override_sneak_glitch(true), + m_physics_override_new_move(true), m_physics_override_sent(false) { assert(m_peer_id != 0); // pre-condition @@ -886,7 +887,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak, - m_physics_override_sneak_glitch)); // 5 + m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only. msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6 int message_count = 6 + m_bone_position.size(); @@ -1049,7 +1050,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_physics_override_sent = true; std::string str = gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity, - m_physics_override_sneak, m_physics_override_sneak_glitch); + m_physics_override_sneak, m_physics_override_sneak_glitch, + m_physics_override_new_move); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); diff --git a/src/content_sao.h b/src/content_sao.h index c1b01b6dd..e53e8ecce 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -397,6 +397,7 @@ public: float m_physics_override_gravity; bool m_physics_override_sneak; bool m_physics_override_sneak_glitch; + bool m_physics_override_new_move; bool m_physics_override_sent; }; diff --git a/src/genericobject.cpp b/src/genericobject.cpp index c4660cf44..07d2445b4 100644 --- a/src/genericobject.cpp +++ b/src/genericobject.cpp @@ -118,7 +118,7 @@ std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups) } std::string gob_cmd_update_physics_override(float physics_override_speed, float physics_override_jump, - float physics_override_gravity, bool sneak, bool sneak_glitch) + float physics_override_gravity, bool sneak, bool sneak_glitch, bool new_move) { std::ostringstream os(std::ios::binary); // command @@ -130,6 +130,7 @@ std::string gob_cmd_update_physics_override(float physics_override_speed, float // these are sent inverted so we get true when the server sends nothing writeU8(os, !sneak); writeU8(os, !sneak_glitch); + writeU8(os, !new_move); return os.str(); } diff --git a/src/genericobject.h b/src/genericobject.h index 48e71db75..7d2ec4b14 100644 --- a/src/genericobject.h +++ b/src/genericobject.h @@ -68,7 +68,8 @@ std::string gob_cmd_punched(s16 damage, s16 result_hp); std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups); std::string gob_cmd_update_physics_override(float physics_override_speed, - float physics_override_jump, float physics_override_gravity, bool sneak, bool sneak_glitch); + float physics_override_jump, float physics_override_gravity, + bool sneak, bool sneak_glitch, bool new_move); std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend, bool frame_loop); diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 9dc3bf5aa..ea4347207 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -49,6 +49,7 @@ LocalPlayer::LocalPlayer(Client *client, const char *name): physics_override_gravity(1.0f), physics_override_sneak(true), physics_override_sneak_glitch(true), + physics_override_new_move(true), // Temporary option for old move code overridePosition(v3f(0,0,0)), last_position(v3f(0,0,0)), last_speed(v3f(0,0,0)), @@ -66,6 +67,7 @@ LocalPlayer::LocalPlayer(Client *client, const char *name): hurt_tilt_strength(0.0f), m_position(0,0,0), m_sneak_node(32767,32767,32767), + m_sneak_node_bb_ymax(0), // To support temporary option for old move code m_sneak_node_bb_top(0,0,0,0,0,0), m_sneak_node_exists(false), m_need_to_get_new_sneak_node(true), @@ -178,6 +180,12 @@ static bool detectLedge(Map *map, INodeDefManager *nodemgr, v3s16 pos) void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, std::vector *collision_info) { + // Temporary option for old move code + if (!physics_override_new_move) { + old_move(dtime, env, pos_max_d, collision_info); + return; + } + Map *map = &env->getMap(); INodeDefManager *nodemgr = m_client->ndef(); @@ -806,3 +814,304 @@ void LocalPlayer::accelerateVertical(const v3f &target_speed, const f32 max_incr m_speed.Y += d_wanted; } +// Temporary option for old move code +void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, + std::vector *collision_info) +{ + Map *map = &env->getMap(); + INodeDefManager *nodemgr = m_client->ndef(); + + v3f position = getPosition(); + + // Copy parent position if local player is attached + if (isAttached) { + setPosition(overridePosition); + m_sneak_node_exists = false; + return; + } + + // Skip collision detection if noclip mode is used + bool fly_allowed = m_client->checkLocalPrivilege("fly"); + bool noclip = m_client->checkLocalPrivilege("noclip") && + g_settings->getBool("noclip"); + bool free_move = noclip && fly_allowed && g_settings->getBool("free_move"); + if (free_move) { + position += m_speed * dtime; + setPosition(position); + m_sneak_node_exists = false; + return; + } + + /* + Collision detection + */ + bool is_valid_position; + MapNode node; + v3s16 pp; + + /* + Check if player is in liquid (the oscillating value) + */ + if (in_liquid) { + // If in liquid, the threshold of coming out is at higher y + pp = floatToInt(position + v3f(0, BS * 0.1, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) { + in_liquid = nodemgr->get(node.getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + } else { + in_liquid = false; + } + } else { + // If not in liquid, the threshold of going in is at lower y + pp = floatToInt(position + v3f(0, BS * 0.5, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) { + in_liquid = nodemgr->get(node.getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + } else { + in_liquid = false; + } + } + + /* + Check if player is in liquid (the stable value) + */ + pp = floatToInt(position + v3f(0, 0, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + if (is_valid_position) + in_liquid_stable = nodemgr->get(node.getContent()).isLiquid(); + else + in_liquid_stable = false; + + /* + Check if player is climbing + */ + pp = floatToInt(position + v3f(0, 0.5 * BS, 0), BS); + v3s16 pp2 = floatToInt(position + v3f(0, -0.2 * BS, 0), BS); + node = map->getNodeNoEx(pp, &is_valid_position); + bool is_valid_position2; + MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2); + + if (!(is_valid_position && is_valid_position2)) + is_climbing = false; + else + is_climbing = (nodemgr->get(node.getContent()).climbable || + nodemgr->get(node2.getContent()).climbable) && !free_move; + + /* + Collision uncertainty radius + Make it a bit larger than the maximum distance of movement + */ + //f32 d = pos_max_d * 1.1; + // A fairly large value in here makes moving smoother + f32 d = 0.15 * BS; + // This should always apply, otherwise there are glitches + sanity_check(d > pos_max_d); + // Maximum distance over border for sneaking + f32 sneak_max = BS * 0.4; + + /* + If sneaking, keep in range from the last walked node and don't + fall off from it + */ + if (control.sneak && m_sneak_node_exists && + !(fly_allowed && g_settings->getBool("free_move")) && !in_liquid && + physics_override_sneak && !got_teleported) { + f32 maxd = 0.5 * BS + sneak_max; + v3f lwn_f = intToFloat(m_sneak_node, BS); + position.X = rangelim(position.X, lwn_f.X - maxd, lwn_f.X + maxd); + position.Z = rangelim(position.Z, lwn_f.Z - maxd, lwn_f.Z + maxd); + + if (!is_climbing) { + // Move up if necessary + f32 new_y = (lwn_f.Y - 0.5 * BS) + m_sneak_node_bb_ymax; + if (position.Y < new_y) + position.Y = new_y; + /* + Collision seems broken, since player is sinking when + sneaking over the edges of current sneaking_node. + TODO (when fixed): Set Y-speed only to 0 when position.Y < new_y. + */ + if (m_speed.Y < 0) + m_speed.Y = 0; + } + } + + if (got_teleported) + got_teleported = false; + + // this shouldn't be hardcoded but transmitted from server + float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2); + +#ifdef __ANDROID__ + player_stepheight += (0.6 * BS); +#endif + + v3f accel_f = v3f(0, 0, 0); + + collisionMoveResult result = collisionMoveSimple(env, m_client, + pos_max_d, m_collisionbox, player_stepheight, dtime, + &position, &m_speed, accel_f); + + /* + If the player's feet touch the topside of any node, this is + set to true. + + Player is allowed to jump when this is true. + */ + bool touching_ground_was = touching_ground; + touching_ground = result.touching_ground; + + //bool standing_on_unloaded = result.standing_on_unloaded; + + /* + Check the nodes under the player to see from which node the + player is sneaking from, if any. If the node from under + the player has been removed, the player falls. + */ + f32 position_y_mod = 0.05 * BS; + if (m_sneak_node_bb_ymax > 0) + position_y_mod = m_sneak_node_bb_ymax - position_y_mod; + v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); + if (m_sneak_node_exists && + nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" && + m_old_node_below_type != "air") { + // Old node appears to have been removed; that is, + // it wasn't air before but now it is + m_need_to_get_new_sneak_node = false; + m_sneak_node_exists = false; + } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") { + // We are on something, so make sure to recalculate the sneak + // node. + m_need_to_get_new_sneak_node = true; + } + + if (m_need_to_get_new_sneak_node && physics_override_sneak) { + m_sneak_node_bb_ymax = 0; + v3s16 pos_i_bottom = floatToInt(position - v3f(0, position_y_mod, 0), BS); + v2f player_p2df(position.X, position.Z); + f32 min_distance_f = 100000.0 * BS; + // If already seeking from some node, compare to it. + v3s16 new_sneak_node = m_sneak_node; + for (s16 x= -1; x <= 1; x++) + for (s16 z= -1; z <= 1; z++) { + v3s16 p = pos_i_bottom + v3s16(x, 0, z); + v3f pf = intToFloat(p, BS); + v2f node_p2df(pf.X, pf.Z); + f32 distance_f = player_p2df.getDistanceFrom(node_p2df); + f32 max_axis_distance_f = MYMAX( + fabs(player_p2df.X - node_p2df.X), + fabs(player_p2df.Y - node_p2df.Y)); + + if (distance_f > min_distance_f || + max_axis_distance_f > 0.5 * BS + sneak_max + 0.1 * BS) + continue; + + // The node to be sneaked on has to be walkable + node = map->getNodeNoEx(p, &is_valid_position); + if (!is_valid_position || nodemgr->get(node).walkable == false) + continue; + // And the node above it has to be nonwalkable + node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position); + if (!is_valid_position || nodemgr->get(node).walkable) + continue; + // If not 'sneak_glitch' the node 2 nodes above it has to be nonwalkable + if (!physics_override_sneak_glitch) { + node =map->getNodeNoEx(p + v3s16(0, 2, 0), &is_valid_position); + if (!is_valid_position || nodemgr->get(node).walkable) + continue; + } + + min_distance_f = distance_f; + new_sneak_node = p; + } + + bool sneak_node_found = (min_distance_f < 100000.0 * BS * 0.9); + + m_sneak_node = new_sneak_node; + m_sneak_node_exists = sneak_node_found; + + if (sneak_node_found) { + f32 cb_max = 0; + MapNode n = map->getNodeNoEx(m_sneak_node); + std::vector nodeboxes; + n.getCollisionBoxes(nodemgr, &nodeboxes); + for (std::vector::iterator it = nodeboxes.begin(); + it != nodeboxes.end(); ++it) { + aabb3f box = *it; + if (box.MaxEdge.Y > cb_max) + cb_max = box.MaxEdge.Y; + } + m_sneak_node_bb_ymax = cb_max; + } + + /* + If sneaking, the player's collision box can be in air, so + this has to be set explicitly + */ + if (sneak_node_found && control.sneak) + touching_ground = true; + } + + /* + Set new position + */ + setPosition(position); + + /* + Report collisions + */ + // Dont report if flying + if (collision_info && !(g_settings->getBool("free_move") && fly_allowed)) { + for (size_t i = 0; i < result.collisions.size(); i++) { + const CollisionInfo &info = result.collisions[i]; + collision_info->push_back(info); + } + } + + if (!result.standing_on_object && !touching_ground_was && touching_ground) { + MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround"); + m_client->event()->put(e); + // Set camera impact value to be used for view bobbing + camera_impact = getSpeed().Y * -1; + } + + { + camera_barely_in_ceiling = false; + v3s16 camera_np = floatToInt(getEyePosition(), BS); + MapNode n = map->getNodeNoEx(camera_np); + if (n.getContent() != CONTENT_IGNORE) { + if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2) + camera_barely_in_ceiling = true; + } + } + + /* + Update the node last under the player + */ + m_old_node_below = floatToInt(position - v3f(0, BS / 2, 0), BS); + m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name; + + /* + Check properties of the node on which the player is standing + */ + const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos())); + // Determine if jumping is possible + m_can_jump = touching_ground && !in_liquid; + if (itemgroup_get(f.groups, "disable_jump")) + m_can_jump = false; + // Jump key pressed while jumping off from a bouncy block + if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") && + m_speed.Y >= -0.5 * BS) { + float jumpspeed = movement_speed_jump * physics_override_jump; + if (m_speed.Y > 1) { + // Reduce boost when speed already is high + m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 )); + } else { + m_speed.Y += jumpspeed; + } + setSpeed(m_speed); + m_can_jump = false; + } +} diff --git a/src/localplayer.h b/src/localplayer.h index ae987b893..01e859bf0 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -58,12 +58,17 @@ public: float physics_override_gravity; bool physics_override_sneak; bool physics_override_sneak_glitch; + // Temporary option for old move code + bool physics_override_new_move; v3f overridePosition; void move(f32 dtime, Environment *env, f32 pos_max_d); void move(f32 dtime, Environment *env, f32 pos_max_d, std::vector *collision_info); + // Temporary option for old move code + void old_move(f32 dtime, Environment *env, f32 pos_max_d, + std::vector *collision_info); void applyControl(float dtime); @@ -137,6 +142,9 @@ private: v3f m_position; v3s16 m_sneak_node; + // Stores the max player uplift by m_sneak_node + // To support temporary option for old move code + f32 m_sneak_node_bb_ymax; // Stores the top bounding box of m_sneak_node aabb3f m_sneak_node_bb_top; // Whether the player is allowed to sneak diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 0699705cb..95e977f9e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -401,7 +401,7 @@ int ObjectRef::l_get_armor_groups(lua_State *L) } // set_physics_override(self, physics_override_speed, physics_override_jump, -// physics_override_gravity, sneak, sneak_glitch) +// physics_override_gravity, sneak, sneak_glitch, new_move) int ObjectRef::l_set_physics_override(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -410,11 +410,18 @@ int ObjectRef::l_set_physics_override(lua_State *L) if (co == NULL) return 0; // Do it if (lua_istable(L, 2)) { - co->m_physics_override_speed = getfloatfield_default(L, 2, "speed", co->m_physics_override_speed); - co->m_physics_override_jump = getfloatfield_default(L, 2, "jump", co->m_physics_override_jump); - co->m_physics_override_gravity = getfloatfield_default(L, 2, "gravity", co->m_physics_override_gravity); - co->m_physics_override_sneak = getboolfield_default(L, 2, "sneak", co->m_physics_override_sneak); - co->m_physics_override_sneak_glitch = getboolfield_default(L, 2, "sneak_glitch", co->m_physics_override_sneak_glitch); + co->m_physics_override_speed = getfloatfield_default( + L, 2, "speed", co->m_physics_override_speed); + co->m_physics_override_jump = getfloatfield_default( + L, 2, "jump", co->m_physics_override_jump); + co->m_physics_override_gravity = getfloatfield_default( + L, 2, "gravity", co->m_physics_override_gravity); + co->m_physics_override_sneak = getboolfield_default( + L, 2, "sneak", co->m_physics_override_sneak); + co->m_physics_override_sneak_glitch = getboolfield_default( + L, 2, "sneak_glitch", co->m_physics_override_sneak_glitch); + co->m_physics_override_new_move = getboolfield_default( + L, 2, "new_move", co->m_physics_override_new_move); co->m_physics_override_sent = false; } else { // old, non-table format @@ -454,6 +461,8 @@ int ObjectRef::l_get_physics_override(lua_State *L) lua_setfield(L, -2, "sneak"); lua_pushboolean(L, co->m_physics_override_sneak_glitch); lua_setfield(L, -2, "sneak_glitch"); + lua_pushboolean(L, co->m_physics_override_new_move); + lua_setfield(L, -2, "new_move"); return 1; } diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index b6fc35bc2..98f5c2b11 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -105,7 +105,7 @@ private: static int l_get_armor_groups(lua_State *L); // set_physics_override(self, physics_override_speed, physics_override_jump, - // physics_override_gravity, sneak, sneak_glitch) + // physics_override_gravity, sneak, sneak_glitch, new_move) static int l_set_physics_override(lua_State *L); // get_physics_override(self) -- cgit v1.2.3 From 858c72297409ca54092c3c908279d2a05fef58cd Mon Sep 17 00:00:00 2001 From: Hybrid Dog Date: Mon, 10 Apr 2017 22:07:52 +0200 Subject: Tools: Fix tool digging speed limit --- doc/lua_api.txt | 5 ++--- src/game.cpp | 7 ------- src/tool.cpp | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 4427e26d8..6e7a1de68 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1231,9 +1231,8 @@ Another example: Make red wool from white wool and red dye: * `0` is something that is directly accessible at the start of gameplay * There is no upper limit * `dig_immediate`: (player can always pick up node without tool wear) - * `2`: node is removed without tool wear after 0.5 seconds or so - (rail, sign) - * `3`: node is removed without tool wear immediately (torch) + * `2`: node is removed without tool wear after 0.5 seconds (rail, sign) + * `3`: node is removed without tool wear after 0.15 seconds (torch) * `disable_jump`: Player (and possibly other things) cannot jump from node * `fall_damage_add_percent`: damage speed = `speed * (1 + value/100)` * `bouncy`: value is bounce speed in percent diff --git a/src/game.cpp b/src/game.cpp index 198baeca3..bcf378e4e 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3888,13 +3888,6 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, if (runData.nodig_delay_timer > 0.3) runData.nodig_delay_timer = 0.3; - // We want a slight delay to very little - // time consuming nodes - const float mindelay = 0.15; - - if (runData.nodig_delay_timer < mindelay) - runData.nodig_delay_timer = mindelay; - bool is_valid_position; MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position); if (is_valid_position) { diff --git a/src/tool.cpp b/src/tool.cpp index 1877a1cf8..105102dd5 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -98,7 +98,7 @@ DigParams getDigParams(const ItemGroupList &groups, return DigParams(true, 0.5, 0, "dig_immediate"); case 3: //infostream<<"dig_immediate=3"< Date: Sat, 11 Mar 2017 17:07:04 +0100 Subject: Light update for map blocks This is not really different from the light update of a voxel manipulator. This update does not assume that the lighting was correct before, therefore it is useful for correction. Also expose this function to the Lua API for light correction, and allow voxel manipulators not to update the light. --- doc/lua_api.txt | 24 +++++++- src/map.cpp | 10 ++++ src/map.h | 10 ++++ src/script/lua_api/l_env.cpp | 31 ++++++++++ src/script/lua_api/l_env.h | 3 + src/script/lua_api/l_vmanip.cpp | 3 +- src/voxelalgorithms.cpp | 122 +++++++++++++++++++++++++++++++++++++++- src/voxelalgorithms.h | 9 +++ 8 files changed, 209 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 6e7a1de68..16e662e0c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2398,6 +2398,22 @@ and `minetest.auth_reload` call the authetification handler. * increase level of leveled node by level, default `level` equals `1` * if `totallevel > maxlevel`, returns rest (`total-max`) * can be negative for decreasing +* `minetest.fix_light(pos1, pos2)`: returns `true`/`false` + * resets the light in a cuboid-shaped part of + the map and removes lighting bugs. + * Loads the area if it is not loaded. + * `pos1` is the corner of the cuboid with the least coordinates + (in node coordinates), inclusive. + * `pos2` is the opposite corner of the cuboid, inclusive. + * The actual updated cuboid might be larger than the specified one, + because only whole map blocks can be updated. + The actual updated area consists of those map blocks that intersect + with the given cuboid. + * However, the neighborhood of the updated area might change + as well, as light can spread out of the cuboid, also light + might be removed. + * returns `false` if the area is not fully generated, + `true` otherwise * `core.check_single_for_falling(pos)` * causes an unsupported `group:falling_node` node to fall and causes an unattached `group:attached_node` node to fall. @@ -3421,8 +3437,14 @@ will place the schematic inside of the VoxelManip. * `read_from_map(p1, p2)`: Loads a chunk of map into the VoxelManip object containing the region formed by `p1` and `p2`. * returns actual emerged `pmin`, actual emerged `pmax` -* `write_to_map()`: Writes the data loaded from the `VoxelManip` back to the map. +* `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to the map. * **important**: data must be set using `VoxelManip:set_data()` before calling this + * if `light` is true, then lighting is automatically recalculated. + The default value is true. + If `light` is false, no light calculations happen, and you should correct + all modified blocks with `minetest.fix_light()` as soon as possible. + Keep in mind that modifying the map where light is incorrect can cause + more lighting bugs. * `get_node_at(pos)`: Returns a `MapNode` table of the node currently loaded in the `VoxelManip` at that position * `set_node_at(pos, node)`: Sets a specific `MapNode` in the `VoxelManip` at that position diff --git a/src/map.cpp b/src/map.cpp index f8bbee180..8754813dd 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2591,6 +2591,16 @@ void ServerMap::PrintInfo(std::ostream &out) out<<"ServerMap: "; } +bool ServerMap::repairBlockLight(v3s16 blockpos, + std::map *modified_blocks) +{ + MapBlock *block = emergeBlock(blockpos, false); + if (!block || !block->isGenerated()) + return false; + voxalgo::repair_block_light(this, block, modified_blocks); + return true; +} + MMVManip::MMVManip(Map *map): VoxelManipulator(), m_is_dirty(false), diff --git a/src/map.h b/src/map.h index 744a4d1e2..739cdb59b 100644 --- a/src/map.h +++ b/src/map.h @@ -477,6 +477,16 @@ public: u64 getSeed(); s16 getWaterLevel(); + /*! + * Fixes lighting in one map block. + * May modify other blocks as well, as light can spread + * out of the specified block. + * Returns false if the block is not generated (so nothing + * changed), true otherwise. + */ + bool repairBlockLight(v3s16 blockpos, + std::map *modified_blocks); + MapSettingsManager settings_mgr; private: diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 4fad7b37c..1fa7845b5 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -847,6 +847,36 @@ int ModApiEnvMod::l_line_of_sight(lua_State *L) return 1; } +// fix_light(p1, p2) +int ModApiEnvMod::l_fix_light(lua_State *L) +{ + GET_ENV_PTR; + + v3s16 blockpos1 = getContainerPos(read_v3s16(L, 1), MAP_BLOCKSIZE); + v3s16 blockpos2 = getContainerPos(read_v3s16(L, 2), MAP_BLOCKSIZE); + ServerMap &map = env->getServerMap(); + std::map modified_blocks; + bool success = true; + v3s16 blockpos; + for (blockpos.X = blockpos1.X; blockpos.X <= blockpos2.X; blockpos.X++) + for (blockpos.Y = blockpos1.Y; blockpos.Y <= blockpos2.Y; blockpos.Y++) + for (blockpos.Z = blockpos1.Z; blockpos.Z <= blockpos2.Z; blockpos.Z++) { + success = success & map.repairBlockLight(blockpos, &modified_blocks); + } + if (modified_blocks.size() > 0) { + MapEditEvent event; + event.type = MEET_OTHER; + for (std::map::iterator it = modified_blocks.begin(); + it != modified_blocks.end(); ++it) + event.modified_blocks.insert(it->first); + + map.dispatchEvent(&event); + } + lua_pushboolean(L, success); + + return 1; +} + // emerge_area(p1, p2, [callback, context]) // emerge mapblocks in area p1..p2, calls callback with context upon completion int ModApiEnvMod::l_emerge_area(lua_State *L) @@ -1089,6 +1119,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(find_node_near); API_FCT(find_nodes_in_area); API_FCT(find_nodes_in_area_under_air); + API_FCT(fix_light); API_FCT(emerge_area); API_FCT(delete_area); API_FCT(get_perlin); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 38b2282d7..3f688b398 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -128,6 +128,9 @@ private: // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_nodes_in_area_under_air(lua_State *L); + // fix_light(p1, p2) -> true/false + static int l_fix_light(lua_State *L); + // emerge_area(p1, p2) static int l_emerge_area(lua_State *L); diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index 7316fb200..254a7e5a6 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -110,9 +110,10 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); + bool update_light = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true; GET_ENV_PTR; ServerMap *map = &(env->getServerMap()); - if (o->is_mapgen_vm) { + if (o->is_mapgen_vm || !update_light) { o->vm->blitBackAll(&(o->modified_blocks)); } else { voxalgo::blit_back_with_light(map, o->vm, diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index f2142717f..40f8595a7 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -1136,7 +1136,7 @@ void finish_bulk_light_update(Map *map, mapblock_v3 minblock, for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { - v3s16 blockpos(b_x, b_y, b_z); + const v3s16 blockpos(b_x, b_y, b_z); MapBlock *block = map->getBlockNoCreateNoEx(blockpos); if (!block || block->isDummy()) // Skip not existing blocks @@ -1282,6 +1282,126 @@ void blit_back_with_light(ServerMap *map, MMVManip *vm, modified_blocks); } +/*! + * Resets the lighting of the given map block to + * complete darkness and full sunlight. + * + * \param light incoming sunlight, light[x][z] is true if there + * is sunlight above the map block at the given x-z coordinates. + * The array's indices are relative node coordinates in the block. + * After the procedure returns, this contains outgoing light at + * the bottom of the map block. + */ +void fill_with_sunlight(MapBlock *block, INodeDefManager *ndef, + bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) +{ + if (block->isDummy()) + return; + // dummy boolean + bool is_valid; + // For each column of nodes: + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + // True if the current node has sunlight. + bool lig = light[z][x]; + // For each node, downwards: + for (s16 y = MAP_BLOCKSIZE - 1; y >= 0; y--) { + MapNode n = block->getNodeNoCheck(x, y, z, &is_valid); + // Ignore IGNORE nodes, these are not generated yet. + if (n.getContent() == CONTENT_IGNORE) + continue; + const ContentFeatures &f = ndef->get(n.getContent()); + if (lig && !f.sunlight_propagates) { + // Sunlight is stopped. + lig = false; + } + // Reset light + n.setLight(LIGHTBANK_DAY, lig ? 15 : 0, f); + n.setLight(LIGHTBANK_NIGHT, 0, f); + block->setNodeNoCheck(x, y, z, n); + } + // Output outgoing light. + light[z][x] = lig; + } +} + +void repair_block_light(ServerMap *map, MapBlock *block, + std::map *modified_blocks) +{ + if (!block || block->isDummy()) + return; + INodeDefManager *ndef = map->getNodeDefManager(); + // First queue is for day light, second is for night light. + UnlightQueue unlight[] = { UnlightQueue(256), UnlightQueue(256) }; + ReLightQueue relight[] = { ReLightQueue(256), ReLightQueue(256) }; + // Will hold sunlight data. + bool lights[MAP_BLOCKSIZE][MAP_BLOCKSIZE]; + SunlightPropagationData data; + // Dummy boolean. + bool is_valid; + + // --- STEP 1: reset everything to sunlight + + mapblock_v3 blockpos = block->getPos(); + (*modified_blocks)[blockpos] = block; + // For each map block: + // Extract sunlight above. + is_sunlight_above_block(map, blockpos, ndef, lights); + // Reset the voxel manipulator. + fill_with_sunlight(block, ndef, lights); + // Copy sunlight data + data.target_block = v3s16(blockpos.X, blockpos.Y - 1, blockpos.Z); + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + data.data.push_back( + SunlightPropagationUnit(v2s16(x, z), lights[z][x])); + } + // Propagate sunlight and shadow below the voxel manipulator. + while (!data.data.empty()) { + if (propagate_block_sunlight(map, ndef, &data, &unlight[0], + &relight[0])) + (*modified_blocks)[data.target_block] = + map->getBlockNoCreateNoEx(data.target_block); + // Step downwards. + data.target_block.Y--; + } + + // --- STEP 2: Get nodes from borders to unlight + + // For each border of the block: + for (direction d = 0; d < 6; d++) { + VoxelArea a = block_pad[d]; + // For each node of the border: + for (s32 x = a.MinEdge.X; x <= a.MaxEdge.X; x++) + for (s32 z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) + for (s32 y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { + v3s16 relpos(x, y, z); + // Get node + MapNode node = block->getNodeNoCheck(x, y, z, &is_valid); + const ContentFeatures &f = ndef->get(node); + // For each light bank + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + u8 light = f.param_type == CPT_LIGHT ? + node.getLightNoChecks(bank, &f): + f.light_source; + // If the new node is dimmer than sunlight, unlight. + // (if it has maximal light, it is pointless to remove + // surrounding light, as it can only become brighter) + if (LIGHT_SUN > light) { + unlight[b].push( + LIGHT_SUN, relpos, blockpos, block, 6); + } + } // end of banks + } // end of nodes + } // end of borders + + // STEP 3: Remove and spread light + + finish_bulk_light_update(map, blockpos, blockpos, unlight, relight, + modified_blocks); +} + VoxelLineIterator::VoxelLineIterator( const v3f &start_position, const v3f &line_vector) : diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index cdffe86c8..b518979d7 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -97,6 +97,15 @@ void update_block_border_lighting(Map *map, MapBlock *block, void blit_back_with_light(ServerMap *map, MMVManip *vm, std::map *modified_blocks); +/*! + * Corrects the light in a map block. + * For server use only. + * + * \param block the block to update + */ +void repair_block_light(ServerMap *map, MapBlock *block, + std::map *modified_blocks); + /*! * This class iterates trough voxels that intersect with * a line. The collision detection does not see nodeboxes, -- cgit v1.2.3 From 1ffb180868ffcec6812cd3aac8f56ffefb91c8bc Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Fri, 21 Apr 2017 15:34:59 +0200 Subject: Soft node overlay (#5186) This commit adds node overlays, which are tiles that are drawn on top of other tiles. --- doc/lua_api.txt | 6 + src/client.cpp | 16 +- src/client/tile.h | 96 +++++--- src/clientmap.cpp | 56 +++-- src/content_mapblock.cpp | 61 ++--- src/content_mapblock.h | 4 +- src/hud.cpp | 5 +- src/mapblock_mesh.cpp | 525 ++++++++++++++++++++++++---------------- src/mapblock_mesh.h | 57 +++-- src/mesh.cpp | 80 +++--- src/mesh.h | 7 +- src/network/networkprotocol.h | 4 +- src/nodedef.cpp | 36 ++- src/nodedef.h | 4 +- src/particles.cpp | 2 +- src/script/common/c_content.cpp | 28 +++ src/wieldmesh.cpp | 188 +++++++------- src/wieldmesh.h | 55 ++++- 18 files changed, 763 insertions(+), 467 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 16e662e0c..4e328ac76 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3908,6 +3908,12 @@ Definition tables tiles = {tile definition 1, def2, def3, def4, def5, def6}, --[[ ^ Textures of node; +Y, -Y, +X, -X, +Z, -Z (old field name: tile_images) ^ List can be shortened to needed length ]] + overlay_tiles = {tile definition 1, def2, def3, def4, def5, def6}, --[[ + ^ Same as `tiles`, but these textures are drawn on top of the + ^ base tiles. You can use this to colorize only specific parts of + ^ your texture. If the texture name is an empty string, that + ^ overlay is not drawn. Since such tiles are drawn twice, it + ^ is not recommended to use overlays on very common nodes. 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 ]] diff --git a/src/client.cpp b/src/client.cpp index 3cea4fbf4..ce42d025e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -487,13 +487,17 @@ void Client::step(float dtime) minimap_mapblock = r.mesh->moveMinimapMapblock(); if (minimap_mapblock == NULL) do_mapper_update = false; - } - if (r.mesh && r.mesh->getMesh()->getMeshBufferCount() == 0) { - delete r.mesh; - } else { - // Replace with the new mesh - block->mesh = r.mesh; + bool is_empty = true; + for (int l = 0; l < MAX_TILE_LAYERS; l++) + if (r.mesh->getMesh(l)->getMeshBufferCount() != 0) + is_empty = false; + + if (is_empty) + delete r.mesh; + else + // Replace with the new mesh + block->mesh = r.mesh; } } else { delete r.mesh; diff --git a/src/client/tile.h b/src/client/tile.h index ef01d2a1f..c6ebee006 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -194,19 +194,22 @@ struct FrameSpec video::ITexture *flags_texture; }; -struct TileSpec +#define MAX_TILE_LAYERS 2 + +//! Defines a layer of a tile. +struct TileLayer { - TileSpec(): + TileLayer(): texture(NULL), texture_id(0), color(), material_type(TILE_MATERIAL_BASIC), material_flags( //0 // <- DEBUG, Use the one below - MATERIAL_FLAG_BACKFACE_CULLING + MATERIAL_FLAG_BACKFACE_CULLING | + MATERIAL_FLAG_TILEABLE_HORIZONTAL| + MATERIAL_FLAG_TILEABLE_VERTICAL ), - rotation(0), - emissive_light(0), shader_id(0), normal_texture(NULL), flags_texture(NULL), @@ -217,49 +220,41 @@ struct TileSpec } /*! - * Two tiles are equal if they can be appended to - * the same mesh buffer. + * Two layers are equal if they can be merged. */ - bool operator==(const TileSpec &other) const + bool operator==(const TileLayer &other) const { - return ( + return texture_id == other.texture_id && material_type == other.material_type && material_flags == other.material_flags && - rotation == other.rotation - ); + color == other.color; } /*! - * Two tiles are not equal if they must be in different mesh buffers. + * Two tiles are not equal if they must have different vertices. */ - bool operator!=(const TileSpec &other) const + bool operator!=(const TileLayer &other) const { return !(*this == other); } - + // Sets everything else except the texture in the material void applyMaterialOptions(video::SMaterial &material) const { switch (material_type) { case TILE_MATERIAL_BASIC: + case TILE_MATERIAL_WAVING_LEAVES: + case TILE_MATERIAL_WAVING_PLANTS: material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; break; case TILE_MATERIAL_ALPHA: - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - break; case TILE_MATERIAL_LIQUID_TRANSPARENT: material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; break; case TILE_MATERIAL_LIQUID_OPAQUE: material.MaterialType = video::EMT_SOLID; break; - case TILE_MATERIAL_WAVING_LEAVES: - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - break; - case TILE_MATERIAL_WAVING_PLANTS: - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - break; } material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) ? true : false; @@ -285,26 +280,26 @@ struct TileSpec } } - // ordered for performance! please do not reorder unless you pahole it first. + bool isTileable() const + { + return (material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL) + && (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL); + } + video::ITexture *texture; u32 texture_id; - // The color of the tile, or if the tile does not own - // a color then the color of the node owning this tile. + /*! + * 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; // Material parameters u8 material_type; u8 material_flags; - - u8 rotation; - //! This much light does the tile emit. - u8 emissive_light; - u32 shader_id; - video::ITexture *normal_texture; - // cacheline (64) - video::ITexture *flags_texture; + // Animation parameters u16 animation_frame_length_ms; u8 animation_frame_count; @@ -313,4 +308,39 @@ struct TileSpec std::vector frames; }; + +/*! + * Defines a face of a node. May have up to two layers. + */ +struct TileSpec +{ + TileSpec(): + rotation(0), + emissive_light(0) + { + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) + layers[layer] = TileLayer(); + } + + /*! + * Returns true if this tile can be merged with the other tile. + */ + bool isTileable(const TileSpec &other) const { + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + if (layers[layer] != other.layers[layer]) + return false; + if (!layers[layer].isTileable()) + return false; + } + return rotation == 0 + && rotation == other.rotation + && emissive_light == other.emissive_light; + } + + u8 rotation; + //! This much light does the tile emit. + u8 emissive_light; + //! The first is base texture, the second is overlay. + TileLayer layers[MAX_TILE_LAYERS]; +}; #endif diff --git a/src/clientmap.cpp b/src/clientmap.cpp index 4cae03bf2..6cd24ffc6 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -290,6 +290,11 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) struct MeshBufList { + /*! + * Specifies in which layer the list is. + * All lists which are in a lower layer are rendered before this list. + */ + u8 layer; video::SMaterial m; std::vector bufs; }; @@ -303,7 +308,7 @@ struct MeshBufListList lists.clear(); } - void add(scene::IMeshBuffer *buf) + void add(scene::IMeshBuffer *buf, u8 layer) { const video::SMaterial &m = buf->getMaterial(); for(std::vector::iterator i = lists.begin(); @@ -315,12 +320,16 @@ struct MeshBufListList if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture) continue; + if(l.layer != layer) + continue; + if (l.m == m) { l.bufs.push_back(buf); return; } } MeshBufList l; + l.layer = layer; l.m = m; l.bufs.push_back(buf); lists.push_back(l); @@ -434,29 +443,34 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); - scene::IMesh *mesh = mapBlockMesh->getMesh(); - assert(mesh); + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + scene::IMesh *mesh = mapBlockMesh->getMesh(layer); + assert(mesh); - u32 c = mesh->getMeshBufferCount(); - for (u32 i = 0; i < c; i++) - { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + u32 c = mesh->getMeshBufferCount(); + for (u32 i = 0; i < c; i++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - video::SMaterial& material = buf->getMaterial(); - video::IMaterialRenderer* rnd = + video::SMaterial& material = buf->getMaterial(); + video::IMaterialRenderer* rnd = driver->getMaterialRenderer(material.MaterialType); - bool transparent = (rnd && rnd->isTransparent()); - if (transparent == is_transparent_pass) { - if (buf->getVertexCount() == 0) - errorstream << "Block [" << analyze_block(block) - << "] contains an empty meshbuf" << std::endl; - - material.setFlag(video::EMF_TRILINEAR_FILTER, m_cache_trilinear_filter); - material.setFlag(video::EMF_BILINEAR_FILTER, m_cache_bilinear_filter); - material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_cache_anistropic_filter); - material.setFlag(video::EMF_WIREFRAME, m_control.show_wireframe); - - drawbufs.add(buf); + bool transparent = (rnd && rnd->isTransparent()); + if (transparent == is_transparent_pass) { + if (buf->getVertexCount() == 0) + errorstream << "Block [" << analyze_block(block) + << "] contains an empty meshbuf" << std::endl; + + material.setFlag(video::EMF_TRILINEAR_FILTER, + m_cache_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, + m_cache_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, + m_cache_anistropic_filter); + material.setFlag(video::EMF_WIREFRAME, + m_control.show_wireframe); + + drawbufs.add(buf, layer); + } } } } diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 153dacf42..9f4223bac 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -78,17 +78,19 @@ void MapblockMeshGenerator::useTile(int index, bool disable_backface_culling) { tile = getNodeTileN(n, p, index, data); if (!data->m_smooth_lighting) - color = encode_light_and_color(light, tile.color, f->light_source); - if (disable_backface_culling) - tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING; - tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; + color = encode_light(light, f->light_source); + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + tile.layers[layer].material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; + if (disable_backface_culling) + tile.layers[layer].material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING; + } } void MapblockMeshGenerator::useDefaultTile(bool set_color) { tile = getNodeTile(n, p, v3s16(0, 0, 0), data); if (set_color && !data->m_smooth_lighting) - color = encode_light_and_color(light, tile.color, f->light_source); + color = encode_light(light, f->light_source); } TileSpec MapblockMeshGenerator::getTile(const v3s16& direction) @@ -106,7 +108,7 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal) vertices[j].Pos = coords[j] + origin; vertices[j].Normal = normal2; if (data->m_smooth_lighting) - vertices[j].Color = blendLight(coords[j], tile.color); + vertices[j].Color = blendLight(coords[j]); else vertices[j].Color = color; if (shade_face) @@ -137,8 +139,7 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, video::SColor colors[6]; if (!data->m_smooth_lighting) { for (int face = 0; face != 6; ++face) { - int tileindex = MYMIN(face, tilecount - 1); - colors[face] = encode_light_and_color(light, tiles[tileindex].color, f->light_source); + colors[face] = encode_light(light, f->light_source); } if (!f->light_source) { applyFacesShading(colors[0], v3f(0, 1, 0)); @@ -240,9 +241,8 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, if (data->m_smooth_lighting) { for (int j = 0; j < 24; ++j) { - int tileindex = MYMIN(j / 4, tilecount - 1); - vertices[j].Color = encode_light_and_color(lights[light_indices[j]], - tiles[tileindex].color, f->light_source); + vertices[j].Color = encode_light(lights[light_indices[j]], + f->light_source); if (!f->light_source) applyFacesShading(vertices[j].Color, vertices[j].Normal); } @@ -289,17 +289,16 @@ u16 MapblockMeshGenerator::blendLight(const v3f &vertex_pos) // Calculates vertex color to be used in mapblock mesh // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) // tile_color - node's tile color -video::SColor MapblockMeshGenerator::blendLight(const v3f &vertex_pos, - video::SColor tile_color) +video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos) { u16 light = blendLight(vertex_pos); - return encode_light_and_color(light, tile_color, f->light_source); + return encode_light(light, f->light_source); } -video::SColor MapblockMeshGenerator::blendLight(const v3f &vertex_pos, - const v3f &vertex_normal, video::SColor tile_color) +video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, + const v3f &vertex_normal) { - video::SColor color = blendLight(vertex_pos, tile_color); + video::SColor color = blendLight(vertex_pos); if (!f->light_source) applyFacesShading(color, vertex_normal); return color; @@ -367,8 +366,13 @@ 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); + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { + TileLayer *layer = ©.layers[layernum]; + if (layer->texture_id == 0) + continue; + if (!layer->has_color) + n.getColor(f, &(layer->color)); + } return copy; } @@ -395,8 +399,8 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing(bool flowing) light = getInteriorLight(ntop, 0, nodedef); } - color_liquid_top = encode_light_and_color(light, tile_liquid_top.color, f->light_source); - color = encode_light_and_color(light, tile_liquid.color, f->light_source); + color_liquid_top = encode_light(light, f->light_source); + color = encode_light(light, f->light_source); } void MapblockMeshGenerator::getLiquidNeighborhood(bool flowing) @@ -547,7 +551,7 @@ void MapblockMeshGenerator::drawLiquidSides(bool flowing) else pos.Y = !top_is_same_liquid ? corner_levels[base.Z][base.X] : 0.5 * BS; if (data->m_smooth_lighting) - color = blendLight(pos, tile_liquid.color); + color = blendLightColor(pos); pos += origin; vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, vertex.v); }; @@ -574,7 +578,7 @@ void MapblockMeshGenerator::drawLiquidTop(bool flowing) int w = corner_resolve[i][1]; vertices[i].Pos.Y += corner_levels[w][u]; if (data->m_smooth_lighting) - vertices[i].Color = blendLight(vertices[i].Pos, tile_liquid_top.color); + vertices[i].Color = blendLightColor(vertices[i].Pos); vertices[i].Pos += origin; } @@ -659,7 +663,9 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() tiles[face] = getTile(g_6dirs[face]); TileSpec glass_tiles[6]; - if (tiles[0].texture && tiles[3].texture && tiles[4].texture) { + if (tiles[1].layers[0].texture && + tiles[2].layers[0].texture && + tiles[3].layers[0].texture) { glass_tiles[0] = tiles[4]; glass_tiles[1] = tiles[0]; glass_tiles[2] = tiles[4]; @@ -763,7 +769,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() // Optionally render internal liquid level defined by param2 // Liquid is textured with 1 tile defined in nodedef 'special_tiles' if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && - f->special_tiles[0].texture) { + f->special_tiles[0].layers[0].texture) { // Internal liquid level has param2 range 0 .. 63, // convert it to -0.5 .. 0.5 float vlev = (param2 / 63.0) * 2.0 - 1.0; @@ -998,7 +1004,8 @@ void MapblockMeshGenerator::drawFencelikeNode() { useDefaultTile(false); TileSpec tile_nocrack = tile; - tile_nocrack.material_flags &= ~MATERIAL_FLAG_CRACK; + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) + tile_nocrack.layers[layer].material_flags &= ~MATERIAL_FLAG_CRACK; // Put wood the right way around in the posts TileSpec tile_rot = tile; @@ -1253,7 +1260,7 @@ void MapblockMeshGenerator::drawMeshNode() // vertex right here. for (int k = 0; k < vertex_count; k++) { video::S3DVertex &vertex = vertices[k]; - vertex.Color = blendLight(vertex.Pos, vertex.Normal, tile.color); + vertex.Color = blendLightColor(vertex.Pos, vertex.Normal); vertex.Pos += origin; } collector->append(tile, vertices, vertex_count, diff --git a/src/content_mapblock.h b/src/content_mapblock.h index c8425024f..6866a4498 100644 --- a/src/content_mapblock.h +++ b/src/content_mapblock.h @@ -60,8 +60,8 @@ public: // lighting void getSmoothLightFrame(); u16 blendLight(const v3f &vertex_pos); - video::SColor blendLight(const v3f &vertex_pos, video::SColor tile_color); - video::SColor blendLight(const v3f &vertex_pos, const v3f &vertex_normal, video::SColor tile_color); + video::SColor blendLightColor(const v3f &vertex_pos); + video::SColor blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal); void useTile(int index, bool disable_backface_culling); void useDefaultTile(bool set_color = true); diff --git a/src/hud.cpp b/src/hud.cpp index f558acf1e..c482912e9 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -687,8 +687,9 @@ void drawItemStack(video::IVideoDriver *driver, assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER); video::SColor c = basecolor; if (imesh->buffer_colors.size() > j) { - std::pair p = imesh->buffer_colors[j]; - c = p.first ? p.second : basecolor; + ItemPartColor *p = &imesh->buffer_colors[j]; + if (p->override_base) + c = p->color; } colorizeMeshBuffer(buf, &c); video::SMaterial &material = buf->getMaterial(); diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index 933dfc32a..0bba644e6 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -323,7 +323,7 @@ void final_color_blend(video::SColor *result, video::SColorf dayLight; get_sunlight_color(&dayLight, daynight_ratio); final_color_blend(result, - encode_light_and_color(light, video::SColor(0xFFFFFFFF), 0), dayLight); + encode_light(light, 0), dayLight); } void final_color_blend(video::SColor *result, @@ -422,12 +422,19 @@ static void getNodeVertexDirs(v3s16 dir, v3s16 *vertex_dirs) struct FastFace { - TileSpec tile; + TileLayer layer; video::S3DVertex vertices[4]; // Precalculated vertices + /*! + * The face is divided into two triangles. If this is true, + * vertices 0 and 2 are connected, othervise vertices 1 and 3 + * are connected. + */ + bool vertex_0_2_connected; + u8 layernum; }; static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, - v3f p, v3s16 dir, v3f scale, std::vector &dest) + v3f p, v3s16 dir, v3f scale, std::vector &dest) { // Position is at the center of the cube. v3f pos = p * BS; @@ -577,27 +584,50 @@ static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, v3f normal(dir.X, dir.Y, dir.Z); - dest.push_back(FastFace()); + u16 li[4] = { li0, li1, li2, li3 }; + u16 day[4]; + u16 night[4]; - FastFace& face = *dest.rbegin(); + for (u8 i = 0; i < 4; i++) { + day[i] = li[i] >> 8; + night[i] = li[i] & 0xFF; + } + + bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2]) + < abs(day[1] - day[3]) + abs(night[1] - night[3]); - 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); + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { + TileLayer *layer = &tile.layers[layernum]; + if (layer->texture_id == 0) + continue; - face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]); - } + dest.push_back(FastFace()); + FastFace& face = *dest.rbegin(); + + for (u8 i = 0; i < 4; i++) { + video::SColor c = encode_light(li[i], tile.emissive_light); + if (!tile.emissive_light) + applyFacesShading(c, normal); - face.tile = tile; + face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]); + } + + /* + Revert triangles for nicer looking gradient if the + brightness of vertices 1 and 3 differ less than + the brightness of vertices 0 and 2. + */ + face.vertex_0_2_connected = vertex_0_2_connected; + + face.layer = *layer; + face.layernum = layernum; + } } /* @@ -663,13 +693,20 @@ TileSpec getNodeTileN(MapNode mn, v3s16 p, u8 tileindex, MeshMakeData *data) { INodeDefManager *ndef = data->m_client->ndef(); const ContentFeatures &f = ndef->get(mn); - TileSpec spec = f.tiles[tileindex]; - if (!spec.has_color) - mn.getColor(f, &spec.color); + TileSpec tile = f.tiles[tileindex]; + TileLayer *top_layer = NULL; + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { + TileLayer *layer = &tile.layers[layernum]; + if (layer->texture_id == 0) + continue; + top_layer = layer; + if (!layer->has_color) + mn.getColor(f, &(layer->color)); + } // Apply temporary crack if (p == data->m_crack_pos_relative) - spec.material_flags |= MATERIAL_FLAG_CRACK; - return spec; + top_layer->material_flags |= MATERIAL_FLAG_CRACK; + return tile; } /* @@ -732,10 +769,9 @@ TileSpec getNodeTile(MapNode mn, v3s16 p, v3s16 dir, MeshMakeData *data) }; u16 tile_index=facedir*16 + dir_i; - TileSpec spec = getNodeTileN(mn, p, dir_to_tile[tile_index], data); - spec.rotation=dir_to_tile[tile_index + 1]; - spec.texture = data->m_client->tsrc()->getTexture(spec.texture_id); - return spec; + TileSpec tile = getNodeTileN(mn, p, dir_to_tile[tile_index], data); + tile.rotation = dir_to_tile[tile_index + 1]; + return tile; } static void getTileInfo( @@ -800,7 +836,9 @@ static void getTileInfo( // eg. water and glass if (equivalent) - tile.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) + tile.layers[layernum].material_flags |= + MATERIAL_FLAG_BACKFACE_CULLING; if (data->m_smooth_lighting == false) { @@ -880,12 +918,7 @@ static void updateFastFaceRow( && next_lights[1] == lights[1] && next_lights[2] == lights[2] && next_lights[3] == lights[3] - && next_tile == tile - && tile.rotation == 0 - && (tile.material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL) - && (tile.material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL) - && tile.color == next_tile.color - && tile.emissive_light == next_tile.emissive_light) { + && next_tile.isTileable(tile)) { next_is_different = false; continuous_tiles_count++; } @@ -988,7 +1021,6 @@ static void updateAllFastFaceRows(MeshMakeData *data, */ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): - m_mesh(new scene::SMesh()), m_minimap_mapblock(NULL), m_client(data->m_client), m_driver(m_client->tsrc()->getDevice()->getVideoDriver()), @@ -1000,6 +1032,8 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_last_daynight_ratio((u32) -1), m_daynight_diffs() { + for (int m = 0; m < MAX_TILE_LAYERS; m++) + m_mesh[m] = new scene::SMesh(); m_enable_shaders = data->m_use_shaders; m_use_tangent_vertices = data->m_use_tangent_vertices; m_enable_vbo = g_settings->getBool("enable_vbo"); @@ -1048,23 +1082,14 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): const u16 indices[] = {0,1,2,2,3,0}; const u16 indices_alternate[] = {0,1,3,2,3,1}; - if(f.tile.texture == NULL) + if (f.layer.texture == NULL) continue; - const u16 *indices_p = indices; + const u16 *indices_p = + f.vertex_0_2_connected ? indices : indices_alternate; - /* - 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 (fabs(f.vertices[0].Color.getLuminance() - - f.vertices[2].Color.getLuminance()) - > fabs(f.vertices[1].Color.getLuminance() - - f.vertices[3].Color.getLuminance())) - indices_p = indices_alternate; - - collector.append(f.tile, f.vertices, 4, indices_p, 6); + collector.append(f.layer, f.vertices, 4, indices_p, 6, + f.layernum); } } @@ -1081,146 +1106,151 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): generator.generate(); } + collector.applyTileColors(); + /* Convert MeshCollector to SMesh */ - for(u32 i = 0; i < collector.prebuffers.size(); i++) - { - PreMeshBuffer &p = collector.prebuffers[i]; - - // Generate animation data - // - Cracks - if(p.tile.material_flags & MATERIAL_FLAG_CRACK) + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) { - // Find the texture name plus ^[crack:N: - std::ostringstream os(std::ios::binary); - os<getTextureName(p.tile.texture_id)<<"^[crack"; - if(p.tile.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) - os<<"o"; // use ^[cracko - os<<":"<<(u32)p.tile.animation_frame_count<<":"; - m_crack_materials.insert(std::make_pair(i, os.str())); - // Replace tile texture with the cracked one - p.tile.texture = m_tsrc->getTextureForMesh( - os.str()+"0", - &p.tile.texture_id); - } - // - Texture animation - if (p.tile.material_flags & MATERIAL_FLAG_ANIMATION) { - // Add to MapBlockMesh in order to animate these tiles - m_animation_tiles[i] = p.tile; - m_animation_frames[i] = 0; - if(g_settings->getBool("desynchronize_mapblock_texture_animation")){ - // Get starting position from noise - m_animation_frame_offsets[i] = 100000 * (2.0 + noise3d( - data->m_blockpos.X, data->m_blockpos.Y, - data->m_blockpos.Z, 0)); - } else { - // Play all synchronized - m_animation_frame_offsets[i] = 0; - } - // Replace tile texture with the first animation frame - FrameSpec animation_frame = p.tile.frames[0]; - p.tile.texture = animation_frame.texture; - } + PreMeshBuffer &p = collector.prebuffers[layer][i]; - 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; + // Generate animation data + // - Cracks + if(p.layer.material_flags & MATERIAL_FLAG_CRACK) + { + // Find the texture name plus ^[crack:N: + std::ostringstream os(std::ios::binary); + os<getTextureName(p.layer.texture_id)<<"^[crack"; + if(p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) + os<<"o"; // use ^[cracko + os<<":"<<(u32)p.layer.animation_frame_count<<":"; + m_crack_materials.insert(std::make_pair(std::pair(layer, i), os.str())); + // Replace tile texture with the cracked one + p.layer.texture = m_tsrc->getTextureForMesh( + os.str()+"0", + &p.layer.texture_id); + } + // - Texture animation + if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { + // Add to MapBlockMesh in order to animate these tiles + m_animation_tiles[std::pair(layer, i)] = p.layer; + m_animation_frames[std::pair(layer, i)] = 0; + if(g_settings->getBool("desynchronize_mapblock_texture_animation")){ + // Get starting position from noise + m_animation_frame_offsets[std::pair(layer, i)] = 100000 * (2.0 + noise3d( + data->m_blockpos.X, data->m_blockpos.Y, + data->m_blockpos.Z, 0)); } else { - vc = &p.vertices[j].Color; + // Play all synchronized + m_animation_frame_offsets[std::pair(layer, i)] = 0; } - 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); + // Replace tile texture with the first animation frame + FrameSpec animation_frame = p.layer.frames[0]; + p.layer.texture = animation_frame.texture; } - } - // Create material - video::SMaterial material; - material.setFlag(video::EMF_LIGHTING, false); - material.setFlag(video::EMF_BACK_FACE_CULLING, true); - material.setFlag(video::EMF_BILINEAR_FILTER, false); - material.setFlag(video::EMF_FOG_ENABLE, true); - material.setTexture(0, p.tile.texture); + 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[std::pair(layer, i)][j] = copy; + + // The sunlight ratio has been stored, + // delete alpha (for the final rendering). + vc->setAlpha(255); + } + } - if (m_enable_shaders) { - material.MaterialType = m_shdrsrc->getShaderInfo(p.tile.shader_id).material; - p.tile.applyMaterialOptionsWithShaders(material); - if (p.tile.normal_texture) { - material.setTexture(1, p.tile.normal_texture); + // Create material + video::SMaterial material; + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_FOG_ENABLE, true); + material.setTexture(0, p.layer.texture); + + if (m_enable_shaders) { + material.MaterialType = m_shdrsrc->getShaderInfo(p.layer.shader_id).material; + p.layer.applyMaterialOptionsWithShaders(material); + if (p.layer.normal_texture) { + material.setTexture(1, p.layer.normal_texture); + } + material.setTexture(2, p.layer.flags_texture); + } else { + p.layer.applyMaterialOptions(material); + } + + scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer]; + + // Create meshbuffer, add to mesh + if (m_use_tangent_vertices) { + scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); + // Set material + buf->Material = material; + // Add to mesh + mesh->addMeshBuffer(buf); + // Mesh grabbed it + buf->drop(); + buf->append(&p.tangent_vertices[0], p.tangent_vertices.size(), + &p.indices[0], p.indices.size()); + } else { + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + // Set material + buf->Material = material; + // Add to mesh + mesh->addMeshBuffer(buf); + // Mesh grabbed it + buf->drop(); + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); } - material.setTexture(2, p.tile.flags_texture); - } else { - p.tile.applyMaterialOptions(material); } - scene::SMesh *mesh = (scene::SMesh *)m_mesh; - // Create meshbuffer, add to mesh + /* + Do some stuff to the mesh + */ + m_camera_offset = camera_offset; + translateMesh(m_mesh[layer], + intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); + if (m_use_tangent_vertices) { - scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); - // Set material - buf->Material = material; - // Add to mesh - mesh->addMeshBuffer(buf); - // Mesh grabbed it - buf->drop(); - buf->append(&p.tangent_vertices[0], p.tangent_vertices.size(), - &p.indices[0], p.indices.size()); - } else { - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - // Set material - buf->Material = material; - // Add to mesh - mesh->addMeshBuffer(buf); - // Mesh grabbed it - buf->drop(); - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); + scene::IMeshManipulator* meshmanip = + m_client->getSceneManager()->getMeshManipulator(); + meshmanip->recalculateTangents(m_mesh[layer], true, false, false); } - } - - /* - Do some stuff to the mesh - */ - m_camera_offset = camera_offset; - translateMesh(m_mesh, - intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); - if (m_use_tangent_vertices) { - scene::IMeshManipulator* meshmanip = - m_client->getSceneManager()->getMeshManipulator(); - meshmanip->recalculateTangents(m_mesh, true, false, false); - } - - if (m_mesh) - { + if (m_mesh[layer]) + { #if 0 - // Usually 1-700 faces and 1-7 materials - std::cout<<"Updated MapBlock has "<getMeshBufferCount() - <<" materials (meshbuffers)"<getMeshBufferCount() + <<" materials (meshbuffers)"<setHardwareMappingHint(scene::EHM_STATIC); + // Use VBO for mesh (this just would set this for ever buffer) + if (m_enable_vbo) { + m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC); + } } } @@ -1235,14 +1265,15 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): MapBlockMesh::~MapBlockMesh() { - if (m_enable_vbo && m_mesh) { - for (u32 i = 0; i < m_mesh->getMeshBufferCount(); i++) { - scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i); - m_driver->removeHardwareBuffer(buf); - } + for (int m = 0; m < MAX_TILE_LAYERS; m++) { + if (m_enable_vbo && m_mesh[m]) + for (u32 i = 0; i < m_mesh[m]->getMeshBufferCount(); i++) { + scene::IMeshBuffer *buf = m_mesh[m]->getMeshBuffer(i); + m_driver->removeHardwareBuffer(buf); + } + m_mesh[m]->drop(); + m_mesh[m] = NULL; } - m_mesh->drop(); - m_mesh = NULL; delete m_minimap_mapblock; } @@ -1259,9 +1290,10 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat // Cracks if(crack != m_last_crack) { - for (UNORDERED_MAP::iterator i = m_crack_materials.begin(); - i != m_crack_materials.end(); ++i) { - scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); + for (std::map, std::string>::iterator i = + m_crack_materials.begin(); i != m_crack_materials.end(); ++i) { + scene::IMeshBuffer *buf = m_mesh[i->first.first]-> + getMeshBuffer(i->first.second); std::string basename = i->second; // Create new texture name from original @@ -1274,10 +1306,10 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat // If the current material is also animated, // update animation info - UNORDERED_MAP::iterator anim_iter = - m_animation_tiles.find(i->first); + std::map, TileLayer>::iterator anim_iter = + m_animation_tiles.find(i->first); if (anim_iter != m_animation_tiles.end()){ - TileSpec &tile = anim_iter->second; + TileLayer &tile = anim_iter->second; tile.texture = new_texture; tile.texture_id = new_texture_id; // force animation update @@ -1289,9 +1321,9 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat } // Texture animation - for (UNORDERED_MAP::iterator i = m_animation_tiles.begin(); - i != m_animation_tiles.end(); ++i) { - const TileSpec &tile = i->second; + for (std::map, TileLayer>::iterator i = + m_animation_tiles.begin(); i != m_animation_tiles.end(); ++i) { + const TileLayer &tile = i->second; // Figure out current frame int frameoffset = m_animation_frame_offsets[i->first]; int frame = (int)(time * 1000 / tile.animation_frame_length_ms @@ -1302,7 +1334,8 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat m_animation_frames[i->first] = frame; - scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); + scene::IMeshBuffer *buf = m_mesh[i->first.first]-> + getMeshBuffer(i->first.second); FrameSpec animation_frame = tile.frames[frame]; buf->getMaterial().setTexture(0, animation_frame.texture); @@ -1318,22 +1351,24 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat if(!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) { // Force reload mesh to VBO - if (m_enable_vbo) { - m_mesh->setDirty(); - } + if (m_enable_vbo) + for (int m = 0; m < MAX_TILE_LAYERS; m++) + m_mesh[m]->setDirty(); video::SColorf day_color; get_sunlight_color(&day_color, daynight_ratio); - for(std::map >::iterator + for(std::map, std::map >::iterator i = m_daynight_diffs.begin(); i != m_daynight_diffs.end(); ++i) { - scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); + scene::IMeshBuffer *buf = m_mesh[i->first.first]-> + getMeshBuffer(i->first.second); video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); for(std::map::iterator j = i->second.begin(); j != i->second.end(); ++j) { - final_color_blend(&(vertices[j->first].Color), j->second, day_color); + final_color_blend(&(vertices[j->first].Color), + j->second, day_color); } } m_last_daynight_ratio = daynight_ratio; @@ -1345,9 +1380,12 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat void MapBlockMesh::updateCameraOffset(v3s16 camera_offset) { if (camera_offset != m_camera_offset) { - translateMesh(m_mesh, intToFloat(m_camera_offset-camera_offset, BS)); - if (m_enable_vbo) { - m_mesh->setDirty(); + for (u8 layer = 0; layer < 2; layer++) { + translateMesh(m_mesh[layer], + intToFloat(m_camera_offset - camera_offset, BS)); + if (m_enable_vbo) { + m_mesh[layer]->setDirty(); + } } m_camera_offset = camera_offset; } @@ -1360,16 +1398,30 @@ void MapBlockMesh::updateCameraOffset(v3s16 camera_offset) void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices) +{ + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { + const TileLayer *layer = &tile.layers[layernum]; + if (layer->texture_id == 0) + continue; + append(*layer, vertices, numVertices, indices, numIndices, + layernum); + } +} + +void MeshCollector::append(const TileLayer &layer, + const video::S3DVertex *vertices, u32 numVertices, + const u16 *indices, u32 numIndices, u8 layernum) { if (numIndices > 65535) { dstream<<"FIXME: MeshCollector::append() called with numIndices="< *buffers = &prebuffers[layernum]; PreMeshBuffer *p = NULL; - for (u32 i = 0; i < prebuffers.size(); i++) { - PreMeshBuffer &pp = prebuffers[i]; - if (pp.tile != tile) + for (u32 i = 0; i < buffers->size(); i++) { + PreMeshBuffer &pp = (*buffers)[i]; + if (pp.layer != layer) continue; if (pp.indices.size() + numIndices > 65535) continue; @@ -1380,9 +1432,9 @@ void MeshCollector::append(const TileSpec &tile, if (p == NULL) { PreMeshBuffer pp; - pp.tile = tile; - prebuffers.push_back(pp); - p = &prebuffers[prebuffers.size() - 1]; + pp.layer = layer; + buffers->push_back(pp); + p = &(*buffers)[buffers->size() - 1]; } u32 vertex_count; @@ -1416,16 +1468,31 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices, v3f pos, video::SColor c, u8 light_source) +{ + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { + const TileLayer *layer = &tile.layers[layernum]; + if (layer->texture_id == 0) + continue; + append(*layer, vertices, numVertices, indices, numIndices, pos, + c, light_source, layernum); + } +} + +void MeshCollector::append(const TileLayer &layer, + const video::S3DVertex *vertices, u32 numVertices, + const u16 *indices, u32 numIndices, + v3f pos, video::SColor c, u8 light_source, u8 layernum) { if (numIndices > 65535) { dstream<<"FIXME: MeshCollector::append() called with numIndices="< *buffers = &prebuffers[layernum]; PreMeshBuffer *p = NULL; - for (u32 i = 0; i < prebuffers.size(); i++) { - PreMeshBuffer &pp = prebuffers[i]; - if(pp.tile != tile) + for (u32 i = 0; i < buffers->size(); i++) { + PreMeshBuffer &pp = (*buffers)[i]; + if(pp.layer != layer) continue; if(pp.indices.size() + numIndices > 65535) continue; @@ -1436,9 +1503,9 @@ void MeshCollector::append(const TileSpec &tile, if (p == NULL) { PreMeshBuffer pp; - pp.tile = tile; - prebuffers.push_back(pp); - p = &prebuffers[prebuffers.size() - 1]; + pp.layer = layer; + buffers->push_back(pp); + p = &(*buffers)[buffers->size() - 1]; } video::SColor original_c = c; @@ -1473,14 +1540,49 @@ void MeshCollector::append(const TileSpec &tile, } } -video::SColor encode_light_and_color(u16 light, const video::SColor &color, - u8 emissive_light) +void MeshCollector::applyTileColors() +{ + if (m_use_tangent_vertices) + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + std::vector *p = &prebuffers[layer]; + for (std::vector::iterator it = p->begin(); + it != p->end(); ++it) { + video::SColor tc = it->layer.color; + if (tc == video::SColor(0xFFFFFFFF)) + continue; + for (u32 index = 0; index < it->tangent_vertices.size(); index++) { + video::SColor *c = &it->tangent_vertices[index].Color; + c->set(c->getAlpha(), c->getRed() * tc.getRed() / 255, + c->getGreen() * tc.getGreen() / 255, + c->getBlue() * tc.getBlue() / 255); + } + } + } + else + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + std::vector *p = &prebuffers[layer]; + for (std::vector::iterator it = p->begin(); + it != p->end(); ++it) { + video::SColor tc = it->layer.color; + if (tc == video::SColor(0xFFFFFFFF)) + continue; + for (u32 index = 0; index < it->vertices.size(); index++) { + video::SColor *c = &it->vertices[index].Color; + c->set(c->getAlpha(), c->getRed() * tc.getRed() / 255, + c->getGreen() * tc.getGreen() / 255, + c->getBlue() * tc.getBlue() / 255); + } + } + } +} + +video::SColor encode_light(u16 light, u8 emissive_light) { // Get components - f32 day = (light & 0xff) / 255.0f; - f32 night = (light >> 8) / 255.0f; + u32 day = (light & 0xff); + u32 night = (light >> 8); // Add emissive light - night += emissive_light * 0.01f; + night += emissive_light * 2.5f; if (night > 255) night = 255; // Since we don't know if the day light is sunlight or @@ -1490,15 +1592,14 @@ video::SColor encode_light_and_color(u16 light, const video::SColor &color, day = 0; else day = day - night; - f32 sum = day + night; + u32 sum = day + night; // Ratio of sunlight: - float r; + u32 r; if (sum > 0) - r = day / sum; + r = day * 255 / 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()); + return video::SColor(r, b, b, b); } diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index 25c699e1c..f32df3958 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -108,7 +108,12 @@ public: scene::IMesh *getMesh() { - return m_mesh; + return m_mesh[0]; + } + + scene::IMesh *getMesh(u8 layer) + { + return m_mesh[layer]; } MinimapMapblock *moveMinimapMapblock() @@ -132,7 +137,7 @@ public: void updateCameraOffset(v3s16 camera_offset); private: - scene::IMesh *m_mesh; + scene::IMesh *m_mesh[MAX_TILE_LAYERS]; MinimapMapblock *m_minimap_mapblock; Client *m_client; video::IVideoDriver *m_driver; @@ -150,20 +155,23 @@ private: // Animation info: cracks // Last crack value passed to animate() int m_last_crack; - // Maps mesh buffer (i.e. material) indices to base texture names - UNORDERED_MAP m_crack_materials; + // Maps mesh and mesh buffer (i.e. material) indices to base texture names + std::map, std::string> m_crack_materials; // Animation info: texture animationi - // Maps meshbuffers to TileSpecs - UNORDERED_MAP m_animation_tiles; - UNORDERED_MAP m_animation_frames; // last animation frame - UNORDERED_MAP m_animation_frame_offsets; + // Maps mesh and mesh buffer indices to TileSpecs + // Keys are pairs of (mesh index, buffer index in the mesh) + std::map, TileLayer> m_animation_tiles; + std::map, int> m_animation_frames; // last animation frame + std::map, int> m_animation_frame_offsets; // Animation info: day/night transitions // Last daynight_ratio value passed to animate() u32 m_last_daynight_ratio; - // For each meshbuffer, stores pre-baked colors of sunlit vertices - std::map > m_daynight_diffs; + // For each mesh and mesh buffer, stores pre-baked colors + // of sunlit vertices + // Keys are pairs of (mesh index, buffer index in the mesh) + std::map, std::map > m_daynight_diffs; // Camera offset info -> do we have to translate the mesh? v3s16 m_camera_offset; @@ -176,7 +184,7 @@ private: */ struct PreMeshBuffer { - TileSpec tile; + TileLayer layer; std::vector indices; std::vector vertices; std::vector tangent_vertices; @@ -184,7 +192,7 @@ struct PreMeshBuffer struct MeshCollector { - std::vector prebuffers; + std::vector prebuffers[MAX_TILE_LAYERS]; bool m_use_tangent_vertices; MeshCollector(bool use_tangent_vertices): @@ -193,27 +201,38 @@ struct MeshCollector } void append(const TileSpec &material, + const video::S3DVertex *vertices, u32 numVertices, + const u16 *indices, u32 numIndices); + void append(const TileLayer &material, const video::S3DVertex *vertices, u32 numVertices, - const u16 *indices, u32 numIndices); + const u16 *indices, u32 numIndices, u8 layernum); void append(const TileSpec &material, + const video::S3DVertex *vertices, u32 numVertices, + const u16 *indices, u32 numIndices, v3f pos, + video::SColor c, u8 light_source); + void append(const TileLayer &material, const video::S3DVertex *vertices, u32 numVertices, - const u16 *indices, u32 numIndices, - v3f pos, video::SColor c, u8 light_source); + const u16 *indices, u32 numIndices, v3f pos, + video::SColor c, u8 light_source, u8 layernum); + /*! + * Colorizes all vertices in the collector. + */ + void applyTileColors(); }; /*! - * Encodes light and color of a node. + * Encodes light of a node. * The result is not the final color, but a * half-baked vertex color. + * You have to multiply the resulting color + * with the node's 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); +video::SColor encode_light(u16 light, u8 emissive_light); // Compute light at node u16 getInteriorLight(MapNode n, s32 increment, INodeDefManager *ndef); diff --git a/src/mesh.cpp b/src/mesh.cpp index a79264ef0..d776f6185 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -385,48 +385,50 @@ void recalculateBoundingBox(scene::IMesh *src_mesh) src_mesh->setBoundingBox(bbox); } -scene::IMesh* cloneMesh(scene::IMesh *src_mesh) +scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) +{ + scene::IMeshBuffer *clone = NULL; + switch (mesh_buffer->getVertexType()) { + case video::EVT_STANDARD: { + video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices(); + u16 *indices = (u16*) mesh_buffer->getIndices(); + scene::SMeshBuffer *temp_buf = new scene::SMeshBuffer(); + temp_buf->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + return temp_buf; + break; + } + case video::EVT_2TCOORDS: { + video::S3DVertex2TCoords *v = + (video::S3DVertex2TCoords *) mesh_buffer->getVertices(); + u16 *indices = (u16*) mesh_buffer->getIndices(); + scene::SMeshBufferTangents *temp_buf = new scene::SMeshBufferTangents(); + temp_buf->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + break; + } + case video::EVT_TANGENTS: { + video::S3DVertexTangents *v = + (video::S3DVertexTangents *) mesh_buffer->getVertices(); + u16 *indices = (u16*) mesh_buffer->getIndices(); + scene::SMeshBufferTangents *temp_buf = new scene::SMeshBufferTangents(); + temp_buf->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + break; + } + } + return clone; +} + +scene::SMesh* cloneMesh(scene::IMesh *src_mesh) { scene::SMesh* dst_mesh = new scene::SMesh(); for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { - scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j); - switch (buf->getVertexType()) { - case video::EVT_STANDARD: { - video::S3DVertex *v = - (video::S3DVertex *) buf->getVertices(); - u16 *indices = (u16*)buf->getIndices(); - scene::SMeshBuffer *temp_buf = new scene::SMeshBuffer(); - temp_buf->append(v, buf->getVertexCount(), - indices, buf->getIndexCount()); - dst_mesh->addMeshBuffer(temp_buf); - temp_buf->drop(); - break; - } - case video::EVT_2TCOORDS: { - video::S3DVertex2TCoords *v = - (video::S3DVertex2TCoords *) buf->getVertices(); - u16 *indices = (u16*)buf->getIndices(); - scene::SMeshBufferTangents *temp_buf = - new scene::SMeshBufferTangents(); - temp_buf->append(v, buf->getVertexCount(), - indices, buf->getIndexCount()); - dst_mesh->addMeshBuffer(temp_buf); - temp_buf->drop(); - break; - } - case video::EVT_TANGENTS: { - video::S3DVertexTangents *v = - (video::S3DVertexTangents *) buf->getVertices(); - u16 *indices = (u16*)buf->getIndices(); - scene::SMeshBufferTangents *temp_buf = - new scene::SMeshBufferTangents(); - temp_buf->append(v, buf->getVertexCount(), - indices, buf->getIndexCount()); - dst_mesh->addMeshBuffer(temp_buf); - temp_buf->drop(); - break; - } - } + scene::IMeshBuffer *temp_buf = cloneMeshBuffer( + src_mesh->getMeshBuffer(j)); + dst_mesh->addMeshBuffer(temp_buf); + temp_buf->drop(); + } return dst_mesh; } diff --git a/src/mesh.h b/src/mesh.h index bcf0d771c..0e946caab 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -82,11 +82,16 @@ void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir); void rotateMeshXYby (scene::IMesh *mesh, f64 degrees); void rotateMeshXZby (scene::IMesh *mesh, f64 degrees); void rotateMeshYZby (scene::IMesh *mesh, f64 degrees); + +/* + * Clone the mesh buffer. + */ +scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer); /* Clone the mesh. */ -scene::IMesh* cloneMesh(scene::IMesh *src_mesh); +scene::SMesh* cloneMesh(scene::IMesh *src_mesh); /* Convert nodeboxes to mesh. Each tile goes into a different buffer. diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index ea532d9e0..b586fa70b 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -148,9 +148,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Add node and tile color and palette Fix plantlike visual_scale being applied squared and add compatibility with pre-30 clients by sending sqrt(visual_scale) + PROTOCOL VERSION 31: + Add tile overlay */ -#define LATEST_PROTOCOL_VERSION 30 +#define LATEST_PROTOCOL_VERSION 31 // Server's supported network protocol range #define SERVER_PROTOCOL_VERSION_MIN 24 diff --git a/src/nodedef.cpp b/src/nodedef.cpp index ce3e378a0..db28325aa 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -378,13 +378,13 @@ void ContentFeatures::reset() void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const { - if (protocol_version < 30) { + if (protocol_version < 31) { serializeOld(os, protocol_version); return; } // version - writeU8(os, 9); + writeU8(os, 10); // general os << serializeString(name); @@ -404,6 +404,8 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, 6); for (u32 i = 0; i < 6; i++) tiledef[i].serialize(os, protocol_version); + for (u32 i = 0; i < 6; i++) + tiledef_overlay[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); @@ -492,7 +494,7 @@ void ContentFeatures::deSerialize(std::istream &is) if (version < 9) { deSerializeOld(is, version); return; - } else if (version > 9) { + } else if (version > 10) { throw SerializationError("unsupported ContentFeatures version"); } @@ -516,6 +518,9 @@ void ContentFeatures::deSerialize(std::istream &is) throw SerializationError("unsupported tile count"); for (u32 i = 0; i < 6; i++) tiledef[i].deSerialize(is, version, drawtype); + if (version >= 10) + for (u32 i = 0; i < 6; i++) + tiledef_overlay[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++) @@ -581,7 +586,7 @@ void ContentFeatures::deSerialize(std::istream &is) } #ifndef SERVER -void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, +void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileLayer *tile, TileDef *tiledef, u32 shader_id, bool use_normal_texture, bool backface_culling, u8 material_type) { @@ -774,14 +779,18 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc // Tiles (fill in f->tiles[]) for (u16 j = 0; j < 6; j++) { - fillTileAttribs(tsrc, &tiles[j], &tdef[j], tile_shader[j], + fillTileAttribs(tsrc, &tiles[j].layers[0], &tdef[j], tile_shader[j], tsettings.use_normal_texture, tiledef[j].backface_culling, material_type); + if (tiledef_overlay[j].name!="") + fillTileAttribs(tsrc, &tiles[j].layers[1], &tiledef_overlay[j], + tile_shader[j], tsettings.use_normal_texture, + 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], + fillTileAttribs(tsrc, &special_tiles[j].layers[0], &tiledef_special[j], tile_shader[j], tsettings.use_normal_texture, tiledef_special[j].backface_culling, material_type); } @@ -1538,8 +1547,19 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const if (protocol_version < 30 && drawtype == NDT_PLANTLIKE) compatible_visual_scale = sqrt(visual_scale); + TileDef compatible_tiles[6]; + for (u8 i = 0; i < 6; i++) { + compatible_tiles[i] = tiledef[i]; + if (tiledef_overlay[i].name != "") { + std::stringstream s; + s << "(" << tiledef[i].name << ")^(" << tiledef_overlay[i].name + << ")"; + compatible_tiles[i].name = s.str(); + } + } + // Protocol >= 24 - if (protocol_version < 30) { + if (protocol_version < 31) { writeU8(os, protocol_version < 27 ? 7 : 8); os << serializeString(name); @@ -1553,7 +1573,7 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const writeF1000(os, compatible_visual_scale); writeU8(os, 6); for (u32 i = 0; i < 6; i++) - tiledef[i].serialize(os, protocol_version); + compatible_tiles[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); diff --git a/src/nodedef.h b/src/nodedef.h index 83968ce27..4d3bacc6c 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -280,6 +280,8 @@ struct ContentFeatures #endif float visual_scale; // Misc. scale parameter TileDef tiledef[6]; + // These will be drawn over the base tiles. + TileDef tiledef_overlay[6]; TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid // If 255, the node is opaque. // Otherwise it uses texture alpha. @@ -405,7 +407,7 @@ struct ContentFeatures } #ifndef SERVER - void fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef, + void fillTileAttribs(ITextureSource *tsrc, TileLayer *tile, TileDef *tiledef, u32 shader_id, bool use_normal_texture, bool backface_culling, u8 material_type); void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, diff --git a/src/particles.cpp b/src/particles.cpp index e1f292fb6..7f406d874 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -620,7 +620,7 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, { // Texture u8 texid = myrand_range(0, 5); - const TileSpec &tile = f.tiles[texid]; + const TileLayer &tile = f.tiles[texid].layers[0]; video::ITexture *texture; struct TileAnimationParams anim; anim.type = TAT_NONE; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 8dfb851e6..573347b4c 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -426,6 +426,34 @@ ContentFeatures read_content_features(lua_State *L, int index) } lua_pop(L, 1); + // overlay_tiles = {} + lua_getfield(L, index, "overlay_tiles"); + if (lua_istable(L, -1)) { + int table = lua_gettop(L); + lua_pushnil(L); + int i = 0; + while (lua_next(L, table) != 0) { + // Read tiledef from value + f.tiledef_overlay[i] = read_tiledef(L, -1, f.drawtype); + // removes value, keeps key for next iteration + lua_pop(L, 1); + i++; + if (i == 6) { + lua_pop(L, 1); + break; + } + } + // Copy last value to all remaining textures + if (i >= 1) { + TileDef lasttile = f.tiledef_overlay[i - 1]; + while (i < 6) { + f.tiledef_overlay[i] = lasttile; + i++; + } + } + } + lua_pop(L, 1); + // special_tiles = {} lua_getfield(L, index, "special_tiles"); // If nil, try the deprecated name "special_materials" instead diff --git a/src/wieldmesh.cpp b/src/wieldmesh.cpp index 40af0be5f..2b23d9e02 100644 --- a/src/wieldmesh.cpp +++ b/src/wieldmesh.cpp @@ -235,27 +235,16 @@ WieldMeshSceneNode::~WieldMeshSceneNode() g_extrusion_mesh_cache = NULL; } -void WieldMeshSceneNode::setCube(const TileSpec tiles[6], +void WieldMeshSceneNode::setCube(const ContentFeatures &f, v3f wield_scale, ITextureSource *tsrc) { scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube(); - changeToMesh(cubemesh); + scene::SMesh *copy = cloneMesh(cubemesh); cubemesh->drop(); - + postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors); + changeToMesh(copy); + copy->drop(); m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR); - - // Customize materials - for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) { - assert(i < 6); - video::SMaterial &material = m_meshnode->getMaterial(i); - if (tiles[i].animation_frame_count == 1) { - material.setTexture(0, tiles[i].texture); - } else { - FrameSpec animation_frame = tiles[i].frames[0]; - material.setTexture(0, animation_frame.texture); - } - tiles[i].applyMaterialOptions(material); - } } void WieldMeshSceneNode::setExtruded(const std::string &imagename, @@ -274,8 +263,10 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, dim = core::dimension2d(dim.Width, frame_height); } scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim); - changeToMesh(mesh); + scene::SMesh *copy = cloneMesh(mesh); mesh->drop(); + changeToMesh(copy); + copy->drop(); m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED); @@ -321,12 +312,12 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) // Color-related m_colors.clear(); - video::SColor basecolor = idef->getItemstackColor(item, client); + m_base_color = idef->getItemstackColor(item, client); // If wield_image is defined, it overrides everything else if (def.wield_image != "") { setExtruded(def.wield_image, def.wield_scale, tsrc, 1); - m_colors.push_back(basecolor); + m_colors.push_back(ItemPartColor()); return; } // Handle nodes @@ -334,66 +325,50 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client) else if (def.type == ITEM_NODE) { if (f.mesh_ptr[0]) { // e.g. mesh nodes and nodeboxes - changeToMesh(f.mesh_ptr[0]); - // mesh_ptr[0] is pre-scaled by BS * f->visual_scale + scene::SMesh *mesh = cloneMesh(f.mesh_ptr[0]); + postProcessNodeMesh(mesh, f, m_enable_shaders, true, + &m_material_type, &m_colors); + changeToMesh(mesh); + mesh->drop(); + // mesh is pre-scaled by BS * f->visual_scale m_meshnode->setScale( def.wield_scale * WIELD_SCALE_FACTOR / (BS * f.visual_scale)); } else if (f.drawtype == NDT_AIRLIKE) { changeToMesh(NULL); } else if (f.drawtype == NDT_PLANTLIKE) { - setExtruded(tsrc->getTextureName(f.tiles[0].texture_id), def.wield_scale, tsrc, f.tiles[0].animation_frame_count); + setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id), + def.wield_scale, tsrc, + f.tiles[0].layers[0].animation_frame_count); } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) { - setCube(f.tiles, def.wield_scale, tsrc); + setCube(f, def.wield_scale, tsrc); } else { MeshMakeData mesh_make_data(client, false); MapNode mesh_make_node(id, 255, 0); mesh_make_data.fillSingleNode(&mesh_make_node); MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0)); - changeToMesh(mapblock_mesh.getMesh()); - translateMesh(m_meshnode->getMesh(), v3f(-BS, -BS, -BS)); + scene::SMesh *mesh = cloneMesh(mapblock_mesh.getMesh()); + translateMesh(mesh, v3f(-BS, -BS, -BS)); + postProcessNodeMesh(mesh, f, m_enable_shaders, true, + &m_material_type, &m_colors); + changeToMesh(mesh); + mesh->drop(); m_meshnode->setScale( def.wield_scale * WIELD_SCALE_FACTOR / (BS * f.visual_scale)); } u32 material_count = m_meshnode->getMaterialCount(); - if (material_count > 6) { - errorstream << "WieldMeshSceneNode::setItem: Invalid material " - "count " << material_count << ", truncating to 6" << std::endl; - 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 = (tile->animation_frame_count > 1); - if (animated) { - FrameSpec animation_frame = tile->frames[0]; - material.setTexture(0, animation_frame.texture); - } else { - material.setTexture(0, tile->texture); - } - m_colors.push_back(tile->has_color ? tile->color : basecolor); - material.MaterialType = m_material_type; - if (m_enable_shaders) { - if (tile->normal_texture) { - if (animated) { - FrameSpec animation_frame = tile->frames[0]; - material.setTexture(1, animation_frame.normal_texture); - } else { - material.setTexture(1, tile->normal_texture); - } - } - material.setTexture(2, tile->flags_texture); - } } return; } else if (def.inventory_image != "") { setExtruded(def.inventory_image, def.wield_scale, tsrc, 1); - m_colors.push_back(basecolor); + m_colors.push_back(ItemPartColor()); return; } @@ -413,9 +388,9 @@ void WieldMeshSceneNode::setColor(video::SColor c) 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 bc(m_base_color); + if ((m_colors.size() > j) && (m_colors[j].override_base)) + bc = m_colors[j].color; video::SColor buffercolor(255, bc.getRed() * red / 255, bc.getGreen() * green / 255, @@ -439,19 +414,7 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) m_meshnode->setMesh(dummymesh); dummymesh->drop(); // m_meshnode grabbed it } else { - if (m_lighting) { - m_meshnode->setMesh(mesh); - } else { - /* - Lighting is disabled, this means the caller can (and probably will) - call setColor later. We therefore need to clone the mesh so that - setColor will only modify this scene node's mesh, not others'. - */ - scene::IMeshManipulator *meshmanip = SceneManager->getMeshManipulator(); - scene::IMesh *new_mesh = meshmanip->createMeshCopy(mesh); - m_meshnode->setMesh(new_mesh); - new_mesh->drop(); // m_meshnode grabbed it - } + m_meshnode->setMesh(mesh); } m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting); @@ -475,24 +438,24 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) g_extrusion_mesh_cache->grab(); } - scene::IMesh *mesh; + scene::SMesh *mesh; // If inventory_image is defined, it overrides everything else if (def.inventory_image != "") { mesh = getExtrudedMesh(tsrc, def.inventory_image); - result->mesh = mesh; - result->buffer_colors.push_back( - std::pair(false, video::SColor(0xFFFFFFFF))); + result->buffer_colors.push_back(ItemPartColor()); } else if (def.type == ITEM_NODE) { if (f.mesh_ptr[0]) { mesh = cloneMesh(f.mesh_ptr[0]); scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); } else if (f.drawtype == NDT_PLANTLIKE) { mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.tiles[0].texture_id)); + tsrc->getTextureName(f.tiles[0].layers[0].texture_id)); } 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()); + scene::IMesh *cube = g_extrusion_mesh_cache->createCube(); + mesh = cloneMesh(cube); + cube->drop(); scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); } else { MeshMakeData mesh_make_data(client, false); @@ -519,32 +482,27 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) u32 mc = mesh->getMeshBufferCount(); for (u32 i = 0; i < mc; ++i) { - const TileSpec *tile = &(f.tiles[i]); scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - result->buffer_colors.push_back( - std::pair(tile->has_color, tile->color)); - 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 (tile->animation_frame_count > 1) { - FrameSpec animation_frame = tile->frames[0]; - material.setTexture(0, animation_frame.texture); - } else { - material.setTexture(0, tile->texture); - } } rotateMeshXZby(mesh, -45); rotateMeshYZby(mesh, -30); - result->mesh = mesh; + + postProcessNodeMesh(mesh, f, false, false, NULL, + &result->buffer_colors); } + result->mesh = mesh; } -scene::IMesh * getExtrudedMesh(ITextureSource *tsrc, + + +scene::SMesh * getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename) { video::ITexture *texture = tsrc->getTextureForMesh(imagename); @@ -553,7 +511,9 @@ scene::IMesh * getExtrudedMesh(ITextureSource *tsrc, } core::dimension2d dim = texture->getSize(); - scene::IMesh *mesh = cloneMesh(g_extrusion_mesh_cache->create(dim)); + scene::IMesh *original = g_extrusion_mesh_cache->create(dim); + scene::SMesh *mesh = cloneMesh(original); + original->drop(); // Customize material video::SMaterial &material = mesh->getMeshBuffer(0)->getMaterial(); @@ -569,3 +529,57 @@ scene::IMesh * getExtrudedMesh(ITextureSource *tsrc, return mesh; } + +void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, + bool use_shaders, bool set_material, video::E_MATERIAL_TYPE *mattype, + std::vector *colors) +{ + u32 mc = mesh->getMeshBufferCount(); + // Allocate colors for existing buffers + colors->clear(); + for (u32 i = 0; i < mc; ++i) + colors->push_back(ItemPartColor()); + + for (u32 i = 0; i < mc; ++i) { + const TileSpec *tile = &(f.tiles[i]); + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { + const TileLayer *layer = &tile->layers[layernum]; + if (layer->texture_id == 0) + continue; + if (layernum != 0) { + scene::IMeshBuffer *copy = cloneMeshBuffer(buf); + copy->getMaterial() = buf->getMaterial(); + mesh->addMeshBuffer(copy); + copy->drop(); + buf = copy; + colors->push_back( + ItemPartColor(layer->has_color, layer->color)); + } else { + (*colors)[i] = ItemPartColor(layer->has_color, layer->color); + } + video::SMaterial &material = buf->getMaterial(); + if (set_material) + layer->applyMaterialOptions(material); + if (mattype) { + material.MaterialType = *mattype; + } + if (layer->animation_frame_count > 1) { + FrameSpec animation_frame = layer->frames[0]; + material.setTexture(0, animation_frame.texture); + } else { + material.setTexture(0, layer->texture); + } + if (use_shaders) { + if (layer->normal_texture) { + if (layer->animation_frame_count > 1) { + FrameSpec animation_frame = layer->frames[0]; + material.setTexture(1, animation_frame.normal_texture); + } else + material.setTexture(1, layer->normal_texture); + } + material.setTexture(2, layer->flags_texture); + } + } + } +} diff --git a/src/wieldmesh.h b/src/wieldmesh.h index d3946b4e0..c98b469d9 100644 --- a/src/wieldmesh.h +++ b/src/wieldmesh.h @@ -26,17 +26,41 @@ with this program; if not, write to the Free Software Foundation, Inc., struct ItemStack; class Client; class ITextureSource; -struct TileSpec; +struct ContentFeatures; + +/*! + * Holds color information of an item mesh's buffer. + */ +struct ItemPartColor { + /*! + * If this is false, the global base color of the item + * will be used instead of the specific color of the + * buffer. + */ + bool override_base; + /*! + * The color of the buffer. + */ + video::SColor color; + + ItemPartColor(): + override_base(false), + color(0) + {} + + ItemPartColor(bool override, video::SColor color): + override_base(override), + color(color) + {} +}; struct ItemMesh { scene::IMesh *mesh; /*! * Stores the color of each mesh buffer. - * If the boolean is true, the color is fixed, else - * palettes can modify it. */ - std::vector > buffer_colors; + std::vector buffer_colors; ItemMesh() : mesh(NULL), buffer_colors() {} }; @@ -51,7 +75,8 @@ public: s32 id = -1, bool lighting = false); virtual ~WieldMeshSceneNode(); - void setCube(const TileSpec tiles[6], v3f wield_scale, ITextureSource *tsrc); + void setCube(const ContentFeatures &f, v3f wield_scale, + ITextureSource *tsrc); void setExtruded(const std::string &imagename, v3f wield_scale, ITextureSource *tsrc, u8 num_frames); void setItem(const ItemStack &item, Client *client); @@ -84,7 +109,12 @@ private: * Stores the colors of the mesh's mesh buffers. * This does not include lighting. */ - std::vector m_colors; + std::vector m_colors; + /*! + * The base color of this mesh. This is the default + * for all mesh buffers. + */ + video::SColor m_base_color; // Bounding box culling is disabled for this type of scene node, // so this variable is just required so we can implement @@ -94,5 +124,16 @@ private: void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); -scene::IMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename); +scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename); + +/*! + * Applies overlays, textures and optionally materials to the given mesh and + * extracts tile colors for colorization. + * \param mattype overrides the buffer's material type, but can also + * be NULL to leave the original material. + * \param colors returns the colors of the mesh buffers in the mesh. + */ +void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, + bool use_shaders, bool set_material, video::E_MATERIAL_TYPE *mattype, + std::vector *colors); #endif -- cgit v1.2.3 From cca58fe0fd1aac4246a23e1fc4f8469bdd280c6d Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Wed, 19 Apr 2017 23:10:39 -0700 Subject: Add on_flood() callback. This callback is called if a liquid definitely floods a non-air node on the map. The callback arguments are (pos, oldnode, newnode) and can return a `bool` value indicating whether flooding the node should be cancelled (`return true` will prevent the node from flooding). Documentation is added, the callback function was tested with a modified minetest_game. Note that `return true` will likely cause the node's `on_flood()` callback to be called every second until the node gets removed, so care must be taken to prevent many callbacks from using this return value. The current default liquid update interval is 1.0 seconds, which isn't unmanageable. The larger aim of this patch is to remove the lava cooling ABM, which is a significant cost to idle servers that have lava on their map. This callback will be much more efficient. --- doc/lua_api.txt | 7 +++++++ src/map.cpp | 12 +++++++++++- src/map.h | 3 ++- src/script/cpp_api/s_node.cpp | 21 +++++++++++++++++++++ src/script/cpp_api/s_node.h | 1 + src/server.cpp | 2 +- 6 files changed, 43 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 4e328ac76..774b1e992 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4002,6 +4002,13 @@ Definition tables ^ Node destructor; called after removing node ^ Not called for bulk node placement (i.e. schematics and VoxelManip) ^ default: nil ]] + on_flood = func(pos, oldnode, newnode), --[[ + ^ Called when a liquid (newnode) is about to flood oldnode, if + ^ it has `floodable = true` in the nodedef. Not called for bulk + ^ node placement (i.e. schematics and VoxelManip) or air nodes. If + ^ return true the node is not flooded, but on_flood callback will + ^ most likely be called over and over again every liquid update + ^ interval. Default: nil ]] after_place_node = func(pos, placer, itemstack, pointed_thing) --[[ ^ Called after constructing node when node was placed using diff --git a/src/map.cpp b/src/map.cpp index 8754813dd..75dcee350 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -44,6 +44,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "database-dummy.h" #include "database-sqlite3.h" +#include "script/serverscripting.h" #include #include #if USE_LEVELDB @@ -637,7 +638,8 @@ s32 Map::transforming_liquid_size() { return m_transforming_liquid.size(); } -void Map::transformLiquids(std::map &modified_blocks) +void Map::transformLiquids(std::map &modified_blocks, + ServerEnvironment *env) { DSTACK(FUNCTION_NAME); //TimeTaker timer("transformLiquids()"); @@ -897,8 +899,16 @@ void Map::transformLiquids(std::map &modified_blocks) // set the liquid level and flow bit to 0 n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); } + + // change the node. n0.setContent(new_node_content); + // on_flood() the node + if (floodable_node != CONTENT_AIR) { + if (env->getScriptIface()->node_on_flood(p0, n00, n0)) + continue; + } + // Ignore light (because calling voxalgo::update_lighting_nodes) n0.setLight(LIGHTBANK_DAY, 0, m_nodedef); n0.setLight(LIGHTBANK_NIGHT, 0, m_nodedef); diff --git a/src/map.h b/src/map.h index 739cdb59b..4d7079823 100644 --- a/src/map.h +++ b/src/map.h @@ -266,7 +266,8 @@ public: // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: " virtual void PrintInfo(std::ostream &out); - void transformLiquids(std::map & modified_blocks); + void transformLiquids(std::map & modified_blocks, + ServerEnvironment *env); /* Node metadata diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index adad01e45..2723f84e1 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -178,6 +178,27 @@ void ScriptApiNode::node_on_destruct(v3s16 p, MapNode node) lua_pop(L, 1); // Pop error handler } +bool ScriptApiNode::node_on_flood(v3s16 p, MapNode node, MapNode newnode) +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + INodeDefManager *ndef = getServer()->ndef(); + + // Push callback function on stack + if (!getItemCallback(ndef->get(node).name.c_str(), "on_flood")) + return false; + + // Call function + push_v3s16(L, p); + pushnode(L, node, ndef); + pushnode(L, newnode, ndef); + PCALL_RES(lua_pcall(L, 3, 1, error_handler)); + lua_remove(L, error_handler); + return (bool) lua_isboolean(L, -1) && (bool) lua_toboolean(L, -1) == true; +} + void ScriptApiNode::node_after_destruct(v3s16 p, MapNode node) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_node.h b/src/script/cpp_api/s_node.h index fe1180cb3..eb127909d 100644 --- a/src/script/cpp_api/s_node.h +++ b/src/script/cpp_api/s_node.h @@ -42,6 +42,7 @@ public: ServerActiveObject *digger); void node_on_construct(v3s16 p, MapNode node); void node_on_destruct(v3s16 p, MapNode node); + bool node_on_flood(v3s16 p, MapNode node, MapNode newnode); void node_after_destruct(v3s16 p, MapNode node); bool node_on_timer(v3s16 p, MapNode node, f32 dtime); void node_on_receive_fields(v3s16 p, diff --git a/src/server.cpp b/src/server.cpp index a921423d2..ac6265d09 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -599,7 +599,7 @@ void Server::AsyncRunStep(bool initial_step) ScopeProfiler sp(g_profiler, "Server: liquid transform"); std::map modified_blocks; - m_env->getMap().transformLiquids(modified_blocks); + m_env->getMap().transformLiquids(modified_blocks, m_env); #if 0 /* Update lighting -- cgit v1.2.3 From 58c083f305d77bd3c7d366c1f0e4a54d80a51719 Mon Sep 17 00:00:00 2001 From: paramat Date: Thu, 20 Apr 2017 22:47:28 +0100 Subject: Sneak glitch: Set default to false The 'sneak glitch' physics override now controls whether a player can use the new move code replications of the old sneak side-effects: sneak ladders and 2 node sneak jump. This completes our intention to replicate the old sneak side-effects in new code and provide them as an option that is disabled by default. --- doc/lua_api.txt | 2 +- src/content_sao.cpp | 2 +- src/localplayer.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 774b1e992..33254fb2a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3038,7 +3038,7 @@ This is basically a reference to a C++ `ServerActiveObject` * `sneak`: whether player can sneak (default: `true`) * `sneak_glitch`: whether player can use the new move code replications of the old sneak side-effects: sneak ladders and 2 node sneak jump - when next to a ledge 2 nodes up (default: `true`) + (default: `false`) * `new_move`: use new move/sneak code. When `false` the exact old code is used for the specific old sneak behaviour (default: `true`) * `get_physics_override()`: returns the table given to set_physics_override diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 908365397..355453fc9 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -788,7 +788,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer m_physics_override_jump(1), m_physics_override_gravity(1), m_physics_override_sneak(true), - m_physics_override_sneak_glitch(true), + m_physics_override_sneak_glitch(false), m_physics_override_new_move(true), m_physics_override_sent(false) { diff --git a/src/localplayer.cpp b/src/localplayer.cpp index ea4347207..ab44f155f 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -48,7 +48,7 @@ LocalPlayer::LocalPlayer(Client *client, const char *name): physics_override_jump(1.0f), physics_override_gravity(1.0f), physics_override_sneak(true), - physics_override_sneak_glitch(true), + physics_override_sneak_glitch(false), physics_override_new_move(true), // Temporary option for old move code overridePosition(v3f(0,0,0)), last_position(v3f(0,0,0)), -- cgit v1.2.3 From 29ab20c27229672c24a7699afbcd54caad903331 Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 23 Apr 2017 14:35:08 +0200 Subject: Player data to Database (#5475) * Player data to Database Add player data into databases (SQLite3 & PG only) PostgreSQL & SQLite: better POO Design for databases Add --migrate-players argument to server + deprecation warning * Remove players directory if empty --- build/android/jni/Android.mk | 1 + builtin/game/chatcommands.lua | 25 +++ doc/lua_api.txt | 2 + src/CMakeLists.txt | 1 + src/client.cpp | 2 +- src/client.h | 4 +- src/content_sao.cpp | 7 +- src/content_sao.h | 4 +- src/database-dummy.h | 9 +- src/database-files.cpp | 179 +++++++++++++++ src/database-files.h | 46 ++++ src/database-leveldb.h | 4 +- src/database-postgresql.cpp | 470 ++++++++++++++++++++++++++++++++++------ src/database-postgresql.h | 115 +++++++--- src/database-redis.h | 2 +- src/database-sqlite3.cpp | 399 +++++++++++++++++++++++++++++++--- src/database-sqlite3.h | 158 ++++++++++++-- src/database.cpp | 4 +- src/database.h | 24 +- src/main.cpp | 21 +- src/map.cpp | 13 +- src/map.h | 8 +- src/remoteplayer.cpp | 48 ---- src/remoteplayer.h | 2 +- src/script/lua_api/l_server.cpp | 17 ++ src/script/lua_api/l_server.h | 3 + src/server.cpp | 50 +---- src/server.h | 3 +- src/serverenvironment.cpp | 234 ++++++++++++++++---- src/serverenvironment.h | 13 +- src/unittest/test_player.cpp | 49 ----- 31 files changed, 1547 insertions(+), 370 deletions(-) create mode 100644 src/database-files.cpp create mode 100644 src/database-files.h (limited to 'doc/lua_api.txt') diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index 2929eaba1..b652c6b5e 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -134,6 +134,7 @@ LOCAL_SRC_FILES := \ jni/src/convert_json.cpp \ jni/src/craftdef.cpp \ jni/src/database-dummy.cpp \ + jni/src/database-files.cpp \ jni/src/database-sqlite3.cpp \ jni/src/database.cpp \ jni/src/debug.cpp \ diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 84f2c3fed..cbf75c1bc 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -279,6 +279,31 @@ core.register_chatcommand("auth_reload", { end, }) +core.register_chatcommand("remove_player", { + params = "", + description = "Remove player data", + privs = {server=true}, + func = function(name, param) + local toname = param + if toname == "" then + return false, "Name field required" + end + + local rc = core.remove_player(toname) + + if rc == 0 then + core.log("action", name .. " removed player data of " .. toname .. ".") + return true, "Player \"" .. toname .. "\" removed." + elseif rc == 1 then + return true, "No such player \"" .. toname .. "\" to remove." + elseif rc == 2 then + return true, "Player \"" .. toname .. "\" is connected, cannot remove." + end + + return false, "Unhandled remove_player return code " .. rc .. "" + end, +}) + core.register_chatcommand("teleport", { params = ",, | | ,, | ", description = "Teleport to player or position", diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 33254fb2a..c3d8d2bf6 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2599,6 +2599,8 @@ These functions return the leftover itemstack. * `minetest.cancel_shutdown_requests()`: cancel current delayed shutdown * `minetest.get_server_status()`: returns server status string * `minetest.get_server_uptime()`: returns the server uptime in seconds +* `minetest.remove_player(name)`: remove player from database (if he is not connected). + * Returns a code (0: successful, 1: no such player, 2: player is connected) ### Bans * `minetest.get_ban_list()`: returns the ban list (same as `minetest.get_ban_description("")`) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37f72a44d..7f779db10 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -377,6 +377,7 @@ set(common_SRCS convert_json.cpp craftdef.cpp database-dummy.cpp + database-files.cpp database-leveldb.cpp database-postgresql.cpp database-redis.cpp diff --git a/src/client.cpp b/src/client.cpp index ce42d025e..94c808a57 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -770,7 +770,7 @@ void Client::initLocalMapSaving(const Address &address, fs::CreateAllDirs(world_path); - m_localdb = new Database_SQLite3(world_path); + m_localdb = new MapDatabaseSQLite3(world_path); m_localdb->beginSave(); actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; } diff --git a/src/client.h b/src/client.h index 5dc3f9bc8..328a24f90 100644 --- a/src/client.h +++ b/src/client.h @@ -49,7 +49,7 @@ class ClientMediaDownloader; struct MapDrawControl; class MtEventManager; struct PointedThing; -class Database; +class MapDatabase; class Minimap; struct MinimapMapblock; class Camera; @@ -645,7 +645,7 @@ private: LocalClientState m_state; // Used for saving server map to disk client-side - Database *m_localdb; + MapDatabase *m_localdb; IntervalLimiter m_localdb_save_interval; u16 m_cache_save_interval; diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 355453fc9..caf6dcbab 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -764,9 +764,10 @@ bool LuaEntitySAO::collideWithObjects() const // No prototype, PlayerSAO does not need to be deserialized -PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer): +PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_, + bool is_singleplayer): UnitSAO(env_, v3f(0,0,0)), - m_player(NULL), + m_player(player_), m_peer_id(peer_id_), m_inventory(NULL), m_damage(0), @@ -819,7 +820,7 @@ PlayerSAO::~PlayerSAO() delete m_inventory; } -void PlayerSAO::initialize(RemotePlayer *player, const std::set &privs) +void PlayerSAO::finalize(RemotePlayer *player, const std::set &privs) { assert(player); m_player = player; diff --git a/src/content_sao.h b/src/content_sao.h index e53e8ecce..e08795579 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -194,7 +194,7 @@ class RemotePlayer; class PlayerSAO : public UnitSAO { public: - PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer); + PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_, bool is_singleplayer); ~PlayerSAO(); ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_PLAYER; } @@ -349,7 +349,7 @@ public: bool getCollisionBox(aabb3f *toset) const; bool collideWithObjects() const { return true; } - void initialize(RemotePlayer *player, const std::set &privs); + void finalize(RemotePlayer *player, const std::set &privs); v3f getEyePosition() const { return m_base_position + getEyeOffset(); } v3f getEyeOffset() const; diff --git a/src/database-dummy.h b/src/database-dummy.h index 9083850cb..7d1cb2279 100644 --- a/src/database-dummy.h +++ b/src/database-dummy.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "irrlichttypes.h" -class Database_Dummy : public Database +class Database_Dummy : public MapDatabase, public PlayerDatabase { public: bool saveBlock(const v3s16 &pos, const std::string &data); @@ -33,6 +33,13 @@ public: bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); + void savePlayer(RemotePlayer *player) {} + bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) { return true; } + bool removePlayer(const std::string &name) { return true; } + void listPlayers(std::vector &) {} + + void beginSave() {} + void endSave() {} private: std::map m_database; }; diff --git a/src/database-files.cpp b/src/database-files.cpp new file mode 100644 index 000000000..08a1f2d03 --- /dev/null +++ b/src/database-files.cpp @@ -0,0 +1,179 @@ +/* +Minetest +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 +#include +#include "database-files.h" +#include "content_sao.h" +#include "remoteplayer.h" +#include "settings.h" +#include "porting.h" +#include "filesys.h" + +// !!! WARNING !!! +// This backend is intended to be used on Minetest 0.4.16 only for the transition backend for +// player files + +void PlayerDatabaseFiles::serialize(std::ostringstream &os, RemotePlayer *player) +{ + // Utilize a Settings object for storing values + Settings args; + args.setS32("version", 1); + args.set("name", player->getName()); + + sanity_check(player->getPlayerSAO()); + args.setS32("hp", player->getPlayerSAO()->getHP()); + args.setV3F("position", player->getPlayerSAO()->getBasePosition()); + args.setFloat("pitch", player->getPlayerSAO()->getPitch()); + args.setFloat("yaw", player->getPlayerSAO()->getYaw()); + args.setS32("breath", player->getPlayerSAO()->getBreath()); + + std::string extended_attrs = ""; + player->serializeExtraAttributes(extended_attrs); + args.set("extended_attributes", extended_attrs); + + args.writeLines(os); + + os << "PlayerArgsEnd\n"; + + player->inventory.serialize(os); +} + +void PlayerDatabaseFiles::savePlayer(RemotePlayer *player) +{ + std::string savedir = m_savedir + DIR_DELIM; + std::string path = savedir + player->getName(); + bool path_found = false; + RemotePlayer testplayer("", NULL); + + for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) { + if (!fs::PathExists(path)) { + path_found = true; + continue; + } + + // Open and deserialize file to check player name + std::ifstream is(path.c_str(), std::ios_base::binary); + if (!is.good()) { + errorstream << "Failed to open " << path << std::endl; + return; + } + + testplayer.deSerialize(is, path, NULL); + is.close(); + if (strcmp(testplayer.getName(), player->getName()) == 0) { + path_found = true; + continue; + } + + path = savedir + player->getName() + itos(i); + } + + if (!path_found) { + errorstream << "Didn't find free file for player " << player->getName() + << std::endl; + return; + } + + // Open and serialize file + std::ostringstream ss(std::ios_base::binary); + serialize(ss, player); + if (!fs::safeWriteToFile(path, ss.str())) { + infostream << "Failed to write " << path << std::endl; + } + player->setModified(false); +} + +bool PlayerDatabaseFiles::removePlayer(const std::string &name) +{ + std::string players_path = m_savedir + DIR_DELIM; + std::string path = players_path + name; + + RemotePlayer temp_player("", NULL); + for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { + // Open file and deserialize + std::ifstream is(path.c_str(), std::ios_base::binary); + if (!is.good()) + continue; + + temp_player.deSerialize(is, path, NULL); + is.close(); + + if (temp_player.getName() == name) { + fs::DeleteSingleFileOrEmptyDirectory(path); + return true; + } + + path = players_path + name + itos(i); + } + + return false; +} + +bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao) +{ + std::string players_path = m_savedir + DIR_DELIM; + std::string path = players_path + player->getName(); + + const std::string player_to_load = player->getName(); + for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { + // Open file and deserialize + std::ifstream is(path.c_str(), std::ios_base::binary); + if (!is.good()) + continue; + + player->deSerialize(is, path, sao); + is.close(); + + if (player->getName() == player_to_load) + return true; + + path = players_path + player_to_load + itos(i); + } + + infostream << "Player file for player " << player_to_load << " not found" << std::endl; + return false; +} + +void PlayerDatabaseFiles::listPlayers(std::vector &res) +{ + std::vector files = fs::GetDirListing(m_savedir); + // list files into players directory + for (std::vector::const_iterator it = files.begin(); it != + files.end(); ++it) { + // Ignore directories + if (it->dir) + continue; + + const std::string &filename = it->name; + std::string full_path = m_savedir + DIR_DELIM + filename; + std::ifstream is(full_path.c_str(), std::ios_base::binary); + if (!is.good()) + continue; + + RemotePlayer player(filename.c_str(), NULL); + // Null env & dummy peer_id + PlayerSAO playerSAO(NULL, &player, 15789, false); + + player.deSerialize(is, "", &playerSAO); + is.close(); + + res.push_back(player.getName()); + } +} diff --git a/src/database-files.h b/src/database-files.h new file mode 100644 index 000000000..d23069c2a --- /dev/null +++ b/src/database-files.h @@ -0,0 +1,46 @@ +/* +Minetest +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 DATABASE_FILES_HEADER +#define DATABASE_FILES_HEADER + +// !!! WARNING !!! +// This backend is intended to be used on Minetest 0.4.16 only for the transition backend for +// player files + +#include "database.h" + +class PlayerDatabaseFiles : public PlayerDatabase +{ +public: + PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir) {} + virtual ~PlayerDatabaseFiles() {} + + void savePlayer(RemotePlayer *player); + bool loadPlayer(RemotePlayer *player, PlayerSAO *sao); + bool removePlayer(const std::string &name); + void listPlayers(std::vector &res); + +private: + void serialize(std::ostringstream &os, RemotePlayer *player); + + std::string m_savedir; +}; + +#endif diff --git a/src/database-leveldb.h b/src/database-leveldb.h index 171946741..52ccebe70 100644 --- a/src/database-leveldb.h +++ b/src/database-leveldb.h @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "leveldb/db.h" -class Database_LevelDB : public Database +class Database_LevelDB : public MapDatabase { public: Database_LevelDB(const std::string &savedir); @@ -39,6 +39,8 @@ public: bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); + void beginSave() {} + void endSave() {} private: leveldb::DB *m_database; }; diff --git a/src/database-postgresql.cpp b/src/database-postgresql.cpp index 83678fd52..a6b62bad5 100644 --- a/src/database-postgresql.cpp +++ b/src/database-postgresql.cpp @@ -39,13 +39,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "exceptions.h" #include "settings.h" +#include "content_sao.h" +#include "remoteplayer.h" -Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) : - m_connect_string(""), +Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) : + m_connect_string(connect_string), m_conn(NULL), m_pgversion(0) { - if (!conf.getNoEx("pgsql_connection", m_connect_string)) { + if (m_connect_string.empty()) { throw SettingNotFoundException( "Set pgsql_connection string in world.mt to " "use the postgresql backend\n" @@ -57,8 +59,6 @@ Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) : "DELETE rights on the database.\n" "Don't create mt_user as a SUPERUSER!"); } - - connectToDatabase(); } Database_PostgreSQL::~Database_PostgreSQL() @@ -118,40 +118,6 @@ bool Database_PostgreSQL::initialized() const return (PQstatus(m_conn) == CONNECTION_OK); } -void Database_PostgreSQL::initStatements() -{ - prepareStatement("read_block", - "SELECT data FROM blocks " - "WHERE posX = $1::int4 AND posY = $2::int4 AND " - "posZ = $3::int4"); - - if (m_pgversion < 90500) { - prepareStatement("write_block_insert", - "INSERT INTO blocks (posX, posY, posZ, data) SELECT " - "$1::int4, $2::int4, $3::int4, $4::bytea " - "WHERE NOT EXISTS (SELECT true FROM blocks " - "WHERE posX = $1::int4 AND posY = $2::int4 AND " - "posZ = $3::int4)"); - - prepareStatement("write_block_update", - "UPDATE blocks SET data = $4::bytea " - "WHERE posX = $1::int4 AND posY = $2::int4 AND " - "posZ = $3::int4"); - } else { - prepareStatement("write_block", - "INSERT INTO blocks (posX, posY, posZ, data) VALUES " - "($1::int4, $2::int4, $3::int4, $4::bytea) " - "ON CONFLICT ON CONSTRAINT blocks_pkey DO " - "UPDATE SET data = $4::bytea"); - } - - prepareStatement("delete_block", "DELETE FROM blocks WHERE " - "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"); - - prepareStatement("list_all_loadable_blocks", - "SELECT posX, posY, posZ FROM blocks"); -} - PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) { ExecStatusType statusType = PQresultStatus(result); @@ -173,30 +139,21 @@ PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) return result; } -void Database_PostgreSQL::createDatabase() +void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name, + const std::string &definition) { - PGresult *result = checkResults(PQexec(m_conn, - "SELECT relname FROM pg_class WHERE relname='blocks';"), - false); + std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" + + table_name + "';"; + PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), false); // If table doesn't exist, create it if (!PQntuples(result)) { - static const char* dbcreate_sql = "CREATE TABLE blocks (" - "posX INT NOT NULL," - "posY INT NOT NULL," - "posZ INT NOT NULL," - "data BYTEA," - "PRIMARY KEY (posX,posY,posZ)" - ");"; - checkResults(PQexec(m_conn, dbcreate_sql)); + checkResults(PQexec(m_conn, definition.c_str())); } PQclear(result); - - infostream << "PostgreSQL: Game Database was inited." << std::endl; } - void Database_PostgreSQL::beginSave() { verifyDatabase(); @@ -208,14 +165,70 @@ void Database_PostgreSQL::endSave() checkResults(PQexec(m_conn, "COMMIT;")); } -bool Database_PostgreSQL::saveBlock(const v3s16 &pos, - const std::string &data) +MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string): + Database_PostgreSQL(connect_string), + MapDatabase() +{ + connectToDatabase(); +} + + +void MapDatabasePostgreSQL::createDatabase() +{ + createTableIfNotExists("blocks", + "CREATE TABLE blocks (" + "posX INT NOT NULL," + "posY INT NOT NULL," + "posZ INT NOT NULL," + "data BYTEA," + "PRIMARY KEY (posX,posY,posZ)" + ");" + ); + + infostream << "PostgreSQL: Map Database was initialized." << std::endl; +} + +void MapDatabasePostgreSQL::initStatements() +{ + prepareStatement("read_block", + "SELECT data FROM blocks " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4"); + + if (getPGVersion() < 90500) { + prepareStatement("write_block_insert", + "INSERT INTO blocks (posX, posY, posZ, data) SELECT " + "$1::int4, $2::int4, $3::int4, $4::bytea " + "WHERE NOT EXISTS (SELECT true FROM blocks " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4)"); + + prepareStatement("write_block_update", + "UPDATE blocks SET data = $4::bytea " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4"); + } else { + prepareStatement("write_block", + "INSERT INTO blocks (posX, posY, posZ, data) VALUES " + "($1::int4, $2::int4, $3::int4, $4::bytea) " + "ON CONFLICT ON CONSTRAINT blocks_pkey DO " + "UPDATE SET data = $4::bytea"); + } + + prepareStatement("delete_block", "DELETE FROM blocks WHERE " + "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"); + + prepareStatement("list_all_loadable_blocks", + "SELECT posX, posY, posZ FROM blocks"); +} + +bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data) { // Verify if we don't overflow the platform integer with the mapblock size if (data.size() > INT_MAX) { errorstream << "Database_PostgreSQL::saveBlock: Data truncation! " - << "data.size() over 0xFFFF (== " << data.size() - << ")" << std::endl; + << "data.size() over 0xFFFFFFFF (== " << data.size() + << ")" << std::endl; return false; } @@ -232,7 +245,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos, }; const int argFmt[] = { 1, 1, 1, 1 }; - if (m_pgversion < 90500) { + if (getPGVersion() < 90500) { execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt); execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt); } else { @@ -241,8 +254,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos, return true; } -void Database_PostgreSQL::loadBlock(const v3s16 &pos, - std::string *block) +void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block) { verifyDatabase(); @@ -256,19 +268,17 @@ void Database_PostgreSQL::loadBlock(const v3s16 &pos, const int argFmt[] = { 1, 1, 1 }; PGresult *results = execPrepared("read_block", ARRLEN(args), args, - argLen, argFmt, false); + argLen, argFmt, false); *block = ""; - if (PQntuples(results)) { - *block = std::string(PQgetvalue(results, 0, 0), - PQgetlength(results, 0, 0)); - } + if (PQntuples(results)) + *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); PQclear(results); } -bool Database_PostgreSQL::deleteBlock(const v3s16 &pos) +bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos) { verifyDatabase(); @@ -286,18 +296,338 @@ bool Database_PostgreSQL::deleteBlock(const v3s16 &pos) return true; } -void Database_PostgreSQL::listAllLoadableBlocks(std::vector &dst) +void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector &dst) { verifyDatabase(); PGresult *results = execPrepared("list_all_loadable_blocks", 0, - NULL, NULL, NULL, false, false); + NULL, NULL, NULL, false, false); int numrows = PQntuples(results); - for (int row = 0; row < numrows; ++row) { + for (int row = 0; row < numrows; ++row) dst.push_back(pg_to_v3s16(results, 0, 0)); + + PQclear(results); +} + +/* + * Player Database + */ +PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string): + Database_PostgreSQL(connect_string), + PlayerDatabase() +{ + connectToDatabase(); +} + + +void PlayerDatabasePostgreSQL::createDatabase() +{ + createTableIfNotExists("player", + "CREATE TABLE player (" + "name VARCHAR(60) NOT NULL," + "pitch NUMERIC(15, 7) NOT NULL," + "yaw NUMERIC(15, 7) NOT NULL," + "posX NUMERIC(15, 7) NOT NULL," + "posY NUMERIC(15, 7) NOT NULL," + "posZ NUMERIC(15, 7) NOT NULL," + "hp INT NOT NULL," + "breath INT NOT NULL," + "creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()," + "modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()," + "PRIMARY KEY (name)" + ");" + ); + + createTableIfNotExists("player_inventories", + "CREATE TABLE player_inventories (" + "player VARCHAR(60) NOT NULL," + "inv_id INT NOT NULL," + "inv_width INT NOT NULL," + "inv_name TEXT NOT NULL DEFAULT ''," + "inv_size INT NOT NULL," + "PRIMARY KEY(player, inv_id)," + "CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES " + "player (name) ON DELETE CASCADE" + ");" + ); + + createTableIfNotExists("player_inventory_items", + "CREATE TABLE player_inventory_items (" + "player VARCHAR(60) NOT NULL," + "inv_id INT NOT NULL," + "slot_id INT NOT NULL," + "item TEXT NOT NULL DEFAULT ''," + "PRIMARY KEY(player, inv_id, slot_id)," + "CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES " + "player (name) ON DELETE CASCADE" + ");" + ); + + createTableIfNotExists("player_metadata", + "CREATE TABLE player_metadata (" + "player VARCHAR(60) NOT NULL," + "attr VARCHAR(256) NOT NULL," + "value TEXT," + "PRIMARY KEY(player, attr)," + "CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES " + "player (name) ON DELETE CASCADE" + ");" + ); + + infostream << "PostgreSQL: Player Database was inited." << std::endl; +} + +void PlayerDatabasePostgreSQL::initStatements() +{ + if (getPGVersion() < 90500) { + prepareStatement("create_player", + "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES " + "($1, $2, $3, $4, $5, $6, $7::int, $8::int)"); + + prepareStatement("update_player", + "UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, " + "breath = $8::int, modification_date = NOW() WHERE name = $1"); + } else { + prepareStatement("save_player", + "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES " + "($1, $2, $3, $4, $5, $6, $7::int, $8::int)" + "ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, " + "posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, " + "modification_date = NOW()"); + } + + prepareStatement("remove_player", "DELETE FROM player WHERE name = $1"); + + prepareStatement("load_player_list", "SELECT name FROM player"); + + prepareStatement("remove_player_inventories", + "DELETE FROM player_inventories WHERE player = $1"); + + prepareStatement("remove_player_inventory_items", + "DELETE FROM player_inventory_items WHERE player = $1"); + + prepareStatement("add_player_inventory", + "INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES " + "($1, $2::int, $3::int, $4, $5::int)"); + + prepareStatement("add_player_inventory_item", + "INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES " + "($1, $2::int, $3::int, $4)"); + + prepareStatement("load_player_inventories", + "SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories " + "WHERE player = $1 ORDER BY inv_id"); + + prepareStatement("load_player_inventory_items", + "SELECT slot_id, item FROM player_inventory_items WHERE " + "player = $1 AND inv_id = $2::int"); + + prepareStatement("load_player", + "SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1"); + + prepareStatement("remove_player_metadata", + "DELETE FROM player_metadata WHERE player = $1"); + + prepareStatement("save_player_metadata", + "INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)"); + + prepareStatement("load_player_metadata", + "SELECT attr, value FROM player_metadata WHERE player = $1"); + +} + +bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername) +{ + verifyDatabase(); + + const char *values[] = { playername.c_str() }; + PGresult *results = execPrepared("load_player", 1, values, false); + + bool res = (PQntuples(results) > 0); + PQclear(results); + return res; +} + +void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) +{ + PlayerSAO* sao = player->getPlayerSAO(); + if (!sao) + return; + + verifyDatabase(); + + v3f pos = sao->getBasePosition(); + std::string pitch = ftos(sao->getPitch()); + std::string yaw = ftos(sao->getYaw()); + std::string posx = ftos(pos.X); + std::string posy = ftos(pos.Y); + std::string posz = ftos(pos.Z); + std::string hp = itos(sao->getHP()); + std::string breath = itos(sao->getBreath()); + const char *values[] = { + player->getName(), + pitch.c_str(), + yaw.c_str(), + posx.c_str(), posy.c_str(), posz.c_str(), + hp.c_str(), + breath.c_str() + }; + + const char* rmvalues[] = { player->getName() }; + beginSave(); + + if (getPGVersion() < 90500) { + if (!playerDataExists(player->getName())) + execPrepared("create_player", 8, values, true, false); + else + execPrepared("update_player", 8, values, true, false); + } + else + execPrepared("save_player", 8, values, true, false); + + // Write player inventories + execPrepared("remove_player_inventories", 1, rmvalues); + execPrepared("remove_player_inventory_items", 1, rmvalues); + + std::vector inventory_lists = sao->getInventory()->getLists(); + for (u16 i = 0; i < inventory_lists.size(); i++) { + const InventoryList* list = inventory_lists[i]; + std::string name = list->getName(), width = itos(list->getWidth()), + inv_id = itos(i), lsize = itos(list->getSize()); + + const char* inv_values[] = { + player->getName(), + inv_id.c_str(), + width.c_str(), + name.c_str(), + lsize.c_str() + }; + execPrepared("add_player_inventory", 5, inv_values); + + for (u32 j = 0; j < list->getSize(); j++) { + std::ostringstream os; + list->getItem(j).serialize(os); + std::string itemStr = os.str(), slotId = itos(j); + + const char* invitem_values[] = { + player->getName(), + inv_id.c_str(), + slotId.c_str(), + itemStr.c_str() + }; + execPrepared("add_player_inventory_item", 4, invitem_values); + } + } + + execPrepared("remove_player_metadata", 1, rmvalues); + const PlayerAttributes &attrs = sao->getExtendedAttributes(); + for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) { + const char *meta_values[] = { + player->getName(), + it->first.c_str(), + it->second.c_str() + }; + execPrepared("save_player_metadata", 3, meta_values); } + endSave(); +} + +bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao) +{ + sanity_check(sao); + verifyDatabase(); + + const char *values[] = { player->getName() }; + PGresult *results = execPrepared("load_player", 1, values, false, false); + + // Player not found, return not found + if (!PQntuples(results)) { + PQclear(results); + return false; + } + + sao->setPitch(pg_to_float(results, 0, 0)); + sao->setYaw(pg_to_float(results, 0, 1)); + sao->setBasePosition(v3f( + pg_to_float(results, 0, 2), + pg_to_float(results, 0, 3), + pg_to_float(results, 0, 4)) + ); + sao->setHPRaw((s16) pg_to_int(results, 0, 5)); + sao->setBreath((u16) pg_to_int(results, 0, 6), false); + + PQclear(results); + + // Load inventory + results = execPrepared("load_player_inventories", 1, values, false, false); + + int resultCount = PQntuples(results); + + for (int row = 0; row < resultCount; ++row) { + InventoryList* invList = player->inventory. + addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3)); + invList->setWidth(pg_to_uint(results, row, 1)); + + u32 invId = pg_to_uint(results, row, 0); + std::string invIdStr = itos(invId); + + const char* values2[] = { + player->getName(), + invIdStr.c_str() + }; + PGresult *results2 = execPrepared("load_player_inventory_items", 2, + values2, false, false); + + int resultCount2 = PQntuples(results2); + for (int row2 = 0; row2 < resultCount2; row2++) { + const std::string itemStr = PQgetvalue(results2, row2, 1); + if (itemStr.length() > 0) { + ItemStack stack; + stack.deSerialize(itemStr); + invList->addItem(pg_to_uint(results2, row2, 0), stack); + } + } + PQclear(results2); + } + + PQclear(results); + + results = execPrepared("load_player_metadata", 1, values, false); + + int numrows = PQntuples(results); + for (int row = 0; row < numrows; row++) { + sao->setExtendedAttribute(PQgetvalue(results, row, 0),PQgetvalue(results, row, 1)); + } + + PQclear(results); + + return true; +} + +bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name) +{ + if (!playerDataExists(name)) + return false; + + verifyDatabase(); + + const char *values[] = { name.c_str() }; + execPrepared("remove_player", 1, values); + + return true; +} + +void PlayerDatabasePostgreSQL::listPlayers(std::vector &res) +{ + verifyDatabase(); + + PGresult *results = execPrepared("load_player_list", 0, NULL, false); + + int numrows = PQntuples(results); + for (int row = 0; row < numrows; row++) + res.push_back(PQgetvalue(results, row, 0)); PQclear(results); } diff --git a/src/database-postgresql.h b/src/database-postgresql.h index 1cfa544e3..d6f208fd9 100644 --- a/src/database-postgresql.h +++ b/src/database-postgresql.h @@ -27,53 +27,33 @@ with this program; if not, write to the Free Software Foundation, Inc., class Settings; -class Database_PostgreSQL : public Database +class Database_PostgreSQL: public Database { public: - Database_PostgreSQL(const Settings &conf); + Database_PostgreSQL(const std::string &connect_string); ~Database_PostgreSQL(); void beginSave(); void endSave(); - bool saveBlock(const v3s16 &pos, const std::string &data); - void loadBlock(const v3s16 &pos, std::string *block); - bool deleteBlock(const v3s16 &pos); - void listAllLoadableBlocks(std::vector &dst); bool initialized() const; -private: - // Database initialization - void connectToDatabase(); - void initStatements(); - void createDatabase(); - inline void prepareStatement(const std::string &name, const std::string &sql) +protected: + // Conversion helpers + inline int pg_to_int(PGresult *res, int row, int col) { - checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL)); + return atoi(PQgetvalue(res, row, col)); } - // Database connectivity checks - void ping(); - void verifyDatabase(); - - // Database usage - PGresult *checkResults(PGresult *res, bool clear = true); - - inline PGresult *execPrepared(const char *stmtName, const int paramsNumber, - const void **params, - const int *paramsLengths = NULL, const int *paramsFormats = NULL, - bool clear = true, bool nobinary = true) + inline u32 pg_to_uint(PGresult *res, int row, int col) { - return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber, - (const char* const*) params, paramsLengths, paramsFormats, - nobinary ? 1 : 0), clear); + return (u32) atoi(PQgetvalue(res, row, col)); } - // Conversion helpers - inline int pg_to_int(PGresult *res, int row, int col) + inline float pg_to_float(PGresult *res, int row, int col) { - return atoi(PQgetvalue(res, row, col)); + return (float) atof(PQgetvalue(res, row, col)); } inline v3s16 pg_to_v3s16(PGresult *res, int row, int col) @@ -85,11 +65,86 @@ private: ); } + inline PGresult *execPrepared(const char *stmtName, const int paramsNumber, + const void **params, + const int *paramsLengths = NULL, const int *paramsFormats = NULL, + bool clear = true, bool nobinary = true) + { + return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber, + (const char* const*) params, paramsLengths, paramsFormats, + nobinary ? 1 : 0), clear); + } + + inline PGresult *execPrepared(const char *stmtName, const int paramsNumber, + const char **params, bool clear = true, bool nobinary = true) + { + return execPrepared(stmtName, paramsNumber, + (const void **)params, NULL, NULL, clear, nobinary); + } + + void createTableIfNotExists(const std::string &table_name, const std::string &definition); + void verifyDatabase(); + + // Database initialization + void connectToDatabase(); + virtual void createDatabase() = 0; + virtual void initStatements() = 0; + inline void prepareStatement(const std::string &name, const std::string &sql) + { + checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL)); + } + + const int getPGVersion() const { return m_pgversion; } +private: + // Database connectivity checks + void ping(); + + // Database usage + PGresult *checkResults(PGresult *res, bool clear = true); + // Attributes std::string m_connect_string; PGconn *m_conn; int m_pgversion; }; +class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase +{ +public: + MapDatabasePostgreSQL(const std::string &connect_string); + virtual ~MapDatabasePostgreSQL() {} + + bool saveBlock(const v3s16 &pos, const std::string &data); + void loadBlock(const v3s16 &pos, std::string *block); + bool deleteBlock(const v3s16 &pos); + void listAllLoadableBlocks(std::vector &dst); + + void beginSave() { Database_PostgreSQL::beginSave(); } + void endSave() { Database_PostgreSQL::endSave(); } + +protected: + virtual void createDatabase(); + virtual void initStatements(); +}; + +class PlayerDatabasePostgreSQL : private Database_PostgreSQL, public PlayerDatabase +{ +public: + PlayerDatabasePostgreSQL(const std::string &connect_string); + virtual ~PlayerDatabasePostgreSQL() {} + + void savePlayer(RemotePlayer *player); + bool loadPlayer(RemotePlayer *player, PlayerSAO *sao); + bool removePlayer(const std::string &name); + void listPlayers(std::vector &res); + +protected: + virtual void createDatabase(); + virtual void initStatements(); + +private: + bool playerDataExists(const std::string &playername); +}; + #endif diff --git a/src/database-redis.h b/src/database-redis.h index 214bc8dd6..fa15dd8a7 100644 --- a/src/database-redis.h +++ b/src/database-redis.h @@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class Settings; -class Database_Redis : public Database +class Database_Redis : public MapDatabase { public: Database_Redis(Settings &conf); diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp index 095d485c0..714f56c39 100644 --- a/src/database-sqlite3.cpp +++ b/src/database-sqlite3.cpp @@ -33,6 +33,8 @@ SQLite format specification: #include "settings.h" #include "porting.h" #include "util/string.h" +#include "content_sao.h" +#include "remoteplayer.h" #include @@ -111,27 +113,26 @@ int Database_SQLite3::busyHandler(void *data, int count) } -Database_SQLite3::Database_SQLite3(const std::string &savedir) : +Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) : + m_database(NULL), m_initialized(false), m_savedir(savedir), - m_database(NULL), - m_stmt_read(NULL), - m_stmt_write(NULL), - m_stmt_list(NULL), - m_stmt_delete(NULL), + m_dbname(dbname), m_stmt_begin(NULL), m_stmt_end(NULL) { } -void Database_SQLite3::beginSave() { +void Database_SQLite3::beginSave() +{ verifyDatabase(); SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE, "Failed to start SQLite3 transaction"); sqlite3_reset(m_stmt_begin); } -void Database_SQLite3::endSave() { +void Database_SQLite3::endSave() +{ verifyDatabase(); SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE, "Failed to commit SQLite3 transaction"); @@ -142,7 +143,7 @@ void Database_SQLite3::openDatabase() { if (m_database) return; - std::string dbp = m_savedir + DIR_DELIM + "map.sqlite"; + std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite"; // Open the database connection @@ -170,6 +171,8 @@ void Database_SQLite3::openDatabase() + itos(g_settings->getU16("sqlite_synchronous")); SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL), "Failed to modify sqlite3 synchronous mode"); + SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL), + "Failed to enable sqlite3 foreign key support"); } void Database_SQLite3::verifyDatabase() @@ -178,8 +181,61 @@ void Database_SQLite3::verifyDatabase() openDatabase(); - PREPARE_STATEMENT(begin, "BEGIN"); - PREPARE_STATEMENT(end, "COMMIT"); + PREPARE_STATEMENT(begin, "BEGIN;"); + PREPARE_STATEMENT(end, "COMMIT;"); + + initStatements(); + + m_initialized = true; +} + +Database_SQLite3::~Database_SQLite3() +{ + FINALIZE_STATEMENT(m_stmt_begin) + FINALIZE_STATEMENT(m_stmt_end) + + SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database"); +} + +/* + * Map database + */ + +MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir): + Database_SQLite3(savedir, "map"), + MapDatabase(), + m_stmt_read(NULL), + m_stmt_write(NULL), + m_stmt_list(NULL), + m_stmt_delete(NULL) +{ + +} + +MapDatabaseSQLite3::~MapDatabaseSQLite3() +{ + FINALIZE_STATEMENT(m_stmt_read) + FINALIZE_STATEMENT(m_stmt_write) + FINALIZE_STATEMENT(m_stmt_list) + FINALIZE_STATEMENT(m_stmt_delete) +} + + +void MapDatabaseSQLite3::createDatabase() +{ + assert(m_database); // Pre-condition + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `blocks` (\n" + " `pos` INT PRIMARY KEY,\n" + " `data` BLOB\n" + ");\n", + NULL, NULL, NULL), + "Failed to create database table"); +} + +void MapDatabaseSQLite3::initStatements() +{ PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); #ifdef __ANDROID__ PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); @@ -189,18 +245,16 @@ void Database_SQLite3::verifyDatabase() PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?"); PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`"); - m_initialized = true; - verbosestream << "ServerMap: SQLite3 database opened." << std::endl; } -inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index) +inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index) { SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)), "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); } -bool Database_SQLite3::deleteBlock(const v3s16 &pos) +bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos) { verifyDatabase(); @@ -216,7 +270,7 @@ bool Database_SQLite3::deleteBlock(const v3s16 &pos) return good; } -bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data) +bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data) { verifyDatabase(); @@ -243,7 +297,7 @@ bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data) return true; } -void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block) +void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block) { verifyDatabase(); @@ -264,37 +318,312 @@ void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block) sqlite3_reset(m_stmt_read); } -void Database_SQLite3::createDatabase() +void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector &dst) +{ + verifyDatabase(); + + while (sqlite3_step(m_stmt_list) == SQLITE_ROW) + dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0))); + + sqlite3_reset(m_stmt_list); +} + +/* + * Player Database + */ + +PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir): + Database_SQLite3(savedir, "players"), + PlayerDatabase(), + m_stmt_player_load(NULL), + m_stmt_player_add(NULL), + m_stmt_player_update(NULL), + m_stmt_player_remove(NULL), + m_stmt_player_list(NULL), + m_stmt_player_load_inventory(NULL), + m_stmt_player_load_inventory_items(NULL), + m_stmt_player_add_inventory(NULL), + m_stmt_player_add_inventory_items(NULL), + m_stmt_player_remove_inventory(NULL), + m_stmt_player_remove_inventory_items(NULL), + m_stmt_player_metadata_load(NULL), + m_stmt_player_metadata_remove(NULL), + m_stmt_player_metadata_add(NULL) +{ + +} +PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3() +{ + FINALIZE_STATEMENT(m_stmt_player_load) + FINALIZE_STATEMENT(m_stmt_player_add) + FINALIZE_STATEMENT(m_stmt_player_update) + FINALIZE_STATEMENT(m_stmt_player_remove) + FINALIZE_STATEMENT(m_stmt_player_list) + FINALIZE_STATEMENT(m_stmt_player_add_inventory) + FINALIZE_STATEMENT(m_stmt_player_add_inventory_items) + FINALIZE_STATEMENT(m_stmt_player_remove_inventory) + FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items) + FINALIZE_STATEMENT(m_stmt_player_load_inventory) + FINALIZE_STATEMENT(m_stmt_player_load_inventory_items) + FINALIZE_STATEMENT(m_stmt_player_metadata_load) + FINALIZE_STATEMENT(m_stmt_player_metadata_add) + FINALIZE_STATEMENT(m_stmt_player_metadata_remove) +}; + + +void PlayerDatabaseSQLite3::createDatabase() { assert(m_database); // Pre-condition + SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `blocks` (\n" - " `pos` INT PRIMARY KEY,\n" - " `data` BLOB\n" - ");\n", + "CREATE TABLE IF NOT EXISTS `player` (" + "`name` VARCHAR(50) NOT NULL," + "`pitch` NUMERIC(11, 4) NOT NULL," + "`yaw` NUMERIC(11, 4) NOT NULL," + "`posX` NUMERIC(11, 4) NOT NULL," + "`posY` NUMERIC(11, 4) NOT NULL," + "`posZ` NUMERIC(11, 4) NOT NULL," + "`hp` INT NOT NULL," + "`breath` INT NOT NULL," + "`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," + "`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," + "PRIMARY KEY (`name`));", NULL, NULL, NULL), - "Failed to create database table"); + "Failed to create player table"); + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `player_metadata` (" + " `player` VARCHAR(50) NOT NULL," + " `metadata` VARCHAR(256) NOT NULL," + " `value` TEXT," + " PRIMARY KEY(`player`, `metadata`)," + " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player metadata table"); + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `player_inventories` (" + " `player` VARCHAR(50) NOT NULL," + " `inv_id` INT NOT NULL," + " `inv_width` INT NOT NULL," + " `inv_name` TEXT NOT NULL DEFAULT ''," + " `inv_size` INT NOT NULL," + " PRIMARY KEY(player, inv_id)," + " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player inventory table"); + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE `player_inventory_items` (" + " `player` VARCHAR(50) NOT NULL," + " `inv_id` INT NOT NULL," + " `slot_id` INT NOT NULL," + " `item` TEXT NOT NULL DEFAULT ''," + " PRIMARY KEY(player, inv_id, slot_id)," + " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player inventory items table"); } -void Database_SQLite3::listAllLoadableBlocks(std::vector &dst) +void PlayerDatabaseSQLite3::initStatements() +{ + PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, " + "`breath`" + "FROM `player` WHERE `name` = ?") + PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, " + "`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, " + "`posX` = ?, `posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ?, " + "`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?") + PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?") + PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`") + + PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` " + "(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)") + PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` " + "(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)") + PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` " + "WHERE `player` = ?") + PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` " + "WHERE `player` = ?") + PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, " + "`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id") + PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` " + "FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?") + + PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM " + "`player_metadata` WHERE `player` = ?") + PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` " + "(`player`, `metadata`, `value`) VALUES (?, ?, ?)") + PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` " + "WHERE `player` = ?") + verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl; +} + +bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name) { verifyDatabase(); + str_to_sqlite(m_stmt_player_load, 1, name); + bool res = (sqlite3_step(m_stmt_player_load) == SQLITE_ROW); + sqlite3_reset(m_stmt_player_load); + return res; +} - while (sqlite3_step(m_stmt_list) == SQLITE_ROW) { - dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0))); +void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) +{ + PlayerSAO* sao = player->getPlayerSAO(); + sanity_check(sao); + + const v3f &pos = sao->getBasePosition(); + // Begin save in brace is mandatory + if (!playerDataExists(player->getName())) { + beginSave(); + str_to_sqlite(m_stmt_player_add, 1, player->getName()); + double_to_sqlite(m_stmt_player_add, 2, sao->getPitch()); + double_to_sqlite(m_stmt_player_add, 3, sao->getYaw()); + double_to_sqlite(m_stmt_player_add, 4, pos.X); + double_to_sqlite(m_stmt_player_add, 5, pos.Y); + double_to_sqlite(m_stmt_player_add, 6, pos.Z); + int64_to_sqlite(m_stmt_player_add, 7, sao->getHP()); + int64_to_sqlite(m_stmt_player_add, 8, sao->getBreath()); + + sqlite3_vrfy(sqlite3_step(m_stmt_player_add), SQLITE_DONE); + sqlite3_reset(m_stmt_player_add); + } else { + beginSave(); + double_to_sqlite(m_stmt_player_update, 1, sao->getPitch()); + double_to_sqlite(m_stmt_player_update, 2, sao->getYaw()); + double_to_sqlite(m_stmt_player_update, 3, pos.X); + double_to_sqlite(m_stmt_player_update, 4, pos.Y); + double_to_sqlite(m_stmt_player_update, 5, pos.Z); + int64_to_sqlite(m_stmt_player_update, 6, sao->getHP()); + int64_to_sqlite(m_stmt_player_update, 7, sao->getBreath()); + str_to_sqlite(m_stmt_player_update, 8, player->getName()); + + sqlite3_vrfy(sqlite3_step(m_stmt_player_update), SQLITE_DONE); + sqlite3_reset(m_stmt_player_update); } - sqlite3_reset(m_stmt_list); + + // Write player inventories + str_to_sqlite(m_stmt_player_remove_inventory, 1, player->getName()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory), SQLITE_DONE); + sqlite3_reset(m_stmt_player_remove_inventory); + + str_to_sqlite(m_stmt_player_remove_inventory_items, 1, player->getName()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE); + sqlite3_reset(m_stmt_player_remove_inventory_items); + + std::vector inventory_lists = sao->getInventory()->getLists(); + for (u16 i = 0; i < inventory_lists.size(); i++) { + const InventoryList* list = inventory_lists[i]; + + str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName()); + int_to_sqlite(m_stmt_player_add_inventory, 2, i); + int_to_sqlite(m_stmt_player_add_inventory, 3, list->getWidth()); + str_to_sqlite(m_stmt_player_add_inventory, 4, list->getName()); + int_to_sqlite(m_stmt_player_add_inventory, 5, list->getSize()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory), SQLITE_DONE); + sqlite3_reset(m_stmt_player_add_inventory); + + for (u32 j = 0; j < list->getSize(); j++) { + std::ostringstream os; + list->getItem(j).serialize(os); + std::string itemStr = os.str(); + + str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName()); + int_to_sqlite(m_stmt_player_add_inventory_items, 2, i); + int_to_sqlite(m_stmt_player_add_inventory_items, 3, j); + str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr); + sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE); + sqlite3_reset(m_stmt_player_add_inventory_items); + } + } + + str_to_sqlite(m_stmt_player_metadata_remove, 1, player->getName()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_remove), SQLITE_DONE); + sqlite3_reset(m_stmt_player_metadata_remove); + + const PlayerAttributes &attrs = sao->getExtendedAttributes(); + for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) { + str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName()); + str_to_sqlite(m_stmt_player_metadata_add, 2, it->first); + str_to_sqlite(m_stmt_player_metadata_add, 3, it->second); + sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE); + sqlite3_reset(m_stmt_player_metadata_add); + } + + endSave(); } -Database_SQLite3::~Database_SQLite3() +bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao) { - FINALIZE_STATEMENT(m_stmt_read) - FINALIZE_STATEMENT(m_stmt_write) - FINALIZE_STATEMENT(m_stmt_list) - FINALIZE_STATEMENT(m_stmt_begin) - FINALIZE_STATEMENT(m_stmt_end) - FINALIZE_STATEMENT(m_stmt_delete) + verifyDatabase(); - SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database"); + str_to_sqlite(m_stmt_player_load, 1, player->getName()); + if (sqlite3_step(m_stmt_player_load) != SQLITE_ROW) { + sqlite3_reset(m_stmt_player_load); + return false; + } + sao->setPitch(sqlite_to_float(m_stmt_player_load, 0)); + sao->setYaw(sqlite_to_float(m_stmt_player_load, 1)); + sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2)); + sao->setHPRaw((s16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), S16_MAX)); + sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false); + sqlite3_reset(m_stmt_player_load); + + // Load inventory + str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName()); + while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) { + InventoryList *invList = player->inventory.addList( + sqlite_to_string(m_stmt_player_load_inventory, 2), + sqlite_to_uint(m_stmt_player_load_inventory, 3)); + invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1)); + + u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0); + + str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName()); + int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId); + while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) { + const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1); + if (itemStr.length() > 0) { + ItemStack stack; + stack.deSerialize(itemStr); + invList->addItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack); + } + } + sqlite3_reset(m_stmt_player_load_inventory_items); + } + + sqlite3_reset(m_stmt_player_load_inventory); + + str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName()); + while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) { + std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0); + std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1); + + sao->setExtendedAttribute(attr, value); + } + sqlite3_reset(m_stmt_player_metadata_load); + return true; +} + +bool PlayerDatabaseSQLite3::removePlayer(const std::string &name) +{ + if (!playerDataExists(name)) + return false; + + str_to_sqlite(m_stmt_player_remove, 1, name); + sqlite3_vrfy(sqlite3_step(m_stmt_player_remove), SQLITE_DONE); + sqlite3_reset(m_stmt_player_remove); + return true; } +void PlayerDatabaseSQLite3::listPlayers(std::vector &res) +{ + verifyDatabase(); + + while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW) + res.push_back(sqlite_to_string(m_stmt_player_list, 0)); + + sqlite3_reset(m_stmt_player_list); +} diff --git a/src/database-sqlite3.h b/src/database-sqlite3.h index 2ab4c8ee9..3244facc9 100644 --- a/src/database-sqlite3.h +++ b/src/database-sqlite3.h @@ -20,8 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef DATABASE_SQLITE3_HEADER #define DATABASE_SQLITE3_HEADER +#include #include #include "database.h" +#include "exceptions.h" extern "C" { #include "sqlite3.h" @@ -30,37 +32,97 @@ extern "C" { class Database_SQLite3 : public Database { public: - Database_SQLite3(const std::string &savedir); - ~Database_SQLite3(); + virtual ~Database_SQLite3(); void beginSave(); void endSave(); - bool saveBlock(const v3s16 &pos, const std::string &data); - void loadBlock(const v3s16 &pos, std::string *block); - bool deleteBlock(const v3s16 &pos); - void listAllLoadableBlocks(std::vector &dst); bool initialized() const { return m_initialized; } +protected: + Database_SQLite3(const std::string &savedir, const std::string &dbname); -private: - // Open the database - void openDatabase(); - // Create the database structure - void createDatabase(); // Open and initialize the database if needed void verifyDatabase(); - void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1); + // Convertors + inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const std::string &str) const + { + sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.c_str(), str.size(), NULL)); + } + + inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const char *str) const + { + sqlite3_vrfy(sqlite3_bind_text(s, iCol, str, strlen(str), NULL)); + } + + inline void int_to_sqlite(sqlite3_stmt *s, int iCol, int val) const + { + sqlite3_vrfy(sqlite3_bind_int(s, iCol, val)); + } + + inline void int64_to_sqlite(sqlite3_stmt *s, int iCol, s64 val) const + { + sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64) val)); + } + + inline void double_to_sqlite(sqlite3_stmt *s, int iCol, double val) const + { + sqlite3_vrfy(sqlite3_bind_double(s, iCol, val)); + } + + inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol) + { + const char* text = reinterpret_cast(sqlite3_column_text(s, iCol)); + return std::string(text ? text : ""); + } + + inline s32 sqlite_to_int(sqlite3_stmt *s, int iCol) + { + return sqlite3_column_int(s, iCol); + } + + inline u32 sqlite_to_uint(sqlite3_stmt *s, int iCol) + { + return (u32) sqlite3_column_int(s, iCol); + } + + inline float sqlite_to_float(sqlite3_stmt *s, int iCol) + { + return (float) sqlite3_column_double(s, iCol); + } + + inline const v3f sqlite_to_v3f(sqlite3_stmt *s, int iCol) + { + return v3f(sqlite_to_float(s, iCol), sqlite_to_float(s, iCol + 1), + sqlite_to_float(s, iCol + 2)); + } + + // Query verifiers helpers + inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const + { + if (s != r) + throw DatabaseException(m + ": " + sqlite3_errmsg(m_database)); + } + + inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const + { + sqlite3_vrfy(s, m, r); + } + + // Create the database structure + virtual void createDatabase() = 0; + virtual void initStatements() = 0; + + sqlite3 *m_database; +private: + // Open the database + void openDatabase(); bool m_initialized; std::string m_savedir; + std::string m_dbname; - sqlite3 *m_database; - sqlite3_stmt *m_stmt_read; - sqlite3_stmt *m_stmt_write; - sqlite3_stmt *m_stmt_list; - sqlite3_stmt *m_stmt_delete; sqlite3_stmt *m_stmt_begin; sqlite3_stmt *m_stmt_end; @@ -69,4 +131,66 @@ private: static int busyHandler(void *data, int count); }; +class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase +{ +public: + MapDatabaseSQLite3(const std::string &savedir); + virtual ~MapDatabaseSQLite3(); + + bool saveBlock(const v3s16 &pos, const std::string &data); + void loadBlock(const v3s16 &pos, std::string *block); + bool deleteBlock(const v3s16 &pos); + void listAllLoadableBlocks(std::vector &dst); + + void beginSave() { Database_SQLite3::beginSave(); } + void endSave() { Database_SQLite3::endSave(); } +protected: + virtual void createDatabase(); + virtual void initStatements(); + +private: + void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1); + + // Map + sqlite3_stmt *m_stmt_read; + sqlite3_stmt *m_stmt_write; + sqlite3_stmt *m_stmt_list; + sqlite3_stmt *m_stmt_delete; +}; + +class PlayerDatabaseSQLite3 : private Database_SQLite3, public PlayerDatabase +{ +public: + PlayerDatabaseSQLite3(const std::string &savedir); + virtual ~PlayerDatabaseSQLite3(); + + void savePlayer(RemotePlayer *player); + bool loadPlayer(RemotePlayer *player, PlayerSAO *sao); + bool removePlayer(const std::string &name); + void listPlayers(std::vector &res); + +protected: + virtual void createDatabase(); + virtual void initStatements(); + +private: + bool playerDataExists(const std::string &name); + + // Players + sqlite3_stmt *m_stmt_player_load; + sqlite3_stmt *m_stmt_player_add; + sqlite3_stmt *m_stmt_player_update; + sqlite3_stmt *m_stmt_player_remove; + sqlite3_stmt *m_stmt_player_list; + sqlite3_stmt *m_stmt_player_load_inventory; + sqlite3_stmt *m_stmt_player_load_inventory_items; + sqlite3_stmt *m_stmt_player_add_inventory; + sqlite3_stmt *m_stmt_player_add_inventory_items; + sqlite3_stmt *m_stmt_player_remove_inventory; + sqlite3_stmt *m_stmt_player_remove_inventory_items; + sqlite3_stmt *m_stmt_player_metadata_load; + sqlite3_stmt *m_stmt_player_metadata_remove; + sqlite3_stmt *m_stmt_player_metadata_add; +}; + #endif diff --git a/src/database.cpp b/src/database.cpp index 262d475ec..8e1483893 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -48,7 +48,7 @@ static inline s64 pythonmodulo(s64 i, s16 mod) } -s64 Database::getBlockAsInteger(const v3s16 &pos) +s64 MapDatabase::getBlockAsInteger(const v3s16 &pos) { return (u64) pos.Z * 0x1000000 + (u64) pos.Y * 0x1000 + @@ -56,7 +56,7 @@ s64 Database::getBlockAsInteger(const v3s16 &pos) } -v3s16 Database::getIntegerAsBlock(s64 i) +v3s16 MapDatabase::getIntegerAsBlock(s64 i) { v3s16 pos; pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048); diff --git a/src/database.h b/src/database.h index 7213f088a..5a2b844fd 100644 --- a/src/database.h +++ b/src/database.h @@ -29,10 +29,15 @@ with this program; if not, write to the Free Software Foundation, Inc., class Database { public: - virtual ~Database() {} + virtual void beginSave() = 0; + virtual void endSave() = 0; + virtual bool initialized() const { return true; } +}; - virtual void beginSave() {} - virtual void endSave() {} +class MapDatabase : public Database +{ +public: + virtual ~MapDatabase() {} virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0; virtual void loadBlock(const v3s16 &pos, std::string *block) = 0; @@ -42,8 +47,19 @@ public: static v3s16 getIntegerAsBlock(s64 i); virtual void listAllLoadableBlocks(std::vector &dst) = 0; +}; - virtual bool initialized() const { return true; } +class PlayerSAO; +class RemotePlayer; + +class PlayerDatabase +{ +public: + virtual ~PlayerDatabase() {} + virtual void savePlayer(RemotePlayer *player) = 0; + virtual bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) = 0; + virtual bool removePlayer(const std::string &name) = 0; + virtual void listPlayers(std::vector &res) = 0; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 1ec278981..2ad4e2780 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif #ifndef SERVER #include "client/clientlauncher.h" + #endif #ifdef HAVE_TOUCHSCREENGUI @@ -102,7 +103,7 @@ static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_a static bool determine_subgame(GameParams *game_params); static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); -static bool migrate_database(const GameParams &game_params, const Settings &cmd_args); +static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args); /**********************************************************************/ @@ -292,6 +293,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Set gameid (\"--gameid list\" prints available ones)")))); allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING, _("Migrate from current map backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING, + _("Migrate from current players backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); #ifndef SERVER @@ -332,7 +335,7 @@ static void print_allowed_options(const OptionList &allowed_options) if (i->second.type != VALUETYPE_FLAG) os1 << _(" "); - std::cout << padStringRight(os1.str(), 24); + std::cout << padStringRight(os1.str(), 30); if (i->second.help != NULL) std::cout << i->second.help; @@ -828,7 +831,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & // Database migration if (cmd_args.exists("migrate")) - return migrate_database(game_params, cmd_args); + return migrate_map_database(game_params, cmd_args); + else if (cmd_args.exists("migrate-players")) + return ServerEnvironment::migratePlayersDatabase(game_params, cmd_args); if (cmd_args.exists("terminal")) { #if USE_CURSES @@ -912,7 +917,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return true; } -static bool migrate_database(const GameParams &game_params, const Settings &cmd_args) +static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args) { std::string migrate_to = cmd_args.get("migrate"); Settings world_mt; @@ -921,20 +926,23 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_ errorstream << "Cannot read world.mt!" << std::endl; return false; } + if (!world_mt.exists("backend")) { errorstream << "Please specify your current backend in world.mt:" << std::endl - << " backend = {sqlite3|leveldb|redis|dummy}" + << " backend = {sqlite3|leveldb|redis|dummy|postgresql}" << std::endl; return false; } + std::string backend = world_mt.get("backend"); if (backend == migrate_to) { errorstream << "Cannot migrate: new backend is same" << " as the old one" << std::endl; return false; } - Database *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt), + + MapDatabase *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt), *new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt); u32 count = 0; @@ -976,4 +984,3 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_ return true; } - diff --git a/src/map.cpp b/src/map.cpp index 75dcee350..c148c51f1 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2286,13 +2286,13 @@ bool ServerMap::loadSectorFull(v2s16 p2d) } #endif -Database *ServerMap::createDatabase( +MapDatabase *ServerMap::createDatabase( const std::string &name, const std::string &savedir, Settings &conf) { if (name == "sqlite3") - return new Database_SQLite3(savedir); + return new MapDatabaseSQLite3(savedir); if (name == "dummy") return new Database_Dummy(); #if USE_LEVELDB @@ -2304,8 +2304,11 @@ Database *ServerMap::createDatabase( return new Database_Redis(conf); #endif #if USE_POSTGRESQL - else if (name == "postgresql") - return new Database_PostgreSQL(conf); + else if (name == "postgresql") { + std::string connect_string = ""; + conf.getNoEx("pgsql_connection", connect_string); + return new MapDatabasePostgreSQL(connect_string); + } #endif else throw BaseException(std::string("Database backend ") + name + " not supported."); @@ -2326,7 +2329,7 @@ bool ServerMap::saveBlock(MapBlock *block) return saveBlock(block, dbase); } -bool ServerMap::saveBlock(MapBlock *block, Database *db) +bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db) { v3s16 p3d = block->getPos(); diff --git a/src/map.h b/src/map.h index 4d7079823..7e597bef6 100644 --- a/src/map.h +++ b/src/map.h @@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map_settings_manager.h" class Settings; -class Database; +class MapDatabase; class ClientMap; class MapSector; class ServerMapSector; @@ -430,7 +430,7 @@ public: /* Database functions */ - static Database *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); + static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); // Returns true if the database file does not exist bool loadFromFolders(); @@ -458,7 +458,7 @@ public: bool loadSectorMeta(v2s16 p2d); bool saveBlock(MapBlock *block); - static bool saveBlock(MapBlock *block, Database *db); + static bool saveBlock(MapBlock *block, MapDatabase *db); // This will generate a sector with getSector if not found. void loadBlock(const std::string §ordir, const std::string &blockfile, MapSector *sector, bool save_after_load=false); @@ -510,7 +510,7 @@ private: This is reset to false when written on disk. */ bool m_map_metadata_changed; - Database *dbase; + MapDatabase *dbase; }; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index c8e5b9132..2dbfe9d9d 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -67,54 +67,6 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): movement_gravity = g_settings->getFloat("movement_gravity") * BS; } -void RemotePlayer::save(std::string savedir, IGameDef *gamedef) -{ - /* - * We have to open all possible player files in the players directory - * and check their player names because some file systems are not - * case-sensitive and player names are case-sensitive. - */ - - // A player to deserialize files into to check their names - RemotePlayer testplayer("", gamedef->idef()); - - savedir += DIR_DELIM; - std::string path = savedir + m_name; - for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { - if (!fs::PathExists(path)) { - // Open file and serialize - std::ostringstream ss(std::ios_base::binary); - serialize(ss); - if (!fs::safeWriteToFile(path, ss.str())) { - infostream << "Failed to write " << path << std::endl; - } - setModified(false); - return; - } - // Open file and deserialize - std::ifstream is(path.c_str(), std::ios_base::binary); - if (!is.good()) { - infostream << "Failed to open " << path << std::endl; - return; - } - testplayer.deSerialize(is, path, NULL); - is.close(); - if (strcmp(testplayer.getName(), m_name) == 0) { - // Open file and serialize - std::ostringstream ss(std::ios_base::binary); - serialize(ss); - if (!fs::safeWriteToFile(path, ss.str())) { - infostream << "Failed to write " << path << std::endl; - } - setModified(false); - return; - } - path = savedir + m_name + itos(i); - } - - infostream << "Didn't find free file for player " << m_name << std::endl; -} - void RemotePlayer::serializeExtraAttributes(std::string &output) { assert(m_sao); diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 9d123393f..ce7db5608 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -37,11 +37,11 @@ enum RemotePlayerChatResult */ class RemotePlayer : public Player { + friend class PlayerDatabaseFiles; public: RemotePlayer(const char *name, IItemDefManager *idef); virtual ~RemotePlayer() {} - void save(std::string savedir, IGameDef *gamedef); void deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao); PlayerSAO *getPlayerSAO() { return m_sao; } diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 6ac4bb653..d94f3e31d 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -334,6 +334,22 @@ int ModApiServer::l_kick_player(lua_State *L) return 1; } +int ModApiServer::l_remove_player(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + std::string name = luaL_checkstring(L, 1); + ServerEnvironment *s_env = dynamic_cast(getEnv(L)); + assert(s_env); + + RemotePlayer *player = s_env->getPlayer(name.c_str()); + if (!player) + lua_pushinteger(L, s_env->removePlayerFromDatabase(name) ? 0 : 1); + else + lua_pushinteger(L, 2); + + return 1; +} + // unban_player_or_ip() int ModApiServer::l_unban_player_or_ip(lua_State *L) { @@ -510,6 +526,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(get_ban_description); API_FCT(ban_player); API_FCT(kick_player); + API_FCT(remove_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 008810784..e6c0df978 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -92,6 +92,9 @@ private: // kick_player(name, [message]) -> success static int l_kick_player(lua_State *L); + // remove_player(name) + static int l_remove_player(lua_State *L); + // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index ac6265d09..4c7e60286 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -60,6 +60,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/base64.h" #include "util/sha1.h" #include "util/hex.h" +#include "database.h" class ClientNotFoundException : public BaseException { @@ -2618,9 +2619,8 @@ void Server::RespawnPlayer(u16 peer_id) bool repositioned = m_script->on_respawnplayer(playersao); if (!repositioned) { - v3f pos = findSpawnPos(); // setPos will send the new position to client - playersao->setPos(pos); + playersao->setPos(findSpawnPos()); } SendPlayerHP(peer_id); @@ -3442,8 +3442,8 @@ v3f Server::findSpawnPos() s32 range = 1 + i; // We're going to try to throw the player to this position v2s16 nodepos2d = v2s16( - -range + (myrand() % (range * 2)), - -range + (myrand() % (range * 2))); + -range + (myrand() % (range * 2)), + -range + (myrand() % (range * 2))); // Get spawn level at point s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d); @@ -3516,8 +3516,6 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version) { - bool newplayer = false; - /* Try to get an existing player */ @@ -3538,44 +3536,18 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version return NULL; } - // Create a new player active object - PlayerSAO *playersao = new PlayerSAO(m_env, peer_id, isSingleplayer()); - player = m_env->loadPlayer(name, playersao); - - // Create player if it doesn't exist if (!player) { - newplayer = true; - player = new RemotePlayer(name, this->idef()); - // Set player position - infostream<<"Server: Finding spawn place for player \"" - <setBasePosition(findSpawnPos()); - - // Make sure the player is saved - player->setModified(true); - - // Add player to environment - m_env->addPlayer(player); - } else { - // If the player exists, ensure that they respawn inside legal bounds - // This fixes an assert crash when the player can't be added - // to the environment - if (objectpos_over_limit(playersao->getBasePosition())) { - actionstream << "Respawn position for player \"" - << name << "\" outside limits, resetting" << std::endl; - playersao->setBasePosition(findSpawnPos()); - } + player = new RemotePlayer(name, idef()); } - playersao->initialize(player, getPlayerEffectivePrivs(player->getName())); - - player->protocol_version = proto_version; + bool newplayer = false; - /* Clean up old HUD elements from previous sessions */ - player->clearHud(); + // Load player + PlayerSAO *playersao = m_env->loadPlayer(player, &newplayer, peer_id, isSingleplayer()); - /* Add object to environment */ - m_env->addActiveObject(playersao); + // Complete init with server parts + playersao->finalize(player, getPlayerEffectivePrivs(player->getName())); + player->protocol_version = proto_version; /* Run scripts */ if (newplayer) { diff --git a/src/server.h b/src/server.h index 4183bcda1..948fb8fc2 100644 --- a/src/server.h +++ b/src/server.h @@ -306,6 +306,7 @@ public: bool showFormspec(const char *name, const std::string &formspec, const std::string &formname); Map & getMap() { return m_env->getMap(); } ServerEnvironment & getEnv() { return *m_env; } + v3f findSpawnPos(); u32 hudAdd(RemotePlayer *player, HudElement *element); bool hudRemove(RemotePlayer *player, u32 id); @@ -472,8 +473,6 @@ private: RemotePlayer *player = NULL); void handleAdminChat(const ChatEventChat *evt); - v3f findSpawnPos(); - // When called, connection mutex should be locked RemoteClient* getClient(u16 peer_id,ClientState state_min=CS_Active); RemoteClient* getClientNoEx(u16 peer_id,ClientState state_min=CS_Active); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index e09c7da16..c0dc0e0ea 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -36,6 +36,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/pointedthing.h" #include "threading/mutex_auto_lock.h" #include "filesys.h" +#include "gameparams.h" +#include "database-dummy.h" +#include "database-files.h" +#include "database-sqlite3.h" +#if USE_POSTGRESQL +#include "database-postgresql.h" +#endif #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:" @@ -365,8 +372,30 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, m_game_time_fraction_counter(0), m_last_clear_objects_time(0), m_recommended_send_interval(0.1), - m_max_lag_estimate(0.1) + m_max_lag_estimate(0.1), + m_player_database(NULL) { + // Determine which database backend to use + std::string conf_path = path_world + DIR_DELIM + "world.mt"; + Settings conf; + bool succeeded = conf.readConfigFile(conf_path.c_str()); + if (!succeeded || !conf.exists("player_backend")) { + // fall back to files + conf.set("player_backend", "files"); + warningstream << "/!\\ You are using old player file backend. " + << "This backend is deprecated and will be removed in next release /!\\" + << std::endl << "Switching to SQLite3 or PostgreSQL is advised, " + << "please read http://wiki.minetest.net/Database_backends." << std::endl; + + if (!conf.updateConfigFile(conf_path.c_str())) { + errorstream << "ServerEnvironment::ServerEnvironment(): " + << "Failed to update world.mt!" << std::endl; + } + } + + std::string name = ""; + conf.getNoEx("player_backend", name); + m_player_database = openPlayerDatabase(name, path_world, conf); } ServerEnvironment::~ServerEnvironment() @@ -392,6 +421,8 @@ ServerEnvironment::~ServerEnvironment() i != m_players.end(); ++i) { delete (*i); } + + delete m_player_database; } Map & ServerEnvironment::getMap() @@ -455,6 +486,11 @@ void ServerEnvironment::removePlayer(RemotePlayer *player) } } +bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) +{ + return m_player_database->removePlayer(name); +} + bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p) { float distance = pos1.getDistanceFrom(pos2); @@ -495,7 +531,7 @@ void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, void ServerEnvironment::saveLoadedPlayers() { - std::string players_path = m_path_world + DIR_DELIM "players"; + std::string players_path = m_path_world + DIR_DELIM + "players"; fs::CreateDir(players_path); for (std::vector::iterator it = m_players.begin(); @@ -503,63 +539,63 @@ void ServerEnvironment::saveLoadedPlayers() ++it) { if ((*it)->checkModified() || ((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) { - (*it)->save(players_path, m_server); + try { + m_player_database->savePlayer(*it); + } catch (DatabaseException &e) { + errorstream << "Failed to save player " << (*it)->getName() << " exception: " + << e.what() << std::endl; + throw; + } } } } void ServerEnvironment::savePlayer(RemotePlayer *player) { - std::string players_path = m_path_world + DIR_DELIM "players"; - fs::CreateDir(players_path); - - player->save(players_path, m_server); + try { + m_player_database->savePlayer(player); + } catch (DatabaseException &e) { + errorstream << "Failed to save player " << player->getName() << " exception: " + << e.what() << std::endl; + throw; + } } -RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao) +PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player, + u16 peer_id, bool is_singleplayer) { - bool newplayer = false; - bool found = false; - std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM; - std::string path = players_path + playername; - - RemotePlayer *player = getPlayer(playername.c_str()); - if (!player) { - player = new RemotePlayer("", m_server->idef()); - newplayer = true; - } - - for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { - //// Open file and deserialize - std::ifstream is(path.c_str(), std::ios_base::binary); - if (!is.good()) - continue; - - player->deSerialize(is, path, sao); - is.close(); - - if (player->getName() == playername) { - found = true; - break; + PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer); + // Create player if it doesn't exist + if (!m_player_database->loadPlayer(player, playersao)) { + *new_player = true; + // Set player position + infostream << "Server: Finding spawn place for player \"" + << player->getName() << "\"" << std::endl; + playersao->setBasePosition(m_server->findSpawnPos()); + + // Make sure the player is saved + player->setModified(true); + } else { + // If the player exists, ensure that they respawn inside legal bounds + // This fixes an assert crash when the player can't be added + // to the environment + if (objectpos_over_limit(playersao->getBasePosition())) { + actionstream << "Respawn position for player \"" + << player->getName() << "\" outside limits, resetting" << std::endl; + playersao->setBasePosition(m_server->findSpawnPos()); } - - path = players_path + playername + itos(i); } - if (!found) { - infostream << "Player file for player " << playername - << " not found" << std::endl; - if (newplayer) - delete player; + // Add player to environment + addPlayer(player); - return NULL; - } + /* Clean up old HUD elements from previous sessions */ + player->clearHud(); - if (newplayer) { - addPlayer(player); - } - player->setModified(false); - return player; + /* Add object to environment */ + addActiveObject(playersao); + + return playersao; } void ServerEnvironment::saveMeta() @@ -2173,3 +2209,111 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) m_active_objects.erase(*i); } } + +PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name, + const std::string &savedir, const Settings &conf) +{ + + if (name == "sqlite3") + return new PlayerDatabaseSQLite3(savedir); + else if (name == "dummy") + return new Database_Dummy(); +#if USE_POSTGRESQL + else if (name == "postgresql") { + std::string connect_string = ""; + conf.getNoEx("pgsql_player_connection", connect_string); + return new PlayerDatabasePostgreSQL(connect_string); + } +#endif + else if (name == "files") + return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players"); + else + throw BaseException(std::string("Database backend ") + name + " not supported."); +} + +bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params, + const Settings &cmd_args) +{ + std::string migrate_to = cmd_args.get("migrate-players"); + Settings world_mt; + std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt!" << std::endl; + return false; + } + + if (!world_mt.exists("player_backend")) { + errorstream << "Please specify your current backend in world.mt:" + << std::endl + << " player_backend = {files|sqlite3|postgresql}" + << std::endl; + return false; + } + + std::string backend = world_mt.get("player_backend"); + if (backend == migrate_to) { + errorstream << "Cannot migrate: new backend is same" + << " as the old one" << std::endl; + return false; + } + + const std::string players_backup_path = game_params.world_path + DIR_DELIM + + "players.bak"; + + if (backend == "files") { + // Create backup directory + fs::CreateDir(players_backup_path); + } + + try { + PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend, + game_params.world_path, world_mt); + PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to, + game_params.world_path, world_mt); + + std::vector player_list; + srcdb->listPlayers(player_list); + for (std::vector::const_iterator it = player_list.begin(); + it != player_list.end(); ++it) { + actionstream << "Migrating player " << it->c_str() << std::endl; + RemotePlayer player(it->c_str(), NULL); + PlayerSAO playerSAO(NULL, &player, 15000, false); + + srcdb->loadPlayer(&player, &playerSAO); + + playerSAO.finalize(&player, std::set()); + player.setPlayerSAO(&playerSAO); + + dstdb->savePlayer(&player); + + // For files source, move player files to backup dir + if (backend == "files") { + fs::Rename( + game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it), + players_backup_path + DIR_DELIM + (*it)); + } + } + + actionstream << "Successfully migrated " << player_list.size() << " players" + << std::endl; + world_mt.set("player_backend", migrate_to); + if (!world_mt.updateConfigFile(world_mt_path.c_str())) + errorstream << "Failed to update world.mt!" << std::endl; + else + actionstream << "world.mt updated" << std::endl; + + // When migration is finished from file backend, remove players directory if empty + if (backend == "files") { + fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM + + "players"); + } + + delete srcdb; + delete dstdb; + + } catch (BaseException &e) { + errorstream << "An error occured during migration: " << e.what() << std::endl; + return false; + } + return true; +} diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 99110542a..0e31aa41a 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -27,7 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc., class IGameDef; class ServerMap; +struct GameParams; class RemotePlayer; +class PlayerDatabase; class PlayerSAO; class ServerEnvironment; class ActiveBlockModifier; @@ -217,9 +219,11 @@ public: // Save players void saveLoadedPlayers(); void savePlayer(RemotePlayer *player); - RemotePlayer *loadPlayer(const std::string &playername, PlayerSAO *sao); + PlayerSAO *loadPlayer(RemotePlayer *player, bool *new_player, u16 peer_id, + bool is_singleplayer); void addPlayer(RemotePlayer *player); void removePlayer(RemotePlayer *player); + bool removePlayerFromDatabase(const std::string &name); /* Save and load time of day and game timer @@ -334,8 +338,13 @@ public: RemotePlayer *getPlayer(const u16 peer_id); RemotePlayer *getPlayer(const char* name); + + static bool migratePlayersDatabase(const GameParams &game_params, + const Settings &cmd_args); private: + static PlayerDatabase *openPlayerDatabase(const std::string &name, + const std::string &savedir, const Settings &conf); /* Internal ActiveObject interface ------------------------------------------- @@ -419,6 +428,8 @@ private: // peer_ids in here should be unique, except that there may be many 0s std::vector m_players; + PlayerDatabase *m_player_database; + // Particles IntervalLimiter m_particle_management_interval; UNORDERED_MAP m_particle_spawners; diff --git a/src/unittest/test_player.cpp b/src/unittest/test_player.cpp index b639878b2..e2b1cd855 100644 --- a/src/unittest/test_player.cpp +++ b/src/unittest/test_player.cpp @@ -31,59 +31,10 @@ public: const char *getName() { return "TestPlayer"; } void runTests(IGameDef *gamedef); - - void testSave(IGameDef *gamedef); - void testLoad(IGameDef *gamedef); }; static TestPlayer g_test_instance; void TestPlayer::runTests(IGameDef *gamedef) { - TEST(testSave, gamedef); - TEST(testLoad, gamedef); -} - -void TestPlayer::testSave(IGameDef *gamedef) -{ - RemotePlayer rplayer("testplayer_save", gamedef->idef()); - PlayerSAO sao(NULL, 1, false); - sao.initialize(&rplayer, std::set()); - rplayer.setPlayerSAO(&sao); - sao.setBreath(10, false); - sao.setHPRaw(8); - sao.setYaw(0.1f); - sao.setPitch(0.6f); - sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f)); - rplayer.save(".", gamedef); - UASSERT(fs::PathExists("testplayer_save")); -} - -void TestPlayer::testLoad(IGameDef *gamedef) -{ - RemotePlayer rplayer("testplayer_load", gamedef->idef()); - PlayerSAO sao(NULL, 1, false); - sao.initialize(&rplayer, std::set()); - rplayer.setPlayerSAO(&sao); - sao.setBreath(10, false); - sao.setHPRaw(8); - sao.setYaw(0.1f); - sao.setPitch(0.6f); - sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f)); - rplayer.save(".", gamedef); - UASSERT(fs::PathExists("testplayer_load")); - - RemotePlayer rplayer_load("testplayer_load", gamedef->idef()); - PlayerSAO sao_load(NULL, 2, false); - std::ifstream is("testplayer_load", std::ios_base::binary); - UASSERT(is.good()); - rplayer_load.deSerialize(is, "testplayer_load", &sao_load); - is.close(); - - UASSERT(strcmp(rplayer_load.getName(), "testplayer_load") == 0); - UASSERT(sao_load.getBreath() == 10); - UASSERT(sao_load.getHP() == 8); - UASSERT(sao_load.getYaw() == 0.1f); - UASSERT(sao_load.getPitch() == 0.6f); - UASSERT(sao_load.getBasePosition() == v3f(450.2f, -15.7f, 68.1f)); } -- cgit v1.2.3 From 70ceeb80a180507e38db957f217e2c78ce901692 Mon Sep 17 00:00:00 2001 From: Bluebird Date: Wed, 26 Apr 2017 00:24:15 -0500 Subject: Tiny documentation fix. (#5659) There are plenty of lines longer than 80 characters, and spliting the function declaration across two lines can be momentarily confusing. --- doc/lua_api.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index c3d8d2bf6..4f90e9223 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1919,8 +1919,7 @@ Helper functions * `math.sign(x, tolerance)` * Get the sign of a number. Optional: Also returns `0` when the absolute value is within the tolerance (default: `0`) -* `string.split(str, separator=",", include_empty=false, max_splits=-1, -* sep_is_pattern=false)` +* `string.split(str, separator=",", include_empty=false, max_splits=-1, sep_is_pattern=false)` * If `max_splits` is negative, do not limit splits. * `sep_is_pattern` specifies if separator is a plain string or a pattern (regex). * e.g. `string:split("a,b", ",") == {"a","b"}` -- cgit v1.2.3 From d130e1fdc09398367bfed3e0d4a4b2574ca33ab1 Mon Sep 17 00:00:00 2001 From: Bluebird Date: Thu, 27 Apr 2017 04:49:07 -0500 Subject: Fix incorrect formspec-tooltip doc, add detail in 'floodable' & 'on_flood' docs (#5660) * Fix incorrect formspec tooltip documentation * Improve `floodable` and `on_flood` documentation. The original documentation did not specify that liquids should not themselves be floodable. This is probably something that should be mentioned. --- doc/lua_api.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 4f90e9223..d4141b5d4 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1580,7 +1580,7 @@ examples. * Sets default background color of tooltips * Sets default font color of tooltips -#### `tooltip[;;,]` +#### `tooltip[;;;]` * Adds tooltip for an element * `` tooltip background color as `ColorString` (optional) * `` tooltip font color as `ColorString` (optional) @@ -3942,7 +3942,9 @@ Definition tables diggable = true, -- If false, can never be dug climbable = false, -- If true, can be climbed on (ladder) buildable_to = false, -- If true, placed nodes can replace this node - floodable = false, -- If true, liquids flow into and replace this node + floodable = false, --[[ + ^ If true, liquids flow into and replace this node. + ^ Warning: making a liquid node 'floodable' does not work and may cause problems. ]] liquidtype = "none", -- "none"/"source"/"flowing" liquid_alternative_flowing = "", -- Flowing version of source liquid liquid_alternative_source = "", -- Source version of flowing liquid @@ -4009,7 +4011,8 @@ Definition tables ^ node placement (i.e. schematics and VoxelManip) or air nodes. If ^ return true the node is not flooded, but on_flood callback will ^ most likely be called over and over again every liquid update - ^ interval. Default: nil ]] + ^ interval. Default: nil. + ^ Warning: making a liquid node 'floodable' does not work and may cause problems. ]] after_place_node = func(pos, placer, itemstack, pointed_thing) --[[ ^ Called after constructing node when node was placed using -- cgit v1.2.3 From 1ef9eee31133a3001ed0c642df5cbe54169850de Mon Sep 17 00:00:00 2001 From: red-001 Date: Thu, 27 Apr 2017 10:49:44 +0100 Subject: Allow scripts to get the client protocol version in non-debug builds. (#5649) --- doc/lua_api.txt | 2 +- src/script/lua_api/l_server.cpp | 10 +++++----- src/script/lua_api/l_server.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index d4141b5d4..b47046cb1 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1991,11 +1991,11 @@ Helper functions max_jitter = 0.5, -- maximum packet time jitter avg_jitter = 0.03, -- average packet time jitter connection_uptime = 200, -- seconds since client connected + prot_vers = 31, -- protocol version used by client -- following information is available on debug build only!!! -- DO NOT USE IN MODS --ser_vers = 26, -- serialization version used by client - --prot_vers = 23, -- protocol version used by client --major = 0, -- major version number --minor = 4, -- minor version number --patch = 10, -- patch version number diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 813d5a945..7b723d14c 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -137,7 +137,7 @@ int ModApiServer::l_get_player_ip(lua_State *L) } } -// get_player_information() +// get_player_information(name) int ModApiServer::l_get_player_information(lua_State *L) { @@ -231,15 +231,15 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_pushnumber(L, uptime); lua_settable(L, table); + lua_pushstring(L,"protocol_version"); + lua_pushnumber(L, prot_vers); + lua_settable(L, table); + #ifndef NDEBUG lua_pushstring(L,"serialization_version"); lua_pushnumber(L, ser_vers); lua_settable(L, table); - lua_pushstring(L,"protocol_version"); - lua_pushnumber(L, prot_vers); - lua_settable(L, table); - lua_pushstring(L,"major"); lua_pushnumber(L, major); lua_settable(L, table); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index e6c0df978..3a4a917c0 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -74,7 +74,7 @@ private: // get_player_ip() static int l_get_player_ip(lua_State *L); - // get_player_information() + // get_player_information(name) static int l_get_player_information(lua_State *L); // get_ban_list() -- cgit v1.2.3 From e21a1ab3bd31f9b854ef77c33698624755fc915c Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Fri, 28 Apr 2017 11:11:43 -0700 Subject: Allow mesh and nodeboxes to wave like plants or leaves. (#3497) We introduce a new value for "waving" - 2: 0 - waving disabled 1 - wave like a plant 2 - wave like a leave Plantlike nodes will only allow waving = 1, but for leaves we will permit both 1 and 2 since current minetest_game sets it to 1 for all leaves. This makes it somewhat backwards compatible. For mesh and nodebox, values 1 and 2 are both valid, and the node can wave in both fashions as desired. I've tested this with the crops:corn plants, which are mesh nodes, and the results are really good. The code change is trivial as well, so I've opted to document the waving parameter in lua_api.txt because it was missing from there. Nodeboxes likely will not wave properly unless waving = 2. However it's possible that waving=1 may be desired by some mod developers for geometries I have not tried, so the code will not prohibit either value for mesh and nodebox drawtypes. Add lua_api.txt documentation for this feature and document both the existing functionality and the expansion to mesh and nodebox drawtypes. --- doc/lua_api.txt | 6 ++++++ src/nodedef.cpp | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index b47046cb1..603619ab0 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3973,6 +3973,12 @@ Definition tables ^ If drawtype "nodebox" is used and selection_box is nil, then node_box is used. ]] legacy_facedir_simple = false, -- Support maps made in and before January 2012 legacy_wallmounted = false, -- Support maps made in and before January 2012 + waving = 0, --[[ valid for mesh, nodebox, plantlike, allfaces_optional nodes + ^ 1 - wave node like plants (top of node moves, bottom is fixed) + ^ 2 - wave node like leaves (whole node moves side-to-side synchronously) + ^ caveats: not all models will properly wave + ^ plantlike drawtype nodes can only wave like plants + ^ allfaces_optional drawtype nodes can only wave like leaves --]] sounds = { footstep = , dig = , -- "__group" = group-based sound (default) diff --git a/src/nodedef.cpp b/src/nodedef.cpp index ce2834c91..1bc483077 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -733,25 +733,29 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc for (u32 i = 0; i < 6; i++) tdef[i].name += std::string("^[noalpha"); } - if (waving == 1) + if (waving >= 1) material_type = TILE_MATERIAL_WAVING_LEAVES; break; case NDT_PLANTLIKE: solidness = 0; - if (waving == 1) + if (waving >= 1) material_type = TILE_MATERIAL_WAVING_PLANTS; break; case NDT_FIRELIKE: solidness = 0; break; case NDT_MESH: + case NDT_NODEBOX: solidness = 0; + if (waving == 1) + material_type = TILE_MATERIAL_WAVING_PLANTS; + else if (waving == 2) + material_type = TILE_MATERIAL_WAVING_LEAVES; break; case NDT_TORCHLIKE: case NDT_SIGNLIKE: case NDT_FENCELIKE: case NDT_RAILLIKE: - case NDT_NODEBOX: solidness = 0; break; } -- cgit v1.2.3 From 7f4cdbcbe9b5b4655c2c5eba2043628487668e24 Mon Sep 17 00:00:00 2001 From: you Date: Fri, 28 Apr 2017 20:12:28 +0200 Subject: Fix click-digging torches (#5652) Torches are dug instantly again. When the digging time is 0, a delay of 0.15 seconds is added between digging nodes. If the left mouse button is released, the delay is set to 0, thus click-digging. --- doc/lua_api.txt | 12 ++++++++---- src/game.cpp | 34 +++++++++++++++++++++------------- src/tool.cpp | 2 +- 3 files changed, 30 insertions(+), 18 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 603619ab0..eba8a5fef 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1230,9 +1230,9 @@ Another example: Make red wool from white wool and red dye: from destroyed nodes. * `0` is something that is directly accessible at the start of gameplay * There is no upper limit -* `dig_immediate`: (player can always pick up node without tool wear) - * `2`: node is removed without tool wear after 0.5 seconds (rail, sign) - * `3`: node is removed without tool wear after 0.15 seconds (torch) +* `dig_immediate`: (player can always pick up node without reducing tool wear) + * `2`: the node always gets the digging time 0.5 seconds (rail, sign) + * `3`: the node always gets the digging time 0 seconds (torch) * `disable_jump`: Player (and possibly other things) cannot jump from node * `fall_damage_add_percent`: damage speed = `speed * (1 + value/100)` * `bouncy`: value is bounce speed in percent @@ -1333,6 +1333,10 @@ result in the tool to be able to dig nodes that have a rating of `2` or `3` for this group, and unable to dig the rating `1`, which is the toughest. Unless there is a matching group that enables digging otherwise. +If the result digging time is 0, a delay of 0.15 seconds is added between +digging nodes; If the player releases LMB after digging, this delay is set to 0, +i.e. players can more quickly click the nodes away instead of holding LMB. + #### Damage groups List of damage for groups of entities. See "Entity damage mechanism". @@ -3445,7 +3449,7 @@ will place the schematic inside of the VoxelManip. If `light` is false, no light calculations happen, and you should correct all modified blocks with `minetest.fix_light()` as soon as possible. Keep in mind that modifying the map where light is incorrect can cause - more lighting bugs. + more lighting bugs. * `get_node_at(pos)`: Returns a `MapNode` table of the node currently loaded in the `VoxelManip` at that position * `set_node_at(pos, node)`: Sets a specific `MapNode` in the `VoxelManip` at that position diff --git a/src/game.cpp b/src/game.cpp index 82bd440df..eb59ee5ae 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1117,6 +1117,7 @@ struct GameRunData { PointedThing pointed_old; bool digging; bool ldown_for_dig; + bool dig_instantly; bool left_punch; bool update_wielded_item_trigger; bool reset_jump_timer; @@ -3495,6 +3496,10 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) client->setCrack(-1, v3s16(0, 0, 0)); runData.dig_time = 0.0; } + } else if (runData.dig_instantly && getLeftReleased()) { + // Remove e.g. torches faster when clicking instead of holding LMB + runData.nodig_delay_timer = 0; + runData.dig_instantly = false; } if (!runData.digging && runData.ldown_for_dig && !isLeftPressed()) { @@ -3807,15 +3812,6 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, ClientMap &map = client->getEnv().getClientMap(); MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos); - if (!runData.digging) { - infostream << "Started digging" << std::endl; - if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n)) - return; - client->interact(0, pointed); - runData.digging = true; - runData.ldown_for_dig = true; - } - // NOTE: Similar piece of code exists on the server side for // cheat detection. // Get digging parameters @@ -3833,6 +3829,16 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, params = getDigParams(nodedef_manager->get(n).groups, tp); } + if (!runData.digging) { + infostream << "Started digging" << std::endl; + runData.dig_instantly = params.time == 0; + if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n)) + return; + client->interact(0, pointed); + runData.digging = true; + runData.ldown_for_dig = true; + } + if (!params.diggable) { // I guess nobody will wait for this long runData.dig_time_complete = 10000000.0; @@ -3847,12 +3853,12 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, } } - if (runData.dig_time_complete >= 0.001) { + if (!runData.dig_instantly) { runData.dig_index = (float)crack_animation_length * runData.dig_time / runData.dig_time_complete; } else { - // This is for torches + // This is for e.g. torches runData.dig_index = crack_animation_length; } @@ -3887,10 +3893,12 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, runData.nodig_delay_timer = runData.dig_time_complete / (float)crack_animation_length; - // We don't want a corresponding delay to - // very time consuming nodes + // We don't want a corresponding delay to very time consuming nodes + // and nodes without digging time (e.g. torches) get a fixed delay. if (runData.nodig_delay_timer > 0.3) runData.nodig_delay_timer = 0.3; + else if (runData.dig_instantly) + runData.nodig_delay_timer = 0.15; bool is_valid_position; MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position); diff --git a/src/tool.cpp b/src/tool.cpp index 105102dd5..bb884938c 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -98,7 +98,7 @@ DigParams getDigParams(const ItemGroupList &groups, return DigParams(true, 0.5, 0, "dig_immediate"); case 3: //infostream<<"dig_immediate=3"< Date: Fri, 17 Mar 2017 10:39:47 +0100 Subject: Add clouds API --- doc/lua_api.txt | 9 ++++ src/client.h | 11 +++++ src/cloudparams.h | 33 ++++++++++++++ src/clouds.cpp | 90 ++++++++++++++++++++----------------- src/clouds.h | 54 +++++++++++++++++++--- src/game.cpp | 13 ++++++ src/network/clientopcodes.cpp | 2 +- src/network/clientpackethandler.cpp | 28 ++++++++++++ src/network/networkprotocol.h | 10 +++++ src/network/serveropcodes.cpp | 2 +- src/remoteplayer.cpp | 8 ++++ src/remoteplayer.h | 12 +++++ src/script/lua_api/l_object.cpp | 81 +++++++++++++++++++++++++++++++++ src/script/lua_api/l_object.h | 6 +++ src/server.cpp | 30 +++++++++++++ src/server.h | 12 +++++ src/sky.cpp | 6 ++- 17 files changed, 357 insertions(+), 50 deletions(-) create mode 100644 src/cloudparams.h (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index eba8a5fef..479e38a2e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3077,6 +3077,15 @@ This is basically a reference to a C++ `ServerActiveObject` * `"skybox"`: Uses 6 textures, `bgcolor` used * `"plain"`: Uses 0 textures, `bgcolor` used * `get_sky()`: returns bgcolor, type and a table with the textures +* `set_clouds(parameters)`: set cloud parameters + * `parameters` is a table with the following optional fields: + * `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`) + * `color`: basic cloud color, with alpha channel (default `#fff0f0e5`) + * `ambient`: cloud color lower bound, use for a "glow at night" effect (default `#000000`) + * `height`: cloud height, i.e. y of cloud base (default per conf, usually `120`) + * `thickness`: cloud thickness in nodes (default `16`) + * `speed`: 2D cloud speed + direction in nodes per second (default `{x=0, y=-2}`) +* `get_clouds()`: returns a table with the current cloud parameters as in `set_clouds` * `override_day_night_ratio(ratio or nil)` * `0`...`1`: Overrides day-night ratio, controlling sunlight to a specific amount * `nil`: Disables override, defaulting to sunlight based on day-night cycle diff --git a/src/client.h b/src/client.h index f5b03f19d..7cbfadd50 100644 --- a/src/client.h +++ b/src/client.h @@ -77,6 +77,7 @@ enum ClientEventType CE_HUDCHANGE, CE_SET_SKY, CE_OVERRIDE_DAY_NIGHT_RATIO, + CE_CLOUD_PARAMS, }; struct ClientEvent @@ -178,6 +179,15 @@ struct ClientEvent bool do_override; float ratio_f; } override_day_night_ratio; + struct { + f32 density; + u32 color_bright; + u32 color_ambient; + f32 height; + f32 thickness; + f32 speed_x; + f32 speed_y; + } cloud_params; }; }; @@ -331,6 +341,7 @@ public: void handleCommand_HudSetFlags(NetworkPacket* pkt); void handleCommand_HudSetParam(NetworkPacket* pkt); void handleCommand_HudSetSky(NetworkPacket* pkt); + void handleCommand_CloudParams(NetworkPacket* pkt); void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); void handleCommand_EyeOffset(NetworkPacket* pkt); diff --git a/src/cloudparams.h b/src/cloudparams.h new file mode 100644 index 000000000..dafec4b27 --- /dev/null +++ b/src/cloudparams.h @@ -0,0 +1,33 @@ +/* +Minetest +Copyright (C) 2017 bendeutsch, Ben Deutsch + +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 CLOUDPARAMS_HEADER +#define CLOUDPARAMS_HEADER + +struct CloudParams +{ + float density; + video::SColor color_bright; + video::SColor color_ambient; + float thickness; + float height; + v2f speed; +}; + +#endif diff --git a/src/clouds.cpp b/src/clouds.cpp index 82b63b6b3..627fac47a 100644 --- a/src/clouds.cpp +++ b/src/clouds.cpp @@ -32,6 +32,7 @@ irr::scene::ISceneManager *g_menucloudsmgr = NULL; static void cloud_3d_setting_changed(const std::string &settingname, void *data) { + // TODO: only re-read cloud settings, not height or radius ((Clouds *)data)->readSettings(); } @@ -44,9 +45,10 @@ Clouds::Clouds( ): scene::ISceneNode(parent, mgr, id), m_seed(seed), - m_camera_pos(0,0), - m_time(0), - m_camera_offset(0,0,0) + m_camera_pos(0.0f, 0.0f), + m_origin(0.0f, 0.0f), + m_camera_offset(0.0f, 0.0f, 0.0f), + m_color(1.0f, 1.0f, 1.0f, 1.0f) { m_material.setFlag(video::EMF_LIGHTING, false); //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); @@ -57,14 +59,18 @@ Clouds::Clouds( //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + m_params.density = 0.4f; + m_params.thickness = 16.0f; + m_params.color_bright = video::SColor(229, 255, 240, 240); + m_params.color_ambient = video::SColor(255, 0, 0, 0); + m_params.speed = v2f(0.0f, -2.0f); + m_passed_cloud_y = cloudheight; readSettings(); g_settings->registerChangedCallback("enable_3d_clouds", &cloud_3d_setting_changed, this); - m_box = aabb3f(-BS*1000000,m_cloud_y-BS,-BS*1000000, - BS*1000000,m_cloud_y+BS,BS*1000000); - + updateBox(); } Clouds::~Clouds() @@ -88,6 +94,10 @@ void Clouds::OnRegisterSceneNode() void Clouds::render() { + + if (m_params.density <= 0.0f) + return; // no need to do anything + video::IVideoDriver* driver = SceneManager->getVideoDriver(); if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) @@ -107,15 +117,12 @@ void Clouds::render() Clouds move from Z+ towards Z- */ - const float cloud_size = BS * 64; - const v2f cloud_speed(0, -BS * 2); + static const float cloud_size = BS * 64.0f; const float cloud_full_radius = cloud_size * m_cloud_radius_i; - // Position of cloud noise origin in world coordinates - v2f world_cloud_origin_pos_f = m_time * cloud_speed; // Position of cloud noise origin from the camera - v2f cloud_origin_from_camera_f = world_cloud_origin_pos_f - m_camera_pos; + v2f cloud_origin_from_camera_f = m_origin - m_camera_pos; // The center point of drawing in the noise v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f; // The integer center point of drawing in the noise @@ -127,7 +134,7 @@ void Clouds::render() v2f world_center_of_drawing_in_noise_f = v2f( center_of_drawing_in_noise_i.X * cloud_size, center_of_drawing_in_noise_i.Y * cloud_size - ) + world_cloud_origin_pos_f; + ) + m_origin; /*video::SColor c_top(128,b*240,b*240,b*255); video::SColor c_side_1(128,b*230,b*230,b*255); @@ -146,10 +153,6 @@ void Clouds::render() c_bottom_f.r *= 0.80; c_bottom_f.g *= 0.80; c_bottom_f.b *= 0.80; - c_top_f.a = 0.9; - c_side_1_f.a = 0.9; - c_side_2_f.a = 0.9; - c_bottom_f.a = 0.9; video::SColor c_top = c_top_f.toSColor(); video::SColor c_side_1 = c_side_1_f.toSColor(); video::SColor c_side_2 = c_side_2_f.toSColor(); @@ -187,11 +190,14 @@ void Clouds::render() zi + center_of_drawing_in_noise_i.Y ); - double noise = noise2d_perlin( + float noise = noise2d_perlin( (float)p_in_noise_i.X * cloud_size_noise, (float)p_in_noise_i.Y * cloud_size_noise, m_seed, 3, 0.5); - grid[i] = (noise >= 0.4); + // normalize to 0..1 (given 3 octaves) + static const float noise_bound = 1.0f + 0.5f + 0.25f; + float density = noise / noise_bound * 0.5f + 0.5f; + grid[i] = (density < m_params.density); } } @@ -236,8 +242,9 @@ void Clouds::render() v[3].Color.setBlue(255); }*/ - f32 rx = cloud_size/2; - f32 ry = 8 * BS; + f32 rx = cloud_size / 2.0f; + // if clouds are flat, the top layer should be at the given height + f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; f32 rz = cloud_size / 2; for(int i=0; igetU16("cloud_radius"); m_enable_3d = g_settings->getBool("enable_3d_clouds"); } - diff --git a/src/clouds.h b/src/clouds.h index 9c6b41786..a0bda28df 100644 --- a/src/clouds.h +++ b/src/clouds.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include #include "constants.h" +#include "cloudparams.h" // Menu clouds class Clouds; @@ -79,27 +80,68 @@ public: void updateCameraOffset(v3s16 camera_offset) { m_camera_offset = camera_offset; - m_box = aabb3f(-BS * 1000000, m_cloud_y - BS - BS * camera_offset.Y, -BS * 1000000, - BS * 1000000, m_cloud_y + BS - BS * camera_offset.Y, BS * 1000000); + updateBox(); } void readSettings(); + void setDensity(float density) + { + m_params.density = density; + // currently does not need bounding + } + + void setColorBright(const video::SColor &color_bright) + { + m_params.color_bright = color_bright; + } + + void setColorAmbient(const video::SColor &color_ambient) + { + m_params.color_ambient = color_ambient; + } + + void setHeight(float height) + { + m_params.height = height; // add bounding when necessary + updateBox(); + } + + void setSpeed(v2f speed) + { + m_params.speed = speed; + } + + void setThickness(float thickness) + { + m_params.thickness = thickness; + updateBox(); + } + private: + void updateBox() + { + float height_bs = m_params.height * BS; + float thickness_bs = m_params.thickness * BS; + m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f, + BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f); + } + video::SMaterial m_material; aabb3f m_box; s16 m_passed_cloud_y; - float m_cloud_y; u16 m_cloud_radius_i; bool m_enable_3d; - video::SColorf m_color; u32 m_seed; v2f m_camera_pos; - float m_time; + v2f m_origin; + v2f m_speed; v3s16 m_camera_offset; + video::SColorf m_color; + CloudParams m_params; + }; #endif - diff --git a/src/game.cpp b/src/game.cpp index a1cc1ab15..ba6530d80 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3295,6 +3295,19 @@ void Game::processClientEvents(CameraOrientation *cam) event.override_day_night_ratio.ratio_f * 1000); break; + case CE_CLOUD_PARAMS: + if (clouds) { + clouds->setDensity(event.cloud_params.density); + clouds->setColorBright(video::SColor(event.cloud_params.color_bright)); + clouds->setColorAmbient(video::SColor(event.cloud_params.color_ambient)); + clouds->setHeight(event.cloud_params.height); + clouds->setThickness(event.cloud_params.thickness); + clouds->setSpeed(v2f( + event.cloud_params.speed_x, + event.cloud_params.speed_y)); + } + break; + default: // unknown or unhandled type break; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 563baf77b..1be6e5522 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -108,7 +108,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_LocalPlayerAnimations }, // 0x51 { "TOCLIENT_EYE_OFFSET", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_EyeOffset }, // 0x52 { "TOCLIENT_DELETE_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeleteParticleSpawner }, // 0x53 - null_command_handler, + { "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54 null_command_handler, null_command_handler, null_command_handler, diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 772ffe905..defc83f31 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1168,6 +1168,34 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) m_client_event_queue.push(event); } +void Client::handleCommand_CloudParams(NetworkPacket* pkt) +{ + f32 density; + video::SColor color_bright; + video::SColor color_ambient; + f32 height; + f32 thickness; + v2f speed; + + *pkt >> density >> color_bright >> color_ambient + >> height >> thickness >> speed; + + ClientEvent event; + event.type = CE_CLOUD_PARAMS; + event.cloud_params.density = density; + // use the underlying u32 representation, because we can't + // use struct members with constructors here, and this way + // we avoid using new() and delete() for no good reason + event.cloud_params.color_bright = color_bright.color; + event.cloud_params.color_ambient = color_ambient.color; + event.cloud_params.height = height; + event.cloud_params.thickness = thickness; + // same here: deconstruct to skip constructor + event.cloud_params.speed_x = speed.X; + event.cloud_params.speed_y = speed.Y; + m_client_event_queue.push(event); +} + void Client::handleCommand_OverrideDayNightRatio(NetworkPacket* pkt) { bool do_override; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index e7a3469b7..a1a4f5bfa 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -610,6 +610,16 @@ enum ToClientCommand u32 id */ + TOCLIENT_CLOUD_PARAMS = 0x54, + /* + f1000 density + u8[4] color_diffuse (ARGB) + u8[4] color_ambient (ARGB) + f1000 height + f1000 thickness + v2f1000 speed + */ + TOCLIENT_SRP_BYTES_S_B = 0x60, /* Belonging to AUTH_MECHANISM_LEGACY_PASSWORD and AUTH_MECHANISM_SRP. diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 31b571ff0..450730ca2 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -197,7 +197,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", 0, true }, // 0x51 { "TOCLIENT_EYE_OFFSET", 0, true }, // 0x52 { "TOCLIENT_DELETE_PARTICLESPAWNER", 0, true }, // 0x53 - null_command_factory, + { "TOCLIENT_CLOUD_PARAMS", 0, true }, // 0x54 null_command_factory, null_command_factory, null_command_factory, diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 2dbfe9d9d..2b4db62f5 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -65,6 +65,14 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): movement_liquid_fluidity_smooth = g_settings->getFloat("movement_liquid_fluidity_smooth") * BS; movement_liquid_sink = g_settings->getFloat("movement_liquid_sink") * BS; movement_gravity = g_settings->getFloat("movement_gravity") * BS; + + // copy defaults + m_cloud_params.density = 0.4f; + m_cloud_params.color_bright = video::SColor(255, 255, 240, 240); + m_cloud_params.color_ambient = video::SColor(255, 0, 0, 0); + m_cloud_params.height = 120.0f; + m_cloud_params.thickness = 16.0f; + m_cloud_params.speed = v2f(0.0f, -2.0f); } void RemotePlayer::serializeExtraAttributes(std::string &output) diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 4b96835fc..b9d9c74f5 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define REMOTEPLAYER_HEADER #include "player.h" +#include "cloudparams.h" class PlayerSAO; @@ -99,6 +100,16 @@ public: *params = m_sky_params; } + void setCloudParams(const CloudParams &cloud_params) + { + m_cloud_params = cloud_params; + } + + const CloudParams &getCloudParams() const + { + return m_cloud_params; + } + bool checkModified() const { return m_dirty || inventory.checkModified(); } void setModified(const bool x) @@ -154,6 +165,7 @@ private: std::string m_sky_type; video::SColor m_sky_bgcolor; std::vector m_sky_params; + CloudParams m_cloud_params; }; #endif diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index a5b6e3941..6cd852299 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1729,6 +1729,85 @@ int ObjectRef::l_get_sky(lua_State *L) return 3; } +// set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) +int ObjectRef::l_set_clouds(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + if (!lua_istable(L, 2)) + return 0; + + CloudParams cloud_params = player->getCloudParams(); + + cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); + + lua_getfield(L, 2, "color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_bright); + lua_pop(L, 1); + lua_getfield(L, 2, "ambient"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_ambient); + lua_pop(L, 1); + + cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height ); + cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); + + lua_getfield(L, 2, "speed"); + if (lua_istable(L, -1)) { + v2f new_speed; + new_speed.X = getfloatfield_default(L, -1, "x", 0); + new_speed.Y = getfloatfield_default(L, -1, "y", 0); + cloud_params.speed = new_speed; + } + lua_pop(L, 1); + + if (!getServer(L)->setClouds(player, cloud_params.density, + cloud_params.color_bright, cloud_params.color_ambient, + cloud_params.height, cloud_params.thickness, + cloud_params.speed)) + return 0; + + player->setCloudParams(cloud_params); + + lua_pushboolean(L, true); + return 1; +} + +int ObjectRef::l_get_clouds(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + const CloudParams &cloud_params = player->getCloudParams(); + + lua_newtable(L); + lua_pushnumber(L, cloud_params.density); + lua_setfield(L, -2, "density"); + push_ARGB8(L, cloud_params.color_bright); + lua_setfield(L, -2, "color"); + push_ARGB8(L, cloud_params.color_ambient); + lua_setfield(L, -2, "ambient"); + lua_pushnumber(L, cloud_params.height); + lua_setfield(L, -2, "height"); + lua_pushnumber(L, cloud_params.thickness); + lua_setfield(L, -2, "thickness"); + lua_newtable(L); + lua_pushnumber(L, cloud_params.speed.X); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, cloud_params.speed.Y); + lua_setfield(L, -2, "y"); + lua_setfield(L, -2, "speed"); + + return 1; +} + + // override_day_night_ratio(self, brightness=0...1) int ObjectRef::l_override_day_night_ratio(lua_State *L) { @@ -1911,6 +1990,8 @@ const luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, hud_get_hotbar_selected_image), luamethod(ObjectRef, set_sky), luamethod(ObjectRef, get_sky), + luamethod(ObjectRef, set_clouds), + luamethod(ObjectRef, get_clouds), luamethod(ObjectRef, override_day_night_ratio), luamethod(ObjectRef, get_day_night_ratio), luamethod(ObjectRef, set_local_animation), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 98f5c2b11..0912a1c49 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -289,6 +289,12 @@ private: // get_sky(self, type, list) static int l_get_sky(lua_State *L); + // set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) + static int l_set_clouds(lua_State *L); + + // get_clouds(self) + static int l_get_clouds(lua_State *L); + // override_day_night_ratio(self, type) static int l_override_day_night_ratio(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index 2edf83947..9ef69cb37 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1882,6 +1882,20 @@ void Server::SendSetSky(u16 peer_id, const video::SColor &bgcolor, Send(&pkt); } +void Server::SendCloudParams(u16 peer_id, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed) +{ + NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id); + pkt << density << color_bright << color_ambient + << height << thickness << speed; + + Send(&pkt); +} + void Server::SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio) { @@ -3196,6 +3210,22 @@ bool Server::setSky(RemotePlayer *player, const video::SColor &bgcolor, return true; } +bool Server::setClouds(RemotePlayer *player, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed) +{ + if (!player) + return false; + + SendCloudParams(player->peer_id, density, + color_bright, color_ambient, height, + thickness, speed); + return true; +} + bool Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, float ratio) { diff --git a/src/server.h b/src/server.h index 948fb8fc2..3a082b9a4 100644 --- a/src/server.h +++ b/src/server.h @@ -332,6 +332,12 @@ public: bool setSky(RemotePlayer *player, const video::SColor &bgcolor, const std::string &type, const std::vector ¶ms); + bool setClouds(RemotePlayer *player, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed); bool overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); @@ -401,6 +407,12 @@ private: void SendHUDSetParam(u16 peer_id, u16 param, const std::string &value); void SendSetSky(u16 peer_id, const video::SColor &bgcolor, const std::string &type, const std::vector ¶ms); + void SendCloudParams(u16 peer_id, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed); void SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio); /* diff --git a/src/sky.cpp b/src/sky.cpp index 211a2dcdc..7f999feb0 100644 --- a/src/sky.cpp +++ b/src/sky.cpp @@ -534,8 +534,10 @@ void Sky::update(float time_of_day, float time_brightness, video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250); video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255); - video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 240, 240, 255); - video::SColorf cloudcolor_bright_dawn_f = video::SColor(255, 255, 223, 191); + // pure white: becomes "diffuse light component" for clouds + video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 255, 255, 255); + // dawn-factoring version of pure white (note: R is above 1.0) + video::SColorf cloudcolor_bright_dawn_f(255.0f/240.0f, 223.0f/240.0f, 191.0f/255.0f); float cloud_color_change_fraction = 0.95; if (sunlight_seen) { -- cgit v1.2.3 From bd921a7916f0fafc493b1c4d0eeb5e2bb1d6a7c2 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sun, 10 Jul 2016 00:08:26 -0500 Subject: Sound API: Add fading sounds --- doc/lua_api.txt | 7 +++ src/client.cpp | 1 + src/client.h | 1 + src/network/clientopcodes.cpp | 2 +- src/network/clientpackethandler.cpp | 35 +++++++++++++- src/network/networkprotocol.h | 11 ++++- src/script/common/c_content.cpp | 2 + src/script/common/c_converter.h | 2 + src/script/lua_api/l_server.cpp | 11 +++++ src/script/lua_api/l_server.h | 3 ++ src/server.cpp | 64 +++++++++++++++++++++++-- src/server.h | 6 ++- src/sound.h | 28 +++++++---- src/sound_openal.cpp | 94 ++++++++++++++++++++++++++++++++++++- 14 files changed, 248 insertions(+), 19 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 479e38a2e..77ffb88e2 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -456,11 +456,13 @@ Examples of sound parameter tables: -- Play locationless on all clients { gain = 1.0, -- default + fade = 0.0, -- default, change to a value > 0 to fade the sound in } -- Play locationless to one player { to_player = name, gain = 1.0, -- default + fade = 0.0, -- default, change to a value > 0 to fade the sound in } -- Play locationless to one player, looped { @@ -2587,6 +2589,11 @@ These functions return the leftover itemstack. * `spec` is a `SimpleSoundSpec` * `parameters` is a sound parameter table * `minetest.sound_stop(handle)` +* `minetest.sound_fade(handle, step, gain)` + * `handle` is a handle returned by minetest.sound_play + * `step` determines how fast a sound will fade. + Negative step will lower the sound volume, positive step will increase the sound volume + * `gain` the target gain for the fade. ### Timing * `minetest.after(time, func, ...)` diff --git a/src/client.cpp b/src/client.cpp index 3c5a70f21..3269c573a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -407,6 +407,7 @@ void Client::step(float dtime) // Step environment m_env.step(dtime); + m_sound->step(dtime); /* Get events diff --git a/src/client.h b/src/client.h index 7cbfadd50..e8db7de44 100644 --- a/src/client.h +++ b/src/client.h @@ -328,6 +328,7 @@ public: void handleCommand_ItemDef(NetworkPacket* pkt); void handleCommand_PlaySound(NetworkPacket* pkt); void handleCommand_StopSound(NetworkPacket* pkt); + void handleCommand_FadeSound(NetworkPacket *pkt); void handleCommand_Privileges(NetworkPacket* pkt); void handleCommand_InventoryFormSpec(NetworkPacket* pkt); void handleCommand_DetachedInventory(NetworkPacket* pkt); diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 1be6e5522..bdcb1dfce 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -109,7 +109,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_EYE_OFFSET", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_EyeOffset }, // 0x52 { "TOCLIENT_DELETE_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeleteParticleSpawner }, // 0x53 { "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54 - null_command_handler, + { "TOCLIENT_FADE_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FadeSound }, // 0x55 null_command_handler, null_command_handler, null_command_handler, diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index defc83f31..a895acc84 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -755,21 +755,39 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) void Client::handleCommand_PlaySound(NetworkPacket* pkt) { + /* + [0] u32 server_id + [4] u16 name length + [6] char name[len] + [ 6 + len] f32 gain + [10 + len] u8 type + [11 + len] (f32 * 3) pos + [23 + len] u16 object_id + [25 + len] bool loop + [26 + len] f32 fade + */ + s32 server_id; std::string name; + float gain; u8 type; // 0=local, 1=positional, 2=object v3f pos; u16 object_id; bool loop; + float fade = 0; *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop; + try { + *pkt >> fade; + } catch (SerializationError &e) {}; + // Start playing int client_id = -1; switch(type) { case 0: // local - client_id = m_sound->playSound(name, loop, gain); + client_id = m_sound->playSound(name, loop, gain, fade); break; case 1: // positional client_id = m_sound->playSoundAt(name, loop, gain, pos); @@ -808,6 +826,21 @@ void Client::handleCommand_StopSound(NetworkPacket* pkt) } } +void Client::handleCommand_FadeSound(NetworkPacket *pkt) +{ + s32 sound_id; + float step; + float gain; + + *pkt >> sound_id >> step >> gain; + + UNORDERED_MAP::iterator i = + m_sounds_server_to_client.find(sound_id); + + if (i != m_sounds_server_to_client.end()) + m_sound->fadeSound(i->second, step, gain); +} + void Client::handleCommand_Privileges(NetworkPacket* pkt) { m_privileges.clear(); diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index a1a4f5bfa..70cad85d8 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -153,9 +153,11 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL VERSION 31: Add tile overlay Stop sending TOSERVER_CLIENT_READY + PROTOCOL VERSION 32: + Add fading sounds */ -#define LATEST_PROTOCOL_VERSION 31 +#define LATEST_PROTOCOL_VERSION 32 // Server's supported network protocol range #define SERVER_PROTOCOL_VERSION_MIN 24 @@ -620,6 +622,13 @@ enum ToClientCommand v2f1000 speed */ + TOCLIENT_FADE_SOUND = 0x55, + /* + s32 sound_id + float step + float gain + */ + TOCLIENT_SRP_BYTES_S_B = 0x60, /* Belonging to AUTH_MECHANISM_LEGACY_PASSWORD and AUTH_MECHANISM_SRP. diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 5fe5af58d..8696ad7cb 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -680,6 +680,7 @@ void read_server_sound_params(lua_State *L, int index, if(lua_istable(L, index)){ getfloatfield(L, index, "gain", params.gain); getstringfield(L, index, "to_player", params.to_player); + getfloatfield(L, index, "fade", params.fade); lua_getfield(L, index, "pos"); if(!lua_isnil(L, -1)){ v3f p = read_v3f(L, -1)*BS; @@ -712,6 +713,7 @@ void read_soundspec(lua_State *L, int index, SimpleSoundSpec &spec) } else if(lua_istable(L, index)){ getstringfield(L, index, "name", spec.name); getfloatfield(L, index, "gain", spec.gain); + getfloatfield(L, index, "fade", spec.fade); } else if(lua_isstring(L, index)){ spec.name = lua_tostring(L, index); } diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index a5fbee765..b0f61a8ca 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -77,6 +77,8 @@ void setfloatfield(lua_State *L, int table, const char *fieldname, float value); void setboolfield(lua_State *L, int table, const char *fieldname, bool value); +void setstringfield(lua_State *L, int table, + const char *fieldname, const char *value); v3f checkFloatPos (lua_State *L, int index); v2f check_v2f (lua_State *L, int index); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 7b723d14c..ea993d7b7 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -455,6 +455,16 @@ int ModApiServer::l_sound_stop(lua_State *L) return 0; } +int ModApiServer::l_sound_fade(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + s32 handle = luaL_checkinteger(L, 1); + float step = luaL_checknumber(L, 2); + float gain = luaL_checknumber(L, 3); + getServer(L)->fadeSound(handle, step, gain); + return 0; +} + // is_singleplayer() int ModApiServer::l_is_singleplayer(lua_State *L) { @@ -518,6 +528,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(show_formspec); API_FCT(sound_play); API_FCT(sound_stop); + API_FCT(sound_fade); API_FCT(get_player_information); API_FCT(get_player_privs); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 3a4a917c0..251a0ce89 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -68,6 +68,9 @@ private: // sound_stop(handle) static int l_sound_stop(lua_State *L); + // sound_fade(handle, step, gain) + static int l_sound_fade(lua_State *L); + // get_player_privs(name, text) static int l_get_player_privs(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index 9ef69cb37..190a1baf2 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2100,15 +2100,23 @@ s32 Server::playSound(const SimpleSoundSpec &spec, m_playing_sounds[id] = ServerPlayingSound(); ServerPlayingSound &psound = m_playing_sounds[id]; psound.params = params; + psound.spec = spec; + float gain = params.gain * spec.gain; NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); - pkt << id << spec.name << (float) (spec.gain * params.gain) - << (u8) params.type << pos << params.object << params.loop; + pkt << id << spec.name << gain + << (u8) params.type << pos << params.object + << params.loop << params.fade; - for(std::vector::iterator i = dst_clients.begin(); + // Backwards compability + bool play_sound = gain > 0; + + for (std::vector::iterator i = dst_clients.begin(); i != dst_clients.end(); ++i) { - psound.clients.insert(*i); - m_clients.send(*i, 0, &pkt, true); + if (play_sound || m_clients.getProtocolVersion(*i) >= 32) { + psound.clients.insert(*i); + m_clients.send(*i, 0, &pkt, true); + } } return id; } @@ -2132,6 +2140,52 @@ void Server::stopSound(s32 handle) m_playing_sounds.erase(i); } +void Server::fadeSound(s32 handle, float step, float gain) +{ + // Get sound reference + UNORDERED_MAP::iterator i = + m_playing_sounds.find(handle); + if (i == m_playing_sounds.end()) + return; + + ServerPlayingSound &psound = i->second; + psound.params.gain = gain; + + NetworkPacket pkt(TOCLIENT_FADE_SOUND, 4); + pkt << handle << step << gain; + + // Backwards compability + bool play_sound = gain > 0; + ServerPlayingSound compat_psound = psound; + compat_psound.clients.clear(); + + NetworkPacket compat_pkt(TOCLIENT_STOP_SOUND, 4); + compat_pkt << handle; + + for (UNORDERED_SET::iterator it = psound.clients.begin(); + it != psound.clients.end();) { + if (m_clients.getProtocolVersion(*it) >= 32) { + // Send as reliable + m_clients.send(*it, 0, &pkt, true); + ++it; + } else { + compat_psound.clients.insert(*it); + // Stop old sound + m_clients.send(*it, 0, &compat_pkt, true); + psound.clients.erase(it++); + } + } + + // Remove sound reference + if (!play_sound || psound.clients.size() == 0) + m_playing_sounds.erase(i); + + if (play_sound && compat_psound.clients.size() > 0) { + // Play new sound volume on older clients + playSound(compat_psound.spec, compat_psound.params); + } +} + void Server::sendRemoveNode(v3s16 p, u16 ignore_id, std::vector *far_players, float far_d_nodes) { diff --git a/src/server.h b/src/server.h index 3a082b9a4..5e6211637 100644 --- a/src/server.h +++ b/src/server.h @@ -115,6 +115,7 @@ struct ServerSoundParams u16 object; float max_hear_distance; bool loop; + float fade; ServerSoundParams(): gain(1.0), @@ -123,7 +124,8 @@ struct ServerSoundParams pos(0,0,0), object(0), max_hear_distance(32*BS), - loop(false) + loop(false), + fade(0) {} v3f getPos(ServerEnvironment *env, bool *pos_exists) const; @@ -132,6 +134,7 @@ struct ServerSoundParams struct ServerPlayingSound { ServerSoundParams params; + SimpleSoundSpec spec; UNORDERED_SET clients; // peer ids }; @@ -231,6 +234,7 @@ public: // Envlock s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms); void stopSound(s32 handle); + void fadeSound(s32 handle, float step, float gain); // Envlock std::set getPlayerEffectivePrivs(const std::string &name); diff --git a/src/sound.h b/src/sound.h index 98f7692d5..7bdb6a26b 100644 --- a/src/sound.h +++ b/src/sound.h @@ -34,8 +34,8 @@ public: struct SimpleSoundSpec { - SimpleSoundSpec(const std::string &name = "", float gain = 1.0) - : name(name), gain(gain) + SimpleSoundSpec(const std::string &name = "", float gain = 1.0, float fade = 0.0) + : name(name), gain(gain), fade(fade) { } @@ -43,13 +43,13 @@ struct SimpleSoundSpec std::string name; float gain; + float fade; }; class ISoundManager { public: virtual ~ISoundManager() {} - // Multiple sounds can be loaded per name; when played, the sound // should be chosen randomly from alternatives // Return value determines success/failure @@ -63,16 +63,21 @@ public: // playSound functions return -1 on failure, otherwise a handle to the // sound. If name=="", call should be ignored without error. - virtual int playSound(const std::string &name, bool loop, float volume) = 0; - virtual int playSoundAt( - const std::string &name, bool loop, float volume, v3f pos) = 0; + virtual int playSound(const std::string &name, bool loop, float volume, + float fade = 0) = 0; + virtual int playSoundAt(const std::string &name, bool loop, float volume, + v3f pos) = 0; virtual void stopSound(int sound) = 0; virtual bool soundExists(int sound) = 0; virtual void updateSoundPosition(int sound, v3f pos) = 0; + virtual bool updateSoundGain(int id, float gain) = 0; + virtual float getSoundGain(int id) = 0; + virtual void step(float dtime) = 0; + virtual void fadeSound(int sound, float step, float gain) = 0; int playSound(const SimpleSoundSpec &spec, bool loop) { - return playSound(spec.name, loop, spec.gain); + return playSound(spec.name, loop, spec.gain, spec.fade); } int playSoundAt(const SimpleSoundSpec &spec, bool loop, v3f pos) { @@ -93,7 +98,10 @@ public: } void updateListener(v3f pos, v3f vel, v3f at, v3f up) {} void setListenerGain(float gain) {} - int playSound(const std::string &name, bool loop, float volume) { return 0; } + int playSound(const std::string &name, bool loop, float volume, float fade) + { + return 0; + } int playSoundAt(const std::string &name, bool loop, float volume, v3f pos) { return 0; @@ -101,6 +109,10 @@ public: void stopSound(int sound) {} bool soundExists(int sound) { return false; } void updateSoundPosition(int sound, v3f pos) {} + bool updateSoundGain(int id, float gain) { return false; } + float getSoundGain(int id) { return 0; } + void step(float dtime) { } + void fadeSound(int sound, float step, float gain) { } }; // Global DummySoundManager singleton diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index b9af9e3a9..a425af827 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -274,6 +274,19 @@ private: UNORDERED_MAP > m_buffers; UNORDERED_MAP m_sounds_playing; v3f m_listener_pos; + struct FadeState { + FadeState() {} + FadeState(float step, float current_gain, float target_gain): + step(step), + current_gain(current_gain), + target_gain(target_gain) {} + float step; + float current_gain; + float target_gain; + }; + + UNORDERED_MAP m_sounds_fading; + float m_fade_delay; public: bool m_is_initialized; OpenALSoundManager(OnDemandSoundFetcher *fetcher): @@ -281,6 +294,7 @@ public: m_device(NULL), m_context(NULL), m_next_id(1), + m_fade_delay(0), m_is_initialized(false) { ALCenum error = ALC_NO_ERROR; @@ -349,6 +363,11 @@ public: infostream<<"Audio: Deinitialized."< >::iterator i = @@ -515,6 +534,7 @@ public: addBuffer(name, buf); return false; } + bool loadSoundData(const std::string &name, const std::string &filedata) { @@ -541,7 +561,7 @@ public: alListenerf(AL_GAIN, gain); } - int playSound(const std::string &name, bool loop, float volume) + int playSound(const std::string &name, bool loop, float volume, float fade) { maintain(); if(name == "") @@ -552,8 +572,16 @@ public: < 0) { + handle = playSoundRaw(buf, loop, 0); + fadeSound(handle, fade, volume); + } else { + handle = playSoundRaw(buf, loop, volume); + } + return handle; } + int playSoundAt(const std::string &name, bool loop, float volume, v3f pos) { maintain(); @@ -567,16 +595,55 @@ public: } return playSoundRawAt(buf, loop, volume, pos); } + void stopSound(int sound) { maintain(); deleteSound(sound); } + + void fadeSound(int soundid, float step, float gain) + { + m_sounds_fading[soundid] = FadeState(step, getSoundGain(soundid), gain); + } + + void doFades(float dtime) + { + m_fade_delay += dtime; + + if (m_fade_delay < 0.1f) + return; + + float chkGain = 0; + for (UNORDERED_MAP::iterator i = m_sounds_fading.begin(); + i != m_sounds_fading.end();) { + if (i->second.step < 0.f) + chkGain = -(i->second.current_gain); + else + chkGain = i->second.current_gain; + + if (chkGain < i->second.target_gain) { + i->second.current_gain += (i->second.step * m_fade_delay); + i->second.current_gain = rangelim(i->second.current_gain, 0, 1); + + updateSoundGain(i->first, i->second.current_gain); + ++i; + } else { + if (i->second.target_gain <= 0.f) + stopSound(i->first); + + m_sounds_fading.erase(i++); + } + } + m_fade_delay = 0; + } + bool soundExists(int sound) { maintain(); return (m_sounds_playing.count(sound) != 0); } + void updateSoundPosition(int id, v3f pos) { UNORDERED_MAP::iterator i = m_sounds_playing.find(id); @@ -589,6 +656,29 @@ public: alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0); alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0); } + + bool updateSoundGain(int id, float gain) + { + UNORDERED_MAP::iterator i = m_sounds_playing.find(id); + if (i == m_sounds_playing.end()) + return false; + + PlayingSound *sound = i->second; + alSourcef(sound->source_id, AL_GAIN, gain); + return true; + } + + float getSoundGain(int id) + { + UNORDERED_MAP::iterator i = m_sounds_playing.find(id); + if (i == m_sounds_playing.end()) + return 0; + + PlayingSound *sound = i->second; + ALfloat gain; + alGetSourcef(sound->source_id, AL_GAIN, &gain); + return gain; + } }; ISoundManager *createOpenALSoundManager(OnDemandSoundFetcher *fetcher) -- cgit v1.2.3 From ad9fcf859ec2347325830e09504ae96968b51ea8 Mon Sep 17 00:00:00 2001 From: paramat Date: Fri, 28 Apr 2017 03:06:49 +0100 Subject: Set sky API: Add bool for clouds in front of custom skybox Default true. Add 'm_clouds_enabled' bool to sky.h, set from new bool in 'set sky' API. Make 'getCloudsVisible()' depend on 'm_clouds_enabled' instead of 'm_visible' (whether normal sky is visible). --- doc/lua_api.txt | 8 +++++--- src/client.h | 1 + src/game.cpp | 3 +++ src/network/clientpackethandler.cpp | 6 ++++++ src/network/networkprotocol.h | 1 + src/remoteplayer.h | 8 ++++++-- src/script/lua_api/l_object.cpp | 15 ++++++++++----- src/script/lua_api/l_object.h | 4 ++-- src/server.cpp | 12 ++++++++---- src/server.h | 6 ++++-- src/sky.cpp | 2 ++ src/sky.h | 7 +++++-- 12 files changed, 53 insertions(+), 20 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 77ffb88e2..599e02fcb 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3077,13 +3077,15 @@ This is basically a reference to a C++ `ServerActiveObject` * `hud_set_hotbar_selected_image(texturename)` * sets image for selected item of hotbar * `hud_get_hotbar_selected_image`: returns texturename -* `set_sky(bgcolor, type, {texture names})` +* `set_sky(bgcolor, type, {texture names}, clouds)` * `bgcolor`: ColorSpec, defaults to white - * Available types: + * `type`: Available types: * `"regular"`: Uses 0 textures, `bgcolor` ignored * `"skybox"`: Uses 6 textures, `bgcolor` used * `"plain"`: Uses 0 textures, `bgcolor` used -* `get_sky()`: returns bgcolor, type and a table with the textures + * `clouds`: Boolean for whether clouds appear in front of `"skybox"` or + `"plain"` custom skyboxes (default: `true`) +* `get_sky()`: returns bgcolor, type, table of textures, clouds * `set_clouds(parameters)`: set cloud parameters * `parameters` is a table with the following optional fields: * `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`) diff --git a/src/client.h b/src/client.h index e8db7de44..0dd519308 100644 --- a/src/client.h +++ b/src/client.h @@ -174,6 +174,7 @@ struct ClientEvent video::SColor *bgcolor; std::string *type; std::vector *params; + bool clouds; } set_sky; struct{ bool do_override; diff --git a/src/game.cpp b/src/game.cpp index ba6530d80..7dd9c942d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3255,6 +3255,8 @@ void Game::processClientEvents(CameraOrientation *cam) case CE_SET_SKY: sky->setVisible(false); + // Whether clouds are visible in front of a custom skybox + sky->setCloudsEnabled(event.set_sky.clouds); if (skybox) { skybox->remove(); @@ -3264,6 +3266,7 @@ void Game::processClientEvents(CameraOrientation *cam) // Handle according to type if (*event.set_sky.type == "regular") { sky->setVisible(true); + sky->setCloudsEnabled(true); } else if (*event.set_sky.type == "skybox" && event.set_sky.params->size() == 6) { sky->setFallbackBgColor(*event.set_sky.bgcolor); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 4316a77d4..c3626158e 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1192,11 +1192,17 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) for (size_t i = 0; i < count; i++) params->push_back(deSerializeString(is)); + bool clouds = true; + try { + clouds = readU8(is); + } catch (...) {} + ClientEvent event; event.type = CE_SET_SKY; event.set_sky.bgcolor = bgcolor; event.set_sky.type = type; event.set_sky.params = params; + event.set_sky.clouds = clouds; m_client_event_queue.push(event); } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 70cad85d8..7126c237b 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -584,6 +584,7 @@ enum ToClientCommand foreach count: u8 len u8[len] param + u8 clouds (boolean) */ TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO = 0x50, diff --git a/src/remoteplayer.h b/src/remoteplayer.h index b9d9c74f5..7d46205c5 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -85,19 +85,21 @@ public: } void setSky(const video::SColor &bgcolor, const std::string &type, - const std::vector ¶ms) + const std::vector ¶ms, bool &clouds) { m_sky_bgcolor = bgcolor; m_sky_type = type; m_sky_params = params; + m_sky_clouds = clouds; } void getSky(video::SColor *bgcolor, std::string *type, - std::vector *params) + std::vector *params, bool *clouds) { *bgcolor = m_sky_bgcolor; *type = m_sky_type; *params = m_sky_params; + *clouds = m_sky_clouds; } void setCloudParams(const CloudParams &cloud_params) @@ -165,6 +167,8 @@ private: std::string m_sky_type; video::SColor m_sky_bgcolor; std::vector m_sky_params; + bool m_sky_clouds; + CloudParams m_cloud_params; }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 6cd852299..6f61ab55c 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1662,7 +1662,7 @@ int ObjectRef::l_hud_get_hotbar_selected_image(lua_State *L) return 1; } -// set_sky(self, bgcolor, type, list) +// set_sky(self, bgcolor, type, list, clouds = true) int ObjectRef::l_set_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -1678,9 +1678,8 @@ int ObjectRef::l_set_sky(lua_State *L) std::vector params; if (lua_istable(L, 4)) { - int table = lua_gettop(L); lua_pushnil(L); - while (lua_next(L, table) != 0) { + while (lua_next(L, 4) != 0) { // key at index -2 and value at index -1 if (lua_isstring(L, -1)) params.push_back(lua_tostring(L, -1)); @@ -1694,7 +1693,11 @@ int ObjectRef::l_set_sky(lua_State *L) if (type == "skybox" && params.size() != 6) throw LuaError("skybox expects 6 textures"); - if (!getServer(L)->setSky(player, bgcolor, type, params)) + bool clouds = true; + if (lua_isboolean(L, 5)) + clouds = lua_toboolean(L, 5); + + if (!getServer(L)->setSky(player, bgcolor, type, params, clouds)) return 0; lua_pushboolean(L, true); @@ -1712,8 +1715,9 @@ int ObjectRef::l_get_sky(lua_State *L) video::SColor bgcolor(255, 255, 255, 255); std::string type; std::vector params; + bool clouds; - player->getSky(&bgcolor, &type, ¶ms); + player->getSky(&bgcolor, &type, ¶ms, &clouds); type = type == "" ? "regular" : type; push_ARGB8(L, bgcolor); @@ -1726,6 +1730,7 @@ int ObjectRef::l_get_sky(lua_State *L) lua_rawseti(L, -2, i); i++; } + lua_pushboolean(L, clouds); return 3; } diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 0912a1c49..9801ce02b 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -283,10 +283,10 @@ private: // hud_get_hotbar_selected_image(self) static int l_hud_get_hotbar_selected_image(lua_State *L); - // set_sky(self, type, list) + // set_sky(self, bgcolor, type, list, clouds = true) static int l_set_sky(lua_State *L); - // get_sky(self, type, list) + // get_sky(self) static int l_get_sky(lua_State *L); // set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) diff --git a/src/server.cpp b/src/server.cpp index 190a1baf2..bf01fb7eb 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1871,7 +1871,8 @@ void Server::SendHUDSetParam(u16 peer_id, u16 param, const std::string &value) } void Server::SendSetSky(u16 peer_id, const video::SColor &bgcolor, - const std::string &type, const std::vector ¶ms) + const std::string &type, const std::vector ¶ms, + bool &clouds) { NetworkPacket pkt(TOCLIENT_SET_SKY, 0, peer_id); pkt << bgcolor << type << (u16) params.size(); @@ -1879,6 +1880,8 @@ void Server::SendSetSky(u16 peer_id, const video::SColor &bgcolor, for(size_t i=0; i ¶ms) + const std::string &type, const std::vector ¶ms, + bool &clouds) { if (!player) return false; - player->setSky(bgcolor, type, params); - SendSetSky(player->peer_id, bgcolor, type, params); + player->setSky(bgcolor, type, params, clouds); + SendSetSky(player->peer_id, bgcolor, type, params, clouds); return true; } diff --git a/src/server.h b/src/server.h index 5e6211637..2e735e77c 100644 --- a/src/server.h +++ b/src/server.h @@ -335,7 +335,8 @@ public: bool setPlayerEyeOffset(RemotePlayer *player, v3f first, v3f third); bool setSky(RemotePlayer *player, const video::SColor &bgcolor, - const std::string &type, const std::vector ¶ms); + const std::string &type, const std::vector ¶ms, + bool &clouds); bool setClouds(RemotePlayer *player, float density, const video::SColor &color_bright, const video::SColor &color_ambient, @@ -410,7 +411,8 @@ private: void SendHUDSetFlags(u16 peer_id, u32 flags, u32 mask); void SendHUDSetParam(u16 peer_id, u16 param, const std::string &value); void SendSetSky(u16 peer_id, const video::SColor &bgcolor, - const std::string &type, const std::vector ¶ms); + const std::string &type, const std::vector ¶ms, + bool &clouds); void SendCloudParams(u16 peer_id, float density, const video::SColor &color_bright, const video::SColor &color_ambient, diff --git a/src/sky.cpp b/src/sky.cpp index 7f999feb0..5414f74bd 100644 --- a/src/sky.cpp +++ b/src/sky.cpp @@ -85,6 +85,8 @@ Sky::Sky(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id, } m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); + + m_clouds_enabled = true; } diff --git a/src/sky.h b/src/sky.h index 72cb2d581..c9678a80b 100644 --- a/src/sky.h +++ b/src/sky.h @@ -65,10 +65,12 @@ public: return m_visible ? m_skycolor : m_fallback_bg_color; } - bool getCloudsVisible() { return m_clouds_visible && m_visible; } + bool getCloudsVisible() { return m_clouds_visible && m_clouds_enabled; } const video::SColorf &getCloudColor() { return m_cloudcolor_f; } void setVisible(bool visible) { m_visible = visible; } + // Set only from set_sky API + void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; } void setFallbackBgColor(const video::SColor &fallback_bg_color) { m_fallback_bg_color = fallback_bg_color; @@ -123,7 +125,8 @@ private: bool m_sunlight_seen; float m_brightness; float m_cloud_brightness; - bool m_clouds_visible; + bool m_clouds_visible; // Whether clouds are disabled due to player underground + bool m_clouds_enabled; // Initialised to true, reset only by set_sky API bool m_directional_colored_fog; video::SColorf m_bgcolor_bright_f; video::SColorf m_skycolor_bright_f; -- cgit v1.2.3 From d6cf5450a897b00e5ed0d053f14108c94dfb3701 Mon Sep 17 00:00:00 2001 From: red-001 Date: Thu, 4 May 2017 21:52:58 +0100 Subject: Add option to also check the center to `find_node_near` (#5255) * Add option to also check the center to `find_node_near` --- doc/lua_api.txt | 4 +++- src/script/lua_api/l_env.cpp | 16 ++++++++-------- src/script/lua_api/l_env.h | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 599e02fcb..1550a78be 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2280,9 +2280,11 @@ and `minetest.auth_reload` call the authetification handler. * `minetest.get_gametime()`: returns the time, in seconds, since the world was created * `minetest.get_day_count()`: returns number days elapsed since world was created, * accounting for time changes. -* `minetest.find_node_near(pos, radius, nodenames)`: returns pos or `nil` +* `minetest.find_node_near(pos, radius, nodenames, [search_center])`: returns pos or `nil` * `radius`: using a maximum metric * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` + * `search_center` is an optional boolean (default: `false`) + If true `pos` is also checked for the nodes * `minetest.find_nodes_in_area(minp, maxp, nodenames)`: returns a list of positions * returns as second value a table with the count of the individual nodes found * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 85791f90b..0ded12658 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -599,7 +599,7 @@ int ModApiEnvMod::l_get_gametime(lua_State *L) } -// find_node_near(pos, radius, nodenames) -> pos or nil +// find_node_near(pos, radius, nodenames, search_center) -> pos or nil // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" int ModApiEnvMod::l_find_node_near(lua_State *L) { @@ -612,27 +612,27 @@ int ModApiEnvMod::l_find_node_near(lua_State *L) v3s16 pos = read_v3s16(L, 1); int radius = luaL_checkinteger(L, 2); std::set filter; - if(lua_istable(L, 3)){ - int table = 3; + if (lua_istable(L, 3)) { lua_pushnil(L); - while(lua_next(L, table) != 0){ + while (lua_next(L, 3) != 0) { // key at index -2 and value at index -1 luaL_checktype(L, -1, LUA_TSTRING); ndef->getIds(lua_tostring(L, -1), filter); // removes value, keeps key for next iteration lua_pop(L, 1); } - } else if(lua_isstring(L, 3)){ + } else if (lua_isstring(L, 3)) { ndef->getIds(lua_tostring(L, 3), filter); } - for (int d=1; d<=radius; d++){ + int start_radius = (lua_toboolean(L, 4)) ? 0 : 1; + for (int d = start_radius; d <= radius; d++) { std::vector list = FacePositionCache::getFacePositions(d); for (std::vector::iterator i = list.begin(); - i != list.end(); ++i){ + i != list.end(); ++i) { v3s16 p = pos + (*i); content_t c = env->getMap().getNodeNoEx(p).getContent(); - if (filter.count(c) != 0){ + if (filter.count(c) != 0) { push_v3s16(L, p); return 1; } diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 3f688b398..629c6bc39 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -116,7 +116,7 @@ private: // get_day_count() -> int static int l_get_day_count(lua_State *L); - // find_node_near(pos, radius, nodenames) -> pos or nil + // find_node_near(pos, radius, nodenames, search_center) -> pos or nil // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_node_near(lua_State *L); -- cgit v1.2.3 From 43d1f375d18a2fbc547a9b4f23d1354d645856ca Mon Sep 17 00:00:00 2001 From: ShadowNinja Date: Fri, 12 Dec 2014 14:49:19 -0500 Subject: Use a settings object for the main settings This unifies the settings APIs. This also unifies the sync and async registration APIs, since the async registration API did not support adding non-functions to the API table. --- builtin/common/misc_helpers.lua | 6 +- builtin/fstk/tabview.lua | 2 +- builtin/game/auth.lua | 6 +- builtin/game/chatcommands.lua | 20 ++--- builtin/game/deprecated.lua | 21 +++++ builtin/game/forceloading.lua | 2 +- builtin/game/init.lua | 2 +- builtin/game/item.lua | 2 +- builtin/game/item_entity.lua | 2 +- builtin/game/misc.lua | 2 +- builtin/game/statbars.lua | 2 +- builtin/game/static_spawn.lua | 4 +- builtin/init.lua | 2 +- builtin/mainmenu/common.lua | 14 ++-- builtin/mainmenu/dlg_config_world.lua | 4 +- builtin/mainmenu/dlg_create_world.lua | 14 ++-- builtin/mainmenu/dlg_settings_advanced.lua | 32 ++++---- builtin/mainmenu/init.lua | 8 +- builtin/mainmenu/store.lua | 6 +- builtin/mainmenu/tab_multiplayer.lua | 32 ++++---- builtin/mainmenu/tab_server.lua | 34 ++++---- builtin/mainmenu/tab_settings.lua | 120 ++++++++++++++--------------- builtin/mainmenu/tab_simple_main.lua | 30 ++++---- builtin/mainmenu/tab_singleplayer.lua | 18 ++--- builtin/mainmenu/tab_texturepacks.lua | 8 +- builtin/mainmenu/textures.lua | 6 +- builtin/profiler/init.lua | 12 ++- builtin/profiler/instrumentation.lua | 18 ++--- builtin/profiler/reporter.lua | 6 +- builtin/profiler/sampling.lua | 2 +- doc/lua_api.txt | 25 +++--- src/script/cpp_api/s_async.cpp | 17 ++-- src/script/cpp_api/s_async.h | 10 +-- src/script/lua_api/l_base.cpp | 14 ++-- src/script/lua_api/l_base.h | 5 +- src/script/lua_api/l_internal.h | 3 +- src/script/lua_api/l_mainmenu.cpp | 36 +++++---- src/script/lua_api/l_mainmenu.h | 3 +- src/script/lua_api/l_settings.cpp | 83 ++++++++++++++++---- src/script/lua_api/l_settings.h | 13 +++- src/script/lua_api/l_util.cpp | 110 +++++--------------------- src/script/lua_api/l_util.h | 18 +---- src/script/scripting_client.cpp | 10 +-- src/script/scripting_mainmenu.cpp | 17 ++-- src/script/scripting_mainmenu.h | 1 + src/script/scripting_server.cpp | 26 +++---- 46 files changed, 411 insertions(+), 417 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index fc284c843..1ca400688 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -463,7 +463,7 @@ if INIT == "game" then core.rotate_node = function(itemstack, placer, pointed_thing) core.rotate_and_place(itemstack, placer, pointed_thing, - core.setting_getbool("creative_mode"), + core.settings:get_bool("creative_mode"), {invert_wall = placer:get_player_control().sneak}) return itemstack end @@ -642,8 +642,8 @@ end local ESCAPE_CHAR = string.char(0x1b) --- Client-sided mods don't have access to getbool -if core.setting_getbool and core.setting_getbool("disable_escape_sequences") then +-- Client-side mods don't have access to settings +if core.settings and core.settings:get_bool("disable_escape_sequences") then function core.get_color_escape_sequence(color) return "" diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua index 72551afd7..3715e231b 100644 --- a/builtin/fstk/tabview.lua +++ b/builtin/fstk/tabview.lua @@ -167,7 +167,7 @@ local function switch_to_tab(self, index) self.current_tab = self.tablist[index].name if (self.autosave_tab) then - core.setting_set(self.name .. "_LAST",self.current_tab) + core.settings:set(self.name .. "_LAST",self.current_tab) end -- call for tab to enter diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua index 46fe3d342..8cb4ebf57 100644 --- a/builtin/game/auth.lua +++ b/builtin/game/auth.lua @@ -106,7 +106,7 @@ core.builtin_auth_handler = { end end -- For the admin, give everything - elseif name == core.setting_get("name") then + elseif name == core.settings:get("name") then for priv, def in pairs(core.registered_privileges) do privileges[priv] = true end @@ -125,7 +125,7 @@ core.builtin_auth_handler = { core.log('info', "Built-in authentication handler adding player '"..name.."'") core.auth_table[name] = { password = password, - privileges = core.string_to_privs(core.setting_get("default_privs")), + privileges = core.string_to_privs(core.settings:get("default_privs")), last_login = os.time(), } save_auth_file() @@ -148,7 +148,7 @@ core.builtin_auth_handler = { if not core.auth_table[name] then core.builtin_auth_handler.create_auth(name, core.get_password_hash(name, - core.setting_get("default_password"))) + core.settings:get("default_password"))) end core.auth_table[name].privileges = privileges core.notify_authentication_modified(name) diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index cbf75c1bc..b792a01cd 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -39,7 +39,7 @@ core.register_on_chat_message(function(name, message) return true -- Handled chat message end) -if core.setting_getbool("profiler.load") then +if core.settings:get_bool("profiler.load") then -- Run after register_chatcommand and its register_on_chat_message -- Before any chattcommands that should be profiled profiler.init_chatcommand() @@ -82,7 +82,7 @@ core.register_chatcommand("me", { core.register_chatcommand("admin", { description = "Show the name of the server owner", func = function(name) - local admin = minetest.setting_get("name") + local admin = minetest.settings:get("name") if admin then return true, "The administrator of this server is "..admin.."." else @@ -119,7 +119,7 @@ local function handle_grant_command(caller, grantname, grantprivstr) local privs = core.get_player_privs(grantname) local privs_unknown = "" local basic_privs = - core.string_to_privs(core.setting_get("basic_privs") or "interact,shout") + core.string_to_privs(core.settings:get("basic_privs") or "interact,shout") for priv, _ in pairs(grantprivs) do if not basic_privs[priv] and not caller_privs.privs then return false, "Your privileges are insufficient." @@ -185,7 +185,7 @@ core.register_chatcommand("revoke", { local revoke_privs = core.string_to_privs(revoke_priv_str) local privs = core.get_player_privs(revoke_name) local basic_privs = - core.string_to_privs(core.setting_get("basic_privs") or "interact,shout") + core.string_to_privs(core.settings:get("basic_privs") or "interact,shout") for priv, _ in pairs(revoke_privs) do if not basic_privs[priv] and not core.check_player_privs(name, {privs=true}) then @@ -419,20 +419,20 @@ core.register_chatcommand("set", { func = function(name, param) local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") if arg and arg == "-n" and setname and setvalue then - core.setting_set(setname, setvalue) + core.settings:set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname, setvalue = string.match(param, "([^ ]+) (.+)") if setname and setvalue then - if not core.setting_get(setname) then + if not core.settings:get(setname) then return false, "Failed. Use '/set -n ' to create a new setting." end - core.setting_set(setname, setvalue) + core.settings:set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname = string.match(param, "([^ ]+)") if setname then - local setvalue = core.setting_get(setname) + local setvalue = core.settings:get(setname) if not setvalue then setvalue = "" end @@ -667,7 +667,7 @@ core.register_chatcommand("rollback_check", { .. " seconds = 86400 = 24h, limit = 5", privs = {rollback=true}, func = function(name, param) - if not core.setting_getbool("enable_rollback_recording") then + if not core.settings:get_bool("enable_rollback_recording") then return false, "Rollback functions are disabled." end local range, seconds, limit = @@ -718,7 +718,7 @@ core.register_chatcommand("rollback", { description = "Revert actions of a player. Default for is 60", privs = {rollback=true}, func = function(name, param) - if not core.setting_getbool("enable_rollback_recording") then + if not core.settings:get_bool("enable_rollback_recording") then return false, "Rollback functions are disabled." end local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)") diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua index cd1cf5e2d..1a9a96f2a 100644 --- a/builtin/game/deprecated.lua +++ b/builtin/game/deprecated.lua @@ -49,3 +49,24 @@ setmetatable(core.env, { function core.rollback_get_last_node_actor(pos, range, seconds) return core.rollback_get_node_actions(pos, range, seconds, 1)[1] end + +-- +-- core.setting_* +-- + +local settings = core.settings + +local function setting_proxy(name) + return function(...) + core.log("deprecated", "WARNING: minetest.setting_* ".. + "functions are deprecated. ".. + "Use methods on the minetest.settings object.") + return settings[name](settings, ...) + end +end + +core.setting_set = setting_proxy("set") +core.setting_get = setting_proxy("get") +core.setting_setbool = setting_proxy("set_bool") +core.setting_getbool = setting_proxy("get_bool") +core.setting_save = setting_proxy("write") diff --git a/builtin/game/forceloading.lua b/builtin/game/forceloading.lua index 8a05de36c..7c5537e85 100644 --- a/builtin/game/forceloading.lua +++ b/builtin/game/forceloading.lua @@ -40,7 +40,7 @@ function core.forceload_block(pos, transient) elseif other_table[hash] ~= nil then relevant_table[hash] = 1 else - if total_forceloaded >= (tonumber(core.setting_get("max_forceloaded_blocks")) or 16) then + if total_forceloaded >= (tonumber(core.settings:get("max_forceloaded_blocks")) or 16) then return false end total_forceloaded = total_forceloaded+1 diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 3e192a30a..e2635f07a 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -13,7 +13,7 @@ dofile(gamepath.."constants.lua") assert(loadfile(gamepath.."item.lua"))(builtin_shared) dofile(gamepath.."register.lua") -if core.setting_getbool("profiler.load") then +if core.settings:get_bool("profiler.load") then profiler = dofile(scriptpath.."profiler"..DIR_DELIM.."init.lua") end diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 671a994c7..e36745f93 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -483,7 +483,7 @@ function core.node_dig(pos, node, digger) wielded = wdef.after_use(wielded, digger, node, dp) or wielded else -- Wear out tool - if not core.setting_getbool("creative_mode") then + if not core.settings:get_bool("creative_mode") then wielded:add_wear(dp.wear) if wielded:get_count() == 0 and wdef.sound and wdef.sound.breaks then core.sound_play(wdef.sound.breaks, {pos = pos, gain = 0.5}) diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index 7b8247116..c0e36be2d 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -14,7 +14,7 @@ end -- If item_entity_ttl is not set, enity will have default life time -- Setting it to -1 disables the feature -local time_to_live = tonumber(core.setting_get("item_entity_ttl")) +local time_to_live = tonumber(core.settings:get("item_entity_ttl")) if not time_to_live then time_to_live = 900 end diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index a3eb26ac2..bfe407b9d 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -121,7 +121,7 @@ function core.get_node_group(name, group) end function core.setting_get_pos(name) - local value = core.setting_get(name) + local value = core.settings:get(name) if not value then return nil end diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua index 4e7781e53..6aa106140 100644 --- a/builtin/game/statbars.lua +++ b/builtin/game/statbars.lua @@ -1,5 +1,5 @@ -- cache setting -local enable_damage = core.setting_getbool("enable_damage") == true +local enable_damage = core.settings:get_bool("enable_damage") local health_bar_definition = { diff --git a/builtin/game/static_spawn.lua b/builtin/game/static_spawn.lua index 100334226..b1157b42e 100644 --- a/builtin/game/static_spawn.lua +++ b/builtin/game/static_spawn.lua @@ -1,10 +1,10 @@ -- Minetest: builtin/static_spawn.lua local function warn_invalid_static_spawnpoint() - if core.setting_get("static_spawnpoint") and + if core.settings:get("static_spawnpoint") and not core.setting_get_pos("static_spawnpoint") then core.log("error", "The static_spawnpoint setting is invalid: \"".. - core.setting_get("static_spawnpoint").."\"") + core.settings:get("static_spawnpoint").."\"") end end diff --git a/builtin/init.lua b/builtin/init.lua index c9fa70fc7..356e119fb 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -38,7 +38,7 @@ dofile(commonpath .. "misc_helpers.lua") if INIT == "game" then dofile(gamepath .. "init.lua") elseif INIT == "mainmenu" then - local mm_script = core.setting_get("main_menu_script") + local mm_script = core.settings:get("main_menu_script") if mm_script and mm_script ~= "" then dofile(mm_script) else diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index 57950c62c..294e1a621 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -43,10 +43,10 @@ end local function configure_selected_world_params(idx) local worldconfig = modmgr.get_worldconfig(menudata.worldlist:get_list()[idx].path) if worldconfig.creative_mode then - core.setting_set("creative_mode", worldconfig.creative_mode) + core.settings:set("creative_mode", worldconfig.creative_mode) end if worldconfig.enable_damage then - core.setting_set("enable_damage", worldconfig.enable_damage) + core.settings:set("enable_damage", worldconfig.enable_damage) end end @@ -164,8 +164,8 @@ end -------------------------------------------------------------------------------- os.tempfolder = function() - if core.setting_get("TMPFolder") then - return core.setting_get("TMPFolder") .. DIR_DELIM .. "MT_" .. math.random(0,10000) + if core.settings:get("TMPFolder") then + return core.settings:get("TMPFolder") .. DIR_DELIM .. "MT_" .. math.random(0,10000) end local filetocheck = os.tmpname() @@ -206,7 +206,7 @@ function menu_handle_key_up_down(fields, textlist, settingname) oldidx < menudata.worldlist:size() then newidx = oldidx + 1 end - core.setting_set(settingname, menudata.worldlist:get_raw_index(newidx)) + core.settings:set(settingname, menudata.worldlist:get_raw_index(newidx)) configure_selected_world_params(newidx) return true end @@ -328,9 +328,9 @@ function menu_worldmt_legacy(selected) for _, mode_name in pairs(modes_names) do local mode_val = menu_worldmt(selected, mode_name) if mode_val then - core.setting_set(mode_name, mode_val) + core.settings:set(mode_name, mode_val) else - menu_worldmt(selected, mode_name, core.setting_get(mode_name)) + menu_worldmt(selected, mode_name, core.settings:get(mode_name)) end end end diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua index 619c927c5..3e5ba16eb 100644 --- a/builtin/mainmenu/dlg_config_world.lua +++ b/builtin/mainmenu/dlg_config_world.lua @@ -123,7 +123,7 @@ local function handle_buttons(this, fields) if fields["world_config_modlist"] ~= nil then local event = core.explode_table_event(fields["world_config_modlist"]) this.data.selected_mod = event.row - core.setting_set("world_config_selected_mod", event.row) + core.settings:set("world_config_selected_mod", event.row) if event.type == "DCL" then enable_mod(this) @@ -227,7 +227,7 @@ function create_configure_world_dlg(worldidx) handle_buttons, nil) - dlg.data.selected_mod = tonumber(core.setting_get("world_config_selected_mod")) + dlg.data.selected_mod = tonumber(core.settings:get("world_config_selected_mod")) if dlg.data.selected_mod == nil then dlg.data.selected_mod = 0 end diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 477b8bcb9..e9ca7799f 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -18,8 +18,8 @@ local function create_world_formspec(dialogdata) local mapgens = core.get_mapgen_names() - local current_seed = core.setting_get("fixed_map_seed") or "" - local current_mg = core.setting_get("mg_name") + local current_seed = core.settings:get("fixed_map_seed") or "" + local current_mg = core.settings:get("mg_name") local mglist = "" local selindex = 1 @@ -33,7 +33,7 @@ local function create_world_formspec(dialogdata) end mglist = mglist:sub(1, -2) - local gameid = core.setting_get("menu_last_game") + local gameid = core.settings:get("menu_last_game") local game, gameidx = nil , 0 if gameid ~= nil then @@ -90,10 +90,10 @@ local function create_world_buttonhandler(this, fields) local message = nil - core.setting_set("fixed_map_seed", fields["te_seed"]) + core.settings:set("fixed_map_seed", fields["te_seed"]) if not menudata.worldlist:uid_exists_raw(worldname) then - core.setting_set("mg_name",fields["dd_mapgen"]) + core.settings:set("mg_name",fields["dd_mapgen"]) message = core.create_world(worldname,gameindex) else message = fgettext("A world named \"$1\" already exists", worldname) @@ -102,13 +102,13 @@ local function create_world_buttonhandler(this, fields) if message ~= nil then gamedata.errormessage = message else - core.setting_set("menu_last_game",gamemgr.games[gameindex].id) + core.settings:set("menu_last_game",gamemgr.games[gameindex].id) if this.data.update_worldlist_filter then menudata.worldlist:set_filtercriteria(gamemgr.games[gameindex].id) mm_texture.update("singleplayer", gamemgr.games[gameindex].id) end menudata.worldlist:refresh() - core.setting_set("mainmenu_last_selected_world", + core.settings:set("mainmenu_last_selected_world", menudata.worldlist:raw_index_by_uid(worldname)) end else diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua index c63eb972e..206ce1620 100644 --- a/builtin/mainmenu/dlg_settings_advanced.lua +++ b/builtin/mainmenu/dlg_settings_advanced.lua @@ -423,7 +423,7 @@ local settings = full_settings local selected_setting = 1 local function get_current_value(setting) - local value = core.setting_get(setting.name) + local value = core.settings:get(setting.name) if value == nil then value = setting.default end @@ -539,11 +539,11 @@ local function handle_change_setting_buttons(this, fields) if setting.type == "bool" then local new_value = fields["dd_setting_value"] -- Note: new_value is the actual (translated) value shown in the dropdown - core.setting_setbool(setting.name, new_value == fgettext("Enabled")) + core.settings:set_bool(setting.name, new_value == fgettext("Enabled")) elseif setting.type == "enum" then local new_value = fields["dd_setting_value"] - core.setting_set(setting.name, new_value) + core.settings:set(setting.name, new_value) elseif setting.type == "int" then local new_value = tonumber(fields["te_setting_value"]) @@ -565,7 +565,7 @@ local function handle_change_setting_buttons(this, fields) core.update_formspec(this:get_formspec()) return true end - core.setting_set(setting.name, new_value) + core.settings:set(setting.name, new_value) elseif setting.type == "float" then local new_value = tonumber(fields["te_setting_value"]) @@ -575,7 +575,7 @@ local function handle_change_setting_buttons(this, fields) core.update_formspec(this:get_formspec()) return true end - core.setting_set(setting.name, new_value) + core.settings:set(setting.name, new_value) elseif setting.type == "flags" then local new_value = fields["te_setting_value"] @@ -589,13 +589,13 @@ local function handle_change_setting_buttons(this, fields) return true end end - core.setting_set(setting.name, new_value) + core.settings:set(setting.name, new_value) else local new_value = fields["te_setting_value"] - core.setting_set(setting.name, new_value) + core.settings:set(setting.name, new_value) end - core.setting_save() + core.settings:write() this:delete() return true end @@ -629,7 +629,7 @@ local function create_settings_formspec(tabview, name, tabdata) local current_level = 0 for _, entry in ipairs(settings) do local name - if not core.setting_getbool("main_menu_technical_settings") and entry.readable_name then + if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then name = fgettext_ne(entry.readable_name) else name = entry.name @@ -666,7 +666,7 @@ local function create_settings_formspec(tabview, name, tabdata) "button[10,6;2,1;btn_edit;" .. fgettext("Edit") .. "]" .. "button[7,6;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" .. "checkbox[0,5.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";" - .. dump(core.setting_getbool("main_menu_technical_settings")) .. "]" + .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]" return formspec end @@ -680,8 +680,8 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) local setting = settings[selected_setting] if setting and setting.type == "bool" then local current_value = get_current_value(setting) - core.setting_setbool(setting.name, not core.is_yes(current_value)) - core.setting_save() + core.settings:set_bool(setting.name, not core.is_yes(current_value)) + core.settings:write() return true else list_enter = true @@ -736,8 +736,8 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) if fields["btn_restore"] then local setting = settings[selected_setting] if setting and setting.type ~= "category" then - core.setting_set(setting.name, setting.default) - core.setting_save() + core.settings:set(setting.name, setting.default) + core.settings:write() core.update_formspec(this:get_formspec()) end return true @@ -749,8 +749,8 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) end if fields["cb_tech_settings"] then - core.setting_set("main_menu_technical_settings", fields["cb_tech_settings"]) - core.setting_save() + core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"]) + core.settings:write() core.update_formspec(this:get_formspec()) return true end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 79e6d5c02..492bb22d6 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -119,9 +119,9 @@ local function init_globals() menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic) menudata.worldlist:set_sortmode("alphabetic") - if not core.setting_get("menu_last_game") then - local default_game = core.setting_get("default_game") or "minetest" - core.setting_set("menu_last_game", default_game) + if not core.settings:get("menu_last_game") then + local default_game = core.settings:get("default_game") or "minetest" + core.settings:set("menu_last_game", default_game) end mm_texture.init() @@ -149,7 +149,7 @@ local function init_globals() tv_main:set_fixed_size(false) if PLATFORM ~= "Android" then - tv_main:set_tab(core.setting_get("maintab_LAST")) + tv_main:set_tab(core.settings:get("maintab_LAST")) end ui.set_default("maintab") tv_main:show() diff --git a/builtin/mainmenu/store.lua b/builtin/mainmenu/store.lua index ad861082d..59391f8bc 100644 --- a/builtin/mainmenu/store.lua +++ b/builtin/mainmenu/store.lua @@ -233,14 +233,14 @@ function modstore.handle_buttons(parent, fields, name, data) if not core.handle_async( function(param) - local fullurl = core.setting_get("modstore_download_url") .. + local fullurl = core.settings:get("modstore_download_url") .. param.moddetails.download_url if param.version ~= nil then local found = false for i=1,#param.moddetails.versions, 1 do if param.moddetails.versions[i].date:sub(1,10) == param.version then - fullurl = core.setting_get("modstore_download_url") .. + fullurl = core.settings:get("modstore_download_url") .. param.moddetails.versions[i].download_url found = true end @@ -400,7 +400,7 @@ function modstore.getscreenshot(ypos,listentry) listentry.texturename = "in progress" --prepare url and filename - local fullurl = core.setting_get("modstore_download_url") .. + local fullurl = core.settings:get("modstore_download_url") .. listentry.details.screenshot_url local filename = os.tempfolder() .. "_MID_" .. listentry.id diff --git a/builtin/mainmenu/tab_multiplayer.lua b/builtin/mainmenu/tab_multiplayer.lua index 0f4921b03..a9b2b35fe 100644 --- a/builtin/mainmenu/tab_multiplayer.lua +++ b/builtin/mainmenu/tab_multiplayer.lua @@ -39,14 +39,14 @@ local function get_formspec(tabview, name, tabdata) -- Address / Port "label[7.75,-0.25;" .. fgettext("Address / Port") .. "]" .. "field[8,0.65;3.25,0.5;te_address;;" .. - core.formspec_escape(core.setting_get("address")) .. "]" .. + core.formspec_escape(core.settings:get("address")) .. "]" .. "field[11.1,0.65;1.4,0.5;te_port;;" .. - core.formspec_escape(core.setting_get("remote_port")) .. "]" .. + core.formspec_escape(core.settings:get("remote_port")) .. "]" .. -- Name / Password "label[7.75,0.95;" .. fgettext("Name / Password") .. "]" .. "field[8,1.85;2.9,0.5;te_name;;" .. - core.formspec_escape(core.setting_get("name")) .. "]" .. + core.formspec_escape(core.settings:get("name")) .. "]" .. "pwdfield[10.73,1.85;1.77,0.5;te_pwd;]" .. -- Description Background @@ -135,7 +135,7 @@ local function main_button_handler(tabview, fields, name, tabdata) if fields.te_name then gamedata.playername = fields.te_name - core.setting_set("name", fields.te_name) + core.settings:set("name", fields.te_name) end if fields.favourites then @@ -163,8 +163,8 @@ local function main_button_handler(tabview, fields, name, tabdata) gamedata.serverdescription = fav.description if gamedata.address and gamedata.port then - core.setting_set("address", gamedata.address) - core.setting_set("remote_port", gamedata.port) + core.settings:set("address", gamedata.address) + core.settings:set("remote_port", gamedata.port) core.start() end end @@ -187,8 +187,8 @@ local function main_button_handler(tabview, fields, name, tabdata) end if address and port then - core.setting_set("address", address) - core.setting_set("remote_port", port) + core.settings:set("address", address) + core.settings:set("remote_port", port) end tabdata.fav_selected = event.row end @@ -219,8 +219,8 @@ local function main_button_handler(tabview, fields, name, tabdata) local port = fav.port gamedata.serverdescription = fav.description if address and port then - core.setting_set("address", address) - core.setting_set("remote_port", port) + core.settings:set("address", address) + core.settings:set("remote_port", port) end tabdata.fav_selected = fav_idx @@ -235,8 +235,8 @@ local function main_button_handler(tabview, fields, name, tabdata) asyncOnlineFavourites() tabdata.fav_selected = nil - core.setting_set("address", "") - core.setting_set("remote_port", "30000") + core.settings:set("address", "") + core.settings:set("remote_port", "30000") return true end @@ -293,8 +293,8 @@ local function main_button_handler(tabview, fields, name, tabdata) end) menudata.search_result = search_result local first_server = search_result[1] - core.setting_set("address", first_server.address) - core.setting_set("remote_port", first_server.port) + core.settings:set("address", first_server.address) + core.settings:set("remote_port", first_server.port) end return true end @@ -325,8 +325,8 @@ local function main_button_handler(tabview, fields, name, tabdata) gamedata.serverdescription = "" end - core.setting_set("address", fields.te_address) - core.setting_set("remote_port", fields.te_port) + core.settings:set("address", fields.te_address) + core.settings:set("remote_port", fields.te_port) core.start() return true diff --git a/builtin/mainmenu/tab_server.lua b/builtin/mainmenu/tab_server.lua index be57ad7ef..8f25ce871 100644 --- a/builtin/mainmenu/tab_server.lua +++ b/builtin/mainmenu/tab_server.lua @@ -19,7 +19,7 @@ local function get_formspec(tabview, name, tabdata) local index = menudata.worldlist:get_current_index( - tonumber(core.setting_get("mainmenu_last_selected_world")) + tonumber(core.settings:get("mainmenu_last_selected_world")) ) local retval = @@ -29,29 +29,29 @@ local function get_formspec(tabview, name, tabdata) "button[8.5,5;3.25,0.5;start_server;" .. fgettext("Start Game") .. "]" .. "label[4,-0.25;" .. fgettext("Select World:") .. "]" .. "checkbox[0.25,0.25;cb_creative_mode;" .. fgettext("Creative Mode") .. ";" .. - dump(core.setting_getbool("creative_mode")) .. "]" .. + dump(core.settings:get_bool("creative_mode")) .. "]" .. "checkbox[0.25,0.7;cb_enable_damage;" .. fgettext("Enable Damage") .. ";" .. - dump(core.setting_getbool("enable_damage")) .. "]" .. + dump(core.settings:get_bool("enable_damage")) .. "]" .. "checkbox[0.25,1.15;cb_server_announce;" .. fgettext("Public") .. ";" .. - dump(core.setting_getbool("server_announce")) .. "]" .. + dump(core.settings:get_bool("server_announce")) .. "]" .. "label[0.25,2.2;" .. fgettext("Name/Password") .. "]" .. "field[0.55,3.2;3.5,0.5;te_playername;;" .. - core.formspec_escape(core.setting_get("name")) .. "]" .. + core.formspec_escape(core.settings:get("name")) .. "]" .. "pwdfield[0.55,4;3.5,0.5;te_passwd;]" - local bind_addr = core.setting_get("bind_address") + local bind_addr = core.settings:get("bind_address") if bind_addr ~= nil and bind_addr ~= "" then retval = retval .. "field[0.55,5.2;2.25,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" .. - core.formspec_escape(core.setting_get("bind_address")) .. "]" .. + core.formspec_escape(core.settings:get("bind_address")) .. "]" .. "field[2.8,5.2;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" .. - core.formspec_escape(core.setting_get("port")) .. "]" + core.formspec_escape(core.settings:get("port")) .. "]" else retval = retval .. "field[0.55,5.2;3.5,0.5;te_serverport;" .. fgettext("Server Port") .. ";" .. - core.formspec_escape(core.setting_get("port")) .. "]" + core.formspec_escape(core.settings:get("port")) .. "]" end - + retval = retval .. "textlist[4,0.25;7.5,3.7;srv_worlds;" .. menu_render_worldlist() .. @@ -75,7 +75,7 @@ local function main_button_handler(this, fields, name, tabdata) world_doubleclick = true end if event.type == "CHG" then - core.setting_set("mainmenu_last_selected_world", + core.settings:set("mainmenu_last_selected_world", menudata.worldlist:get_raw_index(core.get_textlist_index("srv_worlds"))) return true end @@ -86,7 +86,7 @@ local function main_button_handler(this, fields, name, tabdata) end if fields["cb_creative_mode"] then - core.setting_set("creative_mode", fields["cb_creative_mode"]) + core.settings:set("creative_mode", fields["cb_creative_mode"]) local selected = core.get_textlist_index("srv_worlds") menu_worldmt(selected, "creative_mode", fields["cb_creative_mode"]) @@ -94,7 +94,7 @@ local function main_button_handler(this, fields, name, tabdata) end if fields["cb_enable_damage"] then - core.setting_set("enable_damage", fields["cb_enable_damage"]) + core.settings:set("enable_damage", fields["cb_enable_damage"]) local selected = core.get_textlist_index("srv_worlds") menu_worldmt(selected, "enable_damage", fields["cb_enable_damage"]) @@ -102,7 +102,7 @@ local function main_button_handler(this, fields, name, tabdata) end if fields["cb_server_announce"] then - core.setting_set("server_announce", fields["cb_server_announce"]) + core.settings:set("server_announce", fields["cb_server_announce"]) local selected = core.get_textlist_index("srv_worlds") menu_worldmt(selected, "server_announce", fields["cb_server_announce"]) @@ -120,16 +120,16 @@ local function main_button_handler(this, fields, name, tabdata) gamedata.port = fields["te_serverport"] gamedata.address = "" - core.setting_set("port",gamedata.port) + core.settings:set("port",gamedata.port) if fields["te_serveraddr"] ~= nil then - core.setting_set("bind_address",fields["te_serveraddr"]) + core.settings:set("bind_address",fields["te_serveraddr"]) end --update last game local world = menudata.worldlist:get_raw_element(gamedata.selected_world) if world then local game, index = gamemgr.find_by_gameid(world.gameid) - core.setting_set("menu_last_game", game.id) + core.settings:set("menu_last_game", game.id) end core.start() diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 8a97d8334..69683eaa3 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -70,39 +70,39 @@ local dd_options = { local getSettingIndex = { Leaves = function() - local style = core.setting_get("leaves_style") + local style = core.settings:get("leaves_style") for idx, name in pairs(dd_options.leaves[2]) do if style == name then return idx end end return 1 end, NodeHighlighting = function() - local style = core.setting_get("node_highlighting") + local style = core.settings:get("node_highlighting") for idx, name in pairs(dd_options.node_highlighting[2]) do if style == name then return idx end end return 1 end, Filter = function() - if core.setting_get(dd_options.filters[2][3]) == "true" then + if core.settings:get(dd_options.filters[2][3]) == "true" then return 3 - elseif core.setting_get(dd_options.filters[2][3]) == "false" and - core.setting_get(dd_options.filters[2][2]) == "true" then + elseif core.settings:get(dd_options.filters[2][3]) == "false" and + core.settings:get(dd_options.filters[2][2]) == "true" then return 2 end return 1 end, Mipmap = function() - if core.setting_get(dd_options.mipmap[2][3]) == "true" then + if core.settings:get(dd_options.mipmap[2][3]) == "true" then return 3 - elseif core.setting_get(dd_options.mipmap[2][3]) == "false" and - core.setting_get(dd_options.mipmap[2][2]) == "true" then + elseif core.settings:get(dd_options.mipmap[2][3]) == "false" and + core.settings:get(dd_options.mipmap[2][2]) == "true" then return 2 end return 1 end, Antialiasing = function() - local antialiasing_setting = core.setting_get("fsaa") + local antialiasing_setting = core.settings:get("fsaa") for i = 1, #dd_options.antialiasing[2] do if antialiasing_setting == dd_options.antialiasing[2][i] then return i @@ -177,15 +177,15 @@ local function formspec(tabview, name, tabdata) local tab_string = "box[0,0;3.5,4.5;#999999]" .. "checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") .. ";" - .. dump(core.setting_getbool("smooth_lighting")) .. "]" .. + .. dump(core.settings:get_bool("smooth_lighting")) .. "]" .. "checkbox[0.25,0.5;cb_particles;" .. fgettext("Particles") .. ";" - .. dump(core.setting_getbool("enable_particles")) .. "]" .. + .. dump(core.settings:get_bool("enable_particles")) .. "]" .. "checkbox[0.25,1;cb_3d_clouds;" .. fgettext("3D Clouds") .. ";" - .. dump(core.setting_getbool("enable_3d_clouds")) .. "]" .. + .. dump(core.settings:get_bool("enable_3d_clouds")) .. "]" .. "checkbox[0.25,1.5;cb_opaque_water;" .. fgettext("Opaque Water") .. ";" - .. dump(core.setting_getbool("opaque_water")) .. "]" .. + .. dump(core.settings:get_bool("opaque_water")) .. "]" .. "checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";" - .. dump(core.setting_getbool("connected_glass")) .. "]" .. + .. dump(core.settings:get_bool("connected_glass")) .. "]" .. "dropdown[0.25,2.8;3.3;dd_node_highlighting;" .. dd_options.node_highlighting[1] .. ";" .. getSettingIndex.NodeHighlighting() .. "]" .. "dropdown[0.25,3.6;3.3;dd_leaves_style;" .. dd_options.leaves[1] .. ";" @@ -201,10 +201,10 @@ local function formspec(tabview, name, tabdata) .. getSettingIndex.Antialiasing() .. "]" .. "label[3.85,3.45;" .. fgettext("Screen:") .. "]" .. "checkbox[3.85,3.6;cb_autosave_screensize;" .. fgettext("Autosave screen size") .. ";" - .. dump(core.setting_getbool("autosave_screensize")) .. "]" .. + .. dump(core.settings:get_bool("autosave_screensize")) .. "]" .. "box[7.75,0;4,4.4;#999999]" .. "checkbox[8,0;cb_shaders;" .. fgettext("Shaders") .. ";" - .. dump(core.setting_getbool("enable_shaders")) .. "]" + .. dump(core.settings:get_bool("enable_shaders")) .. "]" if PLATFORM == "Android" then tab_string = tab_string .. @@ -221,29 +221,29 @@ local function formspec(tabview, name, tabdata) .. fgettext("Advanced Settings") .. "]" - if core.setting_get("touchscreen_threshold") ~= nil then + if core.settings:get("touchscreen_threshold") ~= nil then tab_string = tab_string .. "label[4.3,4.1;" .. fgettext("Touchthreshold (px)") .. "]" .. "dropdown[3.85,4.55;3.85;dd_touchthreshold;0,10,20,30,40,50;" .. - ((tonumber(core.setting_get("touchscreen_threshold")) / 10) + 1) .. "]" + ((tonumber(core.settings:get("touchscreen_threshold")) / 10) + 1) .. "]" end - if core.setting_getbool("enable_shaders") then + if core.settings:get_bool("enable_shaders") then tab_string = tab_string .. "checkbox[8,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";" - .. dump(core.setting_getbool("enable_bumpmapping")) .. "]" .. + .. dump(core.settings:get_bool("enable_bumpmapping")) .. "]" .. "checkbox[8,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" - .. dump(core.setting_getbool("tone_mapping")) .. "]" .. + .. dump(core.settings:get_bool("tone_mapping")) .. "]" .. "checkbox[8,1.5;cb_generate_normalmaps;" .. fgettext("Normal Mapping") .. ";" - .. dump(core.setting_getbool("generate_normalmaps")) .. "]" .. + .. dump(core.settings:get_bool("generate_normalmaps")) .. "]" .. "checkbox[8,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" - .. dump(core.setting_getbool("enable_parallax_occlusion")) .. "]" .. + .. dump(core.settings:get_bool("enable_parallax_occlusion")) .. "]" .. "checkbox[8,2.5;cb_waving_water;" .. fgettext("Waving Water") .. ";" - .. dump(core.setting_getbool("enable_waving_water")) .. "]" .. + .. dump(core.settings:get_bool("enable_waving_water")) .. "]" .. "checkbox[8,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" - .. dump(core.setting_getbool("enable_waving_leaves")) .. "]" .. + .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" .. "checkbox[8,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" - .. dump(core.setting_getbool("enable_waving_plants")) .. "]" + .. dump(core.settings:get_bool("enable_waving_plants")) .. "]" else tab_string = tab_string .. "tablecolumns[color;text]" .. @@ -274,64 +274,64 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) return true end if fields["cb_smooth_lighting"] then - core.setting_set("smooth_lighting", fields["cb_smooth_lighting"]) + core.settings:set("smooth_lighting", fields["cb_smooth_lighting"]) return true end if fields["cb_particles"] then - core.setting_set("enable_particles", fields["cb_particles"]) + core.settings:set("enable_particles", fields["cb_particles"]) return true end if fields["cb_3d_clouds"] then - core.setting_set("enable_3d_clouds", fields["cb_3d_clouds"]) + core.settings:set("enable_3d_clouds", fields["cb_3d_clouds"]) return true end if fields["cb_opaque_water"] then - core.setting_set("opaque_water", fields["cb_opaque_water"]) + core.settings:set("opaque_water", fields["cb_opaque_water"]) return true end if fields["cb_connected_glass"] then - core.setting_set("connected_glass", fields["cb_connected_glass"]) + core.settings:set("connected_glass", fields["cb_connected_glass"]) return true end if fields["cb_autosave_screensize"] then - core.setting_set("autosave_screensize", fields["cb_autosave_screensize"]) + core.settings:set("autosave_screensize", fields["cb_autosave_screensize"]) return true end if fields["cb_shaders"] then - if (core.setting_get("video_driver") == "direct3d8" or - core.setting_get("video_driver") == "direct3d9") then - core.setting_set("enable_shaders", "false") + if (core.settings:get("video_driver") == "direct3d8" or + core.settings:get("video_driver") == "direct3d9") then + core.settings:set("enable_shaders", "false") gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.") else - core.setting_set("enable_shaders", fields["cb_shaders"]) + core.settings:set("enable_shaders", fields["cb_shaders"]) end return true end if fields["cb_bumpmapping"] then - core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"]) + core.settings:set("enable_bumpmapping", fields["cb_bumpmapping"]) return true end if fields["cb_tonemapping"] then - core.setting_set("tone_mapping", fields["cb_tonemapping"]) + core.settings:set("tone_mapping", fields["cb_tonemapping"]) return true end if fields["cb_generate_normalmaps"] then - core.setting_set("generate_normalmaps", fields["cb_generate_normalmaps"]) + core.settings:set("generate_normalmaps", fields["cb_generate_normalmaps"]) return true end if fields["cb_parallax"] then - core.setting_set("enable_parallax_occlusion", fields["cb_parallax"]) + core.settings:set("enable_parallax_occlusion", fields["cb_parallax"]) return true end if fields["cb_waving_water"] then - core.setting_set("enable_waving_water", fields["cb_waving_water"]) + core.settings:set("enable_waving_water", fields["cb_waving_water"]) return true end if fields["cb_waving_leaves"] then - core.setting_set("enable_waving_leaves", fields["cb_waving_leaves"]) + core.settings:set("enable_waving_leaves", fields["cb_waving_leaves"]) end if fields["cb_waving_plants"] then - core.setting_set("enable_waving_plants", fields["cb_waving_plants"]) + core.settings:set("enable_waving_plants", fields["cb_waving_plants"]) return true end if fields["btn_change_keys"] then @@ -339,7 +339,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) return true end if fields["cb_touchscreen_target"] then - core.setting_set("touchtarget", fields["cb_touchscreen_target"]) + core.settings:set("touchtarget", fields["cb_touchscreen_target"]) return true end if fields["btn_reset_singleplayer"] then @@ -352,49 +352,49 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) for i = 1, #labels.leaves do if fields["dd_leaves_style"] == labels.leaves[i] then - core.setting_set("leaves_style", dd_options.leaves[2][i]) + core.settings:set("leaves_style", dd_options.leaves[2][i]) ddhandled = true end end for i = 1, #labels.node_highlighting do if fields["dd_node_highlighting"] == labels.node_highlighting[i] then - core.setting_set("node_highlighting", dd_options.node_highlighting[2][i]) + core.settings:set("node_highlighting", dd_options.node_highlighting[2][i]) ddhandled = true end end if fields["dd_filters"] == labels.filters[1] then - core.setting_set("bilinear_filter", "false") - core.setting_set("trilinear_filter", "false") + core.settings:set("bilinear_filter", "false") + core.settings:set("trilinear_filter", "false") ddhandled = true elseif fields["dd_filters"] == labels.filters[2] then - core.setting_set("bilinear_filter", "true") - core.setting_set("trilinear_filter", "false") + core.settings:set("bilinear_filter", "true") + core.settings:set("trilinear_filter", "false") ddhandled = true elseif fields["dd_filters"] == labels.filters[3] then - core.setting_set("bilinear_filter", "false") - core.setting_set("trilinear_filter", "true") + core.settings:set("bilinear_filter", "false") + core.settings:set("trilinear_filter", "true") ddhandled = true end if fields["dd_mipmap"] == labels.mipmap[1] then - core.setting_set("mip_map", "false") - core.setting_set("anisotropic_filter", "false") + core.settings:set("mip_map", "false") + core.settings:set("anisotropic_filter", "false") ddhandled = true elseif fields["dd_mipmap"] == labels.mipmap[2] then - core.setting_set("mip_map", "true") - core.setting_set("anisotropic_filter", "false") + core.settings:set("mip_map", "true") + core.settings:set("anisotropic_filter", "false") ddhandled = true elseif fields["dd_mipmap"] == labels.mipmap[3] then - core.setting_set("mip_map", "true") - core.setting_set("anisotropic_filter", "true") + core.settings:set("mip_map", "true") + core.settings:set("anisotropic_filter", "true") ddhandled = true end if fields["dd_antialiasing"] then - core.setting_set("fsaa", + core.settings:set("fsaa", antialiasing_fname_to_name(fields["dd_antialiasing"])) ddhandled = true end if fields["dd_touchthreshold"] then - core.setting_set("touchscreen_threshold", fields["dd_touchthreshold"]) + core.settings:set("touchscreen_threshold", fields["dd_touchthreshold"]) ddhandled = true end diff --git a/builtin/mainmenu/tab_simple_main.lua b/builtin/mainmenu/tab_simple_main.lua index 23820aab7..90a743f68 100644 --- a/builtin/mainmenu/tab_simple_main.lua +++ b/builtin/mainmenu/tab_simple_main.lua @@ -25,12 +25,12 @@ local function get_formspec(tabview, name, tabdata) local retval = "label[9.5,0;".. fgettext("Name / Password") .. "]" .. "field[0.25,3.35;5.5,0.5;te_address;;" .. - core.formspec_escape(core.setting_get("address")) .."]" .. + core.formspec_escape(core.settings:get("address")) .."]" .. "field[5.75,3.35;2.25,0.5;te_port;;" .. - core.formspec_escape(core.setting_get("remote_port")) .."]" .. + core.formspec_escape(core.settings:get("remote_port")) .."]" .. "button[10,2.6;2,1.5;btn_mp_connect;".. fgettext("Connect") .. "]" .. "field[9.8,1;2.6,0.5;te_name;;" .. - core.formspec_escape(core.setting_get("name")) .."]" .. + core.formspec_escape(core.settings:get("name")) .."]" .. "pwdfield[9.8,2;2.6,0.5;te_pwd;]" @@ -89,9 +89,9 @@ local function get_formspec(tabview, name, tabdata) -- checkboxes retval = retval .. "checkbox[8.0,3.9;cb_creative;".. fgettext("Creative Mode") .. ";" .. - dump(core.setting_getbool("creative_mode")) .. "]".. + dump(core.settings:get_bool("creative_mode")) .. "]".. "checkbox[8.0,4.4;cb_damage;".. fgettext("Enable Damage") .. ";" .. - dump(core.setting_getbool("enable_damage")) .. "]" + dump(core.settings:get_bool("enable_damage")) .. "]" -- buttons retval = retval .. "button[0,3.7;8,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" .. @@ -128,8 +128,8 @@ local function main_button_handler(tabview, fields, name, tabdata) end if address and port then - core.setting_set("address", address) - core.setting_set("remote_port", port) + core.settings:set("address", address) + core.settings:set("remote_port", port) end tabdata.fav_selected = event.row end @@ -145,18 +145,18 @@ local function main_button_handler(tabview, fields, name, tabdata) asyncOnlineFavourites() tabdata.fav_selected = nil - core.setting_set("address", "") - core.setting_set("remote_port", "30000") + core.settings:set("address", "") + core.settings:set("remote_port", "30000") return true end if fields.cb_creative then - core.setting_set("creative_mode", fields.cb_creative) + core.settings:set("creative_mode", fields.cb_creative) return true end if fields.cb_damage then - core.setting_set("enable_damage", fields.cb_damage) + core.settings:set("enable_damage", fields.cb_damage) return true end @@ -186,12 +186,8 @@ local function main_button_handler(tabview, fields, name, tabdata) gamedata.selected_world = 0 - core.setting_set("address", fields.te_address) - core.setting_set("remote_port", fields.te_port) - - core.start() - return true - end + core.settings:set("address", fields.te_address) + core.settings:set("remote_port", fields.te_port) if fields.btn_config_sp_world then local configdialog = create_configure_world_dlg(1) diff --git a/builtin/mainmenu/tab_singleplayer.lua b/builtin/mainmenu/tab_singleplayer.lua index 236de763c..c58ad4164 100644 --- a/builtin/mainmenu/tab_singleplayer.lua +++ b/builtin/mainmenu/tab_singleplayer.lua @@ -16,7 +16,7 @@ --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. local function current_game() - local last_game_id = core.setting_get("menu_last_game") + local last_game_id = core.settings:get("menu_last_game") local game, index = gamemgr.find_by_gameid(last_game_id) return game @@ -36,10 +36,10 @@ local function singleplayer_refresh_gamebar() if ("game_btnbar_" .. gamemgr.games[j].id == key) then mm_texture.update("singleplayer", gamemgr.games[j]) core.set_topleft_text(gamemgr.games[j].name) - core.setting_set("menu_last_game",gamemgr.games[j].id) + core.settings:set("menu_last_game",gamemgr.games[j].id) menudata.worldlist:set_filtercriteria(gamemgr.games[j].id) local index = filterlist.get_current_index(menudata.worldlist, - tonumber(core.setting_get("mainmenu_last_selected_world"))) + tonumber(core.settings:get("mainmenu_last_selected_world"))) if not index or index < 1 then local selected = core.get_textlist_index("sp_worlds") if selected ~= nil and selected < #menudata.worldlist:get_list() then @@ -89,7 +89,7 @@ local function get_formspec(tabview, name, tabdata) local retval = "" local index = filterlist.get_current_index(menudata.worldlist, - tonumber(core.setting_get("mainmenu_last_selected_world")) + tonumber(core.settings:get("mainmenu_last_selected_world")) ) retval = retval .. @@ -99,9 +99,9 @@ local function get_formspec(tabview, name, tabdata) "button[8.5,5;3.25,0.5;play;".. fgettext("Play") .. "]" .. "label[4,-0.25;".. fgettext("Select World:") .. "]".. "checkbox[0.25,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. - dump(core.setting_getbool("creative_mode")) .. "]".. + dump(core.settings:get_bool("creative_mode")) .. "]".. "checkbox[0.25,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. - dump(core.setting_getbool("enable_damage")) .. "]".. + dump(core.settings:get_bool("enable_damage")) .. "]".. "textlist[4,0.25;7.5,3.7;sp_worlds;" .. menu_render_worldlist() .. ";" .. index .. "]" @@ -125,7 +125,7 @@ local function main_button_handler(this, fields, name, tabdata) end if event.type == "CHG" and selected ~= nil then - core.setting_set("mainmenu_last_selected_world", + core.settings:set("mainmenu_last_selected_world", menudata.worldlist:get_raw_index(selected)) return true end @@ -136,7 +136,7 @@ local function main_button_handler(this, fields, name, tabdata) end if fields["cb_creative_mode"] then - core.setting_set("creative_mode", fields["cb_creative_mode"]) + core.settings:set("creative_mode", fields["cb_creative_mode"]) local selected = core.get_textlist_index("sp_worlds") menu_worldmt(selected, "creative_mode", fields["cb_creative_mode"]) @@ -144,7 +144,7 @@ local function main_button_handler(this, fields, name, tabdata) end if fields["cb_enable_damage"] then - core.setting_set("enable_damage", fields["cb_enable_damage"]) + core.settings:set("enable_damage", fields["cb_enable_damage"]) local selected = core.get_textlist_index("sp_worlds") menu_worldmt(selected, "enable_damage", fields["cb_enable_damage"]) diff --git a/builtin/mainmenu/tab_texturepacks.lua b/builtin/mainmenu/tab_texturepacks.lua index 4638beaa1..2957481cf 100644 --- a/builtin/mainmenu/tab_texturepacks.lua +++ b/builtin/mainmenu/tab_texturepacks.lua @@ -54,9 +54,9 @@ local function get_formspec(tabview, name, tabdata) local retval = "label[4,-0.25;" .. fgettext("Select texture pack:") .. "]" .. "textlist[4,0.25;7.5,5.0;TPs;" - local current_texture_path = core.setting_get("texture_path") + local current_texture_path = core.settings:get("texture_path") local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true)) - local index = tonumber(core.setting_get("mainmenu_last_selected_TP")) + local index = tonumber(core.settings:get("mainmenu_last_selected_TP")) if not index then index = 1 end @@ -106,7 +106,7 @@ local function main_button_handler(tabview, fields, name, tabdata) local event = core.explode_textlist_event(fields["TPs"]) if event.type == "CHG" or event.type == "DCL" then local index = core.get_textlist_index("TPs") - core.setting_set("mainmenu_last_selected_TP", index) + core.settings:set("mainmenu_last_selected_TP", index) local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true)) local current_index = core.get_textlist_index("TPs") if current_index and #list >= current_index then @@ -114,7 +114,7 @@ local function main_button_handler(tabview, fields, name, tabdata) if list[current_index] == fgettext("None") then new_path = "" end - core.setting_set("texture_path", new_path) + core.settings:set("texture_path", new_path) end end return true diff --git a/builtin/mainmenu/textures.lua b/builtin/mainmenu/textures.lua index dadbb093e..9ba4ade7e 100644 --- a/builtin/mainmenu/textures.lua +++ b/builtin/mainmenu/textures.lua @@ -24,7 +24,7 @@ function mm_texture.init() DIR_DELIM .. "pack" .. DIR_DELIM mm_texture.basetexturedir = mm_texture.defaulttexturedir - mm_texture.texturepack = core.setting_get("texture_path") + mm_texture.texturepack = core.settings:get("texture_path") mm_texture.gameid = nil end @@ -61,7 +61,7 @@ function mm_texture.reset() mm_texture.set_generic("header") if not have_bg then - if core.setting_getbool("menu_clouds") then + if core.settings:get_bool("menu_clouds") then core.set_clouds(true) else mm_texture.set_dirt_bg() @@ -88,7 +88,7 @@ function mm_texture.update_game(gamedetails) if not have_bg then - if core.setting_getbool("menu_clouds") then + if core.settings:get_bool("menu_clouds") then core.set_clouds(true) else mm_texture.set_dirt_bg() diff --git a/builtin/profiler/init.lua b/builtin/profiler/init.lua index c1597d280..874950364 100644 --- a/builtin/profiler/init.lua +++ b/builtin/profiler/init.lua @@ -15,10 +15,18 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +local function get_bool_default(name, default) + local val = core.settings:get_bool(name) + if val == nil then + return default + end + return val +end + local profiler_path = core.get_builtin_path()..DIR_DELIM.."profiler"..DIR_DELIM local profiler = {} local sampler = assert(loadfile(profiler_path .. "sampling.lua"))(profiler) -local instrumentation = assert(loadfile(profiler_path .. "instrumentation.lua"))(profiler, sampler) +local instrumentation = assert(loadfile(profiler_path .. "instrumentation.lua"))(profiler, sampler, get_bool_default) local reporter = dofile(profiler_path .. "reporter.lua") profiler.instrument = instrumentation.instrument @@ -27,7 +35,7 @@ profiler.instrument = instrumentation.instrument -- Is called later, after `core.register_chatcommand` was set up. -- function profiler.init_chatcommand() - local instrument_profiler = core.setting_getbool("instrument.profiler") or false + local instrument_profiler = get_bool_default("instrument.profiler", false) if instrument_profiler then instrumentation.init_chatcommand() end diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua index 4311215b2..be3a460e5 100644 --- a/builtin/profiler/instrumentation.lua +++ b/builtin/profiler/instrumentation.lua @@ -17,8 +17,9 @@ local format, pairs, type = string.format, pairs, type local core, get_current_modname = core, core.get_current_modname -local profiler, sampler = ... -local instrument_builtin = core.setting_getbool("instrument.builtin") or false +local profiler, sampler, get_bool_default = ... + +local instrument_builtin = get_bool_default("instrument.builtin", false) local register_functions = { register_globalstep = 0, @@ -137,7 +138,7 @@ local function instrument_register(func, func_name) end local function init_chatcommand() - if core.setting_getbool("instrument.chatcommand") or true then + if get_bool_default("instrument.chatcommand", true) then local orig_register_chatcommand = core.register_chatcommand core.register_chatcommand = function(cmd, def) def.func = instrument { @@ -153,8 +154,7 @@ end -- Start instrumenting selected functions -- local function init() - local is_set = core.setting_getbool - if is_set("instrument.entity") or true then + if get_bool_default("instrument.entity", true) then -- Explicitly declare entity api-methods. -- Simple iteration would ignore lookup via __index. local entity_instrumentation = { @@ -180,7 +180,7 @@ local function init() end end - if is_set("instrument.abm") or true then + if get_bool_default("instrument.abm", true) then -- Wrap register_abm() to automatically instrument abms. local orig_register_abm = core.register_abm core.register_abm = function(spec) @@ -193,7 +193,7 @@ local function init() end end - if is_set("instrument.lbm") or true then + if get_bool_default("instrument.lbm", true) then -- Wrap register_lbm() to automatically instrument lbms. local orig_register_lbm = core.register_lbm core.register_lbm = function(spec) @@ -206,13 +206,13 @@ local function init() end end - if is_set("instrument.global_callback") or true then + if get_bool_default("instrument.global_callback", true) then for func_name, _ in pairs(register_functions) do core[func_name] = instrument_register(core[func_name], func_name) end end - if is_set("instrument.profiler") or false then + if get_bool_default("instrument.profiler", false) then -- Measure overhead of instrumentation, but keep it down for functions -- So keep the `return` for better optimization. profiler.empty_instrument = instrument { diff --git a/builtin/profiler/reporter.lua b/builtin/profiler/reporter.lua index 5b38ed4df..fed47a36b 100644 --- a/builtin/profiler/reporter.lua +++ b/builtin/profiler/reporter.lua @@ -18,7 +18,7 @@ local DIR_DELIM, LINE_DELIM = DIR_DELIM, "\n" local table, unpack, string, pairs, io, os = table, unpack, string, pairs, io, os local rep, sprintf, tonumber = string.rep, string.format, tonumber -local core, setting_get = core, core.setting_get +local core, settings = core, core.settings local reporter = {} --- @@ -229,7 +229,7 @@ end local worldpath = core.get_worldpath() local function get_save_path(format, filter) - local report_path = setting_get("profiler.report_path") or "" + local report_path = settings:get("profiler.report_path") or "" if report_path ~= "" then core.mkdir(sprintf("%s%s%s", worldpath, DIR_DELIM, report_path)) end @@ -249,7 +249,7 @@ end -- function reporter.save(profile, format, filter) if not format or format == "" then - format = setting_get("profiler.default_report_format") or "txt" + format = settings:get("profiler.default_report_format") or "txt" end if filter == "" then filter = nil diff --git a/builtin/profiler/sampling.lua b/builtin/profiler/sampling.lua index 1d1ef256d..4b53399a5 100644 --- a/builtin/profiler/sampling.lua +++ b/builtin/profiler/sampling.lua @@ -185,7 +185,7 @@ end function sampler.init() sampler.reset() - if core.setting_getbool("instrument.profiler") then + if core.settings:get_bool("instrument.profiler") then core.register_globalstep(function() if logged_time == 0 then return diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 1550a78be..166cc024a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -172,8 +172,8 @@ 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. +`minetest.settings` can be used to read custom or existing settings at load +time, if necessary. (See `Settings`) ### `models` Models for entities or meshnodes. @@ -2177,16 +2177,10 @@ Call these functions only at load time! * See `minetest.builtin_auth_handler` in `builtin.lua` for reference ### Setting-related -* `minetest.setting_set(name, value)` - * Setting names can't contain whitespace or any of `="{}#`. - * Setting values can't contain the sequence `\n"""`. - * Setting names starting with "secure." can't be set. -* `minetest.setting_get(name)`: returns string or `nil` -* `minetest.setting_setbool(name, value)` - * See documentation on `setting_set` for restrictions. -* `minetest.setting_getbool(name)`: returns boolean or `nil` -* `minetest.setting_get_pos(name)`: returns position or nil -* `minetest.setting_save()`, returns `nil`, save all settings to config file +* `minetest.settings`: Settings object containing all of the settings from the + main config file (`minetest.conf`). +* `minetest.setting_get_pos(name)`: Loads a setting from the main settings and + parses it as a position (in the format `(1,2,3)`). Returns a position or nil. ### Authentication * `minetest.notify_authentication_modified(name)` @@ -3536,10 +3530,15 @@ It can be created via `Settings(filename)`. * `get(key)`: returns a value * `get_bool(key)`: returns a boolean * `set(key, value)` + * Setting names can't contain whitespace or any of `="{}#`. + * Setting values can't contain the sequence `\n"""`. + * Setting names starting with "secure." can't be set on the main settings object (`minetest.settings`). +* `set_bool(key, value)` + * See documentation for set() above. * `remove(key)`: returns a boolean (`true` for success) * `get_names()`: returns `{key1,...}` * `write()`: returns a boolean (`true` for success) - * write changes to file + * Writes changes to file. * `to_table()`: returns `{[key1]=value1,...}` Mapgen objects diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index 080ae887c..722359066 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -76,14 +76,9 @@ AsyncEngine::~AsyncEngine() } /******************************************************************************/ -bool AsyncEngine::registerFunction(const char* name, lua_CFunction func) +void AsyncEngine::registerStateInitializer(StateInitializer func) { - if (initDone) { - return false; - } - - functionList[name] = func; - return true; + stateInitializers.push_back(func); } /******************************************************************************/ @@ -204,11 +199,9 @@ void AsyncEngine::pushFinishedJobs(lua_State* L) { /******************************************************************************/ void AsyncEngine::prepareEnvironment(lua_State* L, int top) { - for (UNORDERED_MAP::iterator it = functionList.begin(); - it != functionList.end(); ++it) { - lua_pushstring(L, it->first.c_str()); - lua_pushcfunction(L, it->second); - lua_settable(L, top); + for (std::vector::iterator it = stateInitializers.begin(); + it != stateInitializers.end(); it++) { + (*it)(L, top); } } diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 93e9759b4..dbe0654e2 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -75,16 +75,16 @@ private: // Asynchornous thread and job management class AsyncEngine { friend class AsyncWorkerThread; + typedef void (*StateInitializer)(lua_State *L, int top); public: AsyncEngine(); ~AsyncEngine(); /** - * Register function to be used within engine - * @param name Function name to be used within Lua environment + * Register function to be called on new states * @param func C function to be called */ - bool registerFunction(const char* name, lua_CFunction func); + void registerStateInitializer(StateInitializer func); /** * Create async engine tasks and lock function registration @@ -140,8 +140,8 @@ private: // Variable locking the engine against further modification bool initDone; - // Internal store for registred functions - UNORDERED_MAP functionList; + // Internal store for registred state initializers + std::vector stateInitializers; // Internal counter to create job IDs unsigned int jobIdCounter; diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index dfe743b72..5d7ba9640 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -74,17 +74,13 @@ std::string ModApiBase::getCurrentModPath(lua_State *L) } -bool ModApiBase::registerFunction( - lua_State *L, - const char *name, - lua_CFunction fct, - int top) +bool ModApiBase::registerFunction(lua_State *L, const char *name, + lua_CFunction func, int top) { - //TODO check presence first! + // TODO: Check presence first! - lua_pushstring(L,name); - lua_pushcfunction(L,fct); - lua_settable(L, top); + lua_pushcfunction(L, func); + lua_setfield(L, top, name); return true; } diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h index cd382629d..af89afd93 100644 --- a/src/script/lua_api/l_base.h +++ b/src/script/lua_api/l_base.h @@ -68,9 +68,8 @@ public: static bool registerFunction(lua_State *L, const char* name, - lua_CFunction fct, - int top - ); + lua_CFunction func, + int top); }; #endif /* L_BASE_H_ */ diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index b7627619e..e9b689931 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -31,8 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define luamethod(class, name) {#name, class::l_##name} #define luamethod_aliased(class, name, alias) {#name, class::l_##name}, {#alias, class::l_##name} -#define API_FCT(name) registerFunction(L, #name, l_##name,top) -#define ASYNC_API_FCT(name) engine.registerFunction(#name, l_##name) +#define API_FCT(name) registerFunction(L, #name, l_##name, top) #define MAP_LOCK_REQUIRED #define NO_MAP_LOCK_REQUIRED diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 4d0be257c..dc8654960 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include + /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, std::string name) { @@ -1141,23 +1142,24 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) } /******************************************************************************/ -void ModApiMainMenu::InitializeAsync(AsyncEngine& engine) +void ModApiMainMenu::InitializeAsync(lua_State *L, int top) { - ASYNC_API_FCT(get_worlds); - ASYNC_API_FCT(get_games); - ASYNC_API_FCT(get_favorites); - ASYNC_API_FCT(get_mapgen_names); - ASYNC_API_FCT(get_modpath); - ASYNC_API_FCT(get_gamepath); - ASYNC_API_FCT(get_texturepath); - ASYNC_API_FCT(get_texturepath_share); - ASYNC_API_FCT(create_dir); - ASYNC_API_FCT(delete_dir); - ASYNC_API_FCT(copy_dir); - //ASYNC_API_FCT(extract_zip); //TODO remove dependency to GuiEngine - ASYNC_API_FCT(download_file); - ASYNC_API_FCT(get_modstore_details); - ASYNC_API_FCT(get_modstore_list); - //ASYNC_API_FCT(gettext); (gettext lib isn't threadsafe) + API_FCT(get_worlds); + API_FCT(get_games); + API_FCT(get_favorites); + API_FCT(get_mapgen_names); + API_FCT(get_modpath); + API_FCT(get_gamepath); + API_FCT(get_texturepath); + API_FCT(get_texturepath_share); + API_FCT(create_dir); + API_FCT(delete_dir); + API_FCT(copy_dir); + //API_FCT(extract_zip); //TODO remove dependency to GuiEngine + API_FCT(download_file); + API_FCT(get_modstore_details); + API_FCT(get_modstore_list); + //API_FCT(gettext); (gettext lib isn't threadsafe) } + diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index e31ac0112..d4946bab1 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -142,6 +142,7 @@ private: static int l_do_async_callback(lua_State *L); public: + /** * initialize this API module * @param L lua stack to initialize @@ -149,7 +150,7 @@ public: */ static void Initialize(lua_State *L, int top); - static void InitializeAsync(AsyncEngine& engine); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 809f7d115..70807f3d2 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -23,6 +23,47 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "log.h" + +#define SET_SECURITY_CHECK(L, name) \ + if (o->m_settings == g_settings && ScriptApiSecurity::isSecure(L) && \ + name.compare(0, 7, "secure.") == 0) { \ + throw LuaError("Attempt to set secure setting."); \ + } + +LuaSettings::LuaSettings(Settings *settings, const std::string &filename) : + m_settings(settings), + m_filename(filename), + m_is_own_settings(false), + m_write_allowed(true) +{ +} + +LuaSettings::LuaSettings(const std::string &filename, bool write_allowed) : + m_filename(filename), + m_is_own_settings(true), + m_write_allowed(write_allowed) +{ + m_settings = new Settings(); + m_settings->readConfigFile(filename.c_str()); +} + +LuaSettings::~LuaSettings() +{ + if (m_is_own_settings) + delete m_settings; +} + + +void LuaSettings::create(lua_State *L, Settings *settings, + const std::string &filename) +{ + LuaSettings *o = new LuaSettings(settings, filename); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + + // garbage collector int LuaSettings::gc_object(lua_State* L) { @@ -31,6 +72,7 @@ int LuaSettings::gc_object(lua_State* L) return 0; } + // get(self, key) -> value int LuaSettings::l_get(lua_State* L) { @@ -74,12 +116,30 @@ int LuaSettings::l_set(lua_State* L) std::string key = std::string(luaL_checkstring(L, 2)); const char* value = luaL_checkstring(L, 3); + SET_SECURITY_CHECK(L, key); + if (!o->m_settings->set(key, value)) throw LuaError("Invalid sequence found in setting parameters"); return 0; } +// set_bool(self, key, value) +int LuaSettings::l_set_bool(lua_State* L) +{ + NO_MAP_LOCK_REQUIRED; + LuaSettings* o = checkobject(L, 1); + + std::string key = std::string(luaL_checkstring(L, 2)); + bool value = lua_toboolean(L, 3); + + SET_SECURITY_CHECK(L, key); + + o->m_settings->setBool(key, value); + + return 1; +} + // remove(self, key) -> success int LuaSettings::l_remove(lua_State* L) { @@ -88,6 +148,8 @@ int LuaSettings::l_remove(lua_State* L) std::string key = std::string(luaL_checkstring(L, 2)); + SET_SECURITY_CHECK(L, key); + bool success = o->m_settings->remove(key); lua_pushboolean(L, success); @@ -147,19 +209,6 @@ int LuaSettings::l_to_table(lua_State* L) return 1; } -LuaSettings::LuaSettings(const char* filename, bool write_allowed) -{ - m_write_allowed = write_allowed; - m_filename = std::string(filename); - - m_settings = new Settings(); - m_settings->readConfigFile(m_filename.c_str()); -} - -LuaSettings::~LuaSettings() -{ - delete m_settings; -} void LuaSettings::Register(lua_State* L) { @@ -190,7 +239,7 @@ void LuaSettings::Register(lua_State* L) } // LuaSettings(filename) -// Creates an LuaSettings and leaves it on top of stack +// Creates a LuaSettings and leaves it on top of the stack int LuaSettings::create_object(lua_State* L) { NO_MAP_LOCK_REQUIRED; @@ -209,8 +258,9 @@ LuaSettings* LuaSettings::checkobject(lua_State* L, int narg) NO_MAP_LOCK_REQUIRED; luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); - if(!ud) luaL_typerror(L, narg, className); - return *(LuaSettings**)ud; // unbox pointer + if (!ud) + luaL_typerror(L, narg, className); + return *(LuaSettings**) ud; // unbox pointer } const char LuaSettings::className[] = "Settings"; @@ -218,6 +268,7 @@ const luaL_Reg LuaSettings::methods[] = { luamethod(LuaSettings, get), luamethod(LuaSettings, get_bool), luamethod(LuaSettings, set), + luamethod(LuaSettings, set_bool), luamethod(LuaSettings, remove), luamethod(LuaSettings, get_names), luamethod(LuaSettings, write), diff --git a/src/script/lua_api/l_settings.h b/src/script/lua_api/l_settings.h index b90f0a8f2..f3bc7410b 100644 --- a/src/script/lua_api/l_settings.h +++ b/src/script/lua_api/l_settings.h @@ -42,6 +42,9 @@ private: // set(self, key, value) static int l_set(lua_State *L); + // set_bool(self, key, value) + static int l_set_bool(lua_State* L); + // remove(self, key) -> success static int l_remove(lua_State *L); @@ -54,16 +57,20 @@ private: // to_table(self) -> {[key1]=value1,...} static int l_to_table(lua_State *L); - bool m_write_allowed; Settings *m_settings; std::string m_filename; + bool m_is_own_settings; + bool m_write_allowed; public: - LuaSettings(const char *filename, bool write_allowed); + LuaSettings(Settings *settings, const std::string &filename); + LuaSettings(const std::string &filename, bool write_allowed); ~LuaSettings(); + static void create(lua_State *L, Settings *settings, const std::string &filename); + // LuaSettings(filename) - // Creates an LuaSettings and leaves it on top of stack + // Creates a LuaSettings and leaves it on top of the stack static int create_object(lua_State *L); static LuaSettings *checkobject(lua_State *L, int narg); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 809a2eb68..c4a988e07 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_util.h" #include "lua_api/l_internal.h" +#include "lua_api/l_settings.h" #include "common/c_converter.h" #include "common/c_content.h" #include "cpp_api/s_async.h" @@ -77,71 +78,6 @@ int ModApiUtil::l_get_us_time(lua_State *L) return 1; } -#define CHECK_SECURE_SETTING(L, name) \ - if (ScriptApiSecurity::isSecure(L) && \ - name.compare(0, 7, "secure.") == 0) { \ - throw LuaError("Attempt to set secure setting."); \ - } - -// setting_set(name, value) -int ModApiUtil::l_setting_set(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - std::string name = luaL_checkstring(L, 1); - std::string value = luaL_checkstring(L, 2); - CHECK_SECURE_SETTING(L, name); - g_settings->set(name, value); - return 0; -} - -// setting_get(name) -int ModApiUtil::l_setting_get(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - const char *name = luaL_checkstring(L, 1); - try{ - std::string value = g_settings->get(name); - lua_pushstring(L, value.c_str()); - } catch(SettingNotFoundException &e){ - lua_pushnil(L); - } - return 1; -} - -// setting_setbool(name) -int ModApiUtil::l_setting_setbool(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - std::string name = luaL_checkstring(L, 1); - bool value = lua_toboolean(L, 2); - CHECK_SECURE_SETTING(L, name); - g_settings->setBool(name, value); - return 0; -} - -// setting_getbool(name) -int ModApiUtil::l_setting_getbool(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - const char *name = luaL_checkstring(L, 1); - try{ - bool value = g_settings->getBool(name); - lua_pushboolean(L, value); - } catch(SettingNotFoundException &e){ - lua_pushnil(L); - } - return 1; -} - -// setting_save() -int ModApiUtil::l_setting_save(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - if(g_settings_path != "") - g_settings->updateConfigFile(g_settings_path.c_str()); - return 0; -} - // parse_json(str[, nullvalue]) int ModApiUtil::l_parse_json(lua_State *L) { @@ -493,12 +429,6 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(get_us_time); - API_FCT(setting_set); - API_FCT(setting_get); - API_FCT(setting_setbool); - API_FCT(setting_getbool); - API_FCT(setting_save); - API_FCT(parse_json); API_FCT(write_json); @@ -524,6 +454,9 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(decode_base64); API_FCT(get_version); + + LuaSettings::create(L, g_settings, g_settings_path); + lua_setfield(L, top, "settings"); } void ModApiUtil::InitializeClient(lua_State *L, int top) @@ -548,34 +481,31 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(get_version); } -void ModApiUtil::InitializeAsync(AsyncEngine& engine) +void ModApiUtil::InitializeAsync(lua_State *L, int top) { - ASYNC_API_FCT(log); + API_FCT(log); - ASYNC_API_FCT(get_us_time); + API_FCT(get_us_time); - //ASYNC_API_FCT(setting_set); - ASYNC_API_FCT(setting_get); - //ASYNC_API_FCT(setting_setbool); - ASYNC_API_FCT(setting_getbool); - //ASYNC_API_FCT(setting_save); + API_FCT(parse_json); + API_FCT(write_json); - ASYNC_API_FCT(parse_json); - ASYNC_API_FCT(write_json); + API_FCT(is_yes); - ASYNC_API_FCT(is_yes); + API_FCT(get_builtin_path); - ASYNC_API_FCT(get_builtin_path); + API_FCT(compress); + API_FCT(decompress); - ASYNC_API_FCT(compress); - ASYNC_API_FCT(decompress); + API_FCT(mkdir); + API_FCT(get_dir_list); - ASYNC_API_FCT(mkdir); - ASYNC_API_FCT(get_dir_list); + API_FCT(encode_base64); + API_FCT(decode_base64); - ASYNC_API_FCT(encode_base64); - ASYNC_API_FCT(decode_base64); + API_FCT(get_version); - ASYNC_API_FCT(get_version); + LuaSettings::create(L, g_settings, g_settings_path); + lua_setfield(L, top, "settings"); } diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 7325a841a..c36c054e6 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -45,21 +45,6 @@ private: // get us precision time static int l_get_us_time(lua_State *L); - // setting_set(name, value) - static int l_setting_set(lua_State *L); - - // setting_get(name) - static int l_setting_get(lua_State *L); - - // setting_setbool(name, value) - static int l_setting_setbool(lua_State *L); - - // setting_getbool(name) - static int l_setting_getbool(lua_State *L); - - // setting_save() - static int l_setting_save(lua_State *L); - // parse_json(str[, nullvalue]) static int l_parse_json(lua_State *L); @@ -109,8 +94,9 @@ private: static int l_get_version(lua_State *L); public: - static void Initialize(lua_State *L, int top); + static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); static void InitializeAsync(AsyncEngine &engine); diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index b5a5085be..24f70b8c1 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -62,17 +62,17 @@ ClientScripting::ClientScripting(Client *client): void ClientScripting::InitializeModApi(lua_State *L, int top) { - ModApiUtil::InitializeClient(L, top); - ModApiClient::Initialize(L, top); - ModApiStorage::Initialize(L, top); - ModApiEnvMod::InitializeClient(L, top); - LuaItemStack::Register(L); StorageRef::Register(L); LuaMinimap::Register(L); NodeMetaRef::RegisterClient(L); LuaLocalPlayer::Register(L); LuaCamera::Register(L); + + ModApiUtil::InitializeClient(L, top); + ModApiClient::Initialize(L, top); + ModApiStorage::Initialize(L, top); + ModApiEnvMod::InitializeClient(L, top); } void ClientScripting::on_client_ready(LocalPlayer *localplayer) diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index d79864a95..48957b472 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -59,23 +59,28 @@ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine) /******************************************************************************/ void MainMenuScripting::initializeModApi(lua_State *L, int top) { + registerLuaClasses(L, top); + // Initialize mod API modules ModApiMainMenu::Initialize(L, top); ModApiUtil::Initialize(L, top); ModApiSound::Initialize(L, top); - // Register reference classes (userdata) - LuaSettings::Register(L); - - // Register functions to async environment - ModApiMainMenu::InitializeAsync(asyncEngine); - ModApiUtil::InitializeAsync(asyncEngine); + asyncEngine.registerStateInitializer(registerLuaClasses); + asyncEngine.registerStateInitializer(ModApiMainMenu::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiUtil::InitializeAsync); // Initialize async environment //TODO possibly make number of async threads configurable asyncEngine.initialize(MAINMENU_NUM_ASYNC_THREADS); } +/******************************************************************************/ +void MainMenuScripting::registerLuaClasses(lua_State *L, int top) +{ + LuaSettings::Register(L); +} + /******************************************************************************/ void MainMenuScripting::step() { diff --git a/src/script/scripting_mainmenu.h b/src/script/scripting_mainmenu.h index 9aacd55d6..7b3a6eba8 100644 --- a/src/script/scripting_mainmenu.h +++ b/src/script/scripting_mainmenu.h @@ -44,6 +44,7 @@ public: const std::string &serialized_params); private: void initializeModApi(lua_State *L, int top); + static void registerLuaClasses(lua_State *L, int top); AsyncEngine asyncEngine; DISABLE_CLASS_COPY(MainMenuScripting); diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index ce56fcf19..cd01b0773 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -82,19 +82,6 @@ ServerScripting::ServerScripting(Server* server) 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); @@ -111,6 +98,19 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ObjectRef::Register(L); LuaSettings::Register(L); StorageRef::Register(L); + + // 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); } void log_deprecated(const std::string &message) -- cgit v1.2.3 From b6f4a9c7e1a4f0bac66fd6f6ff844425ac775975 Mon Sep 17 00:00:00 2001 From: TeTpaAka Date: Fri, 29 May 2015 20:30:55 +0200 Subject: Make the player collisionbox settable --- doc/lua_api.txt | 3 ++- src/content_cao.cpp | 6 ++++++ src/content_sao.cpp | 5 ++++- src/localplayer.h | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 166cc024a..1f8abd70c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3705,7 +3705,8 @@ Definition tables collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, visual = "cube"/"sprite"/"upright_sprite"/"mesh"/"wielditem", visual_size = {x=1, y=1}, - mesh = "model", + mesh = "model", -- for players (0, -1, 0) is ground level, + -- for all other entities (0, 0, 0) is ground level. textures = {}, -- number of required textures depends on visual colors = {}, -- number of required colors depends on visual spritediv = {x=1, y=1}, diff --git a/src/content_cao.cpp b/src/content_cao.cpp index c5cb1e21a..d18a0233f 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -1585,7 +1585,13 @@ void GenericCAO::processMessage(const std::string &data) } if (m_is_local_player) { LocalPlayer *player = m_env->getLocalPlayer(); + player->makes_footstep_sound = m_prop.makes_footstep_sound; + + aabb3f collisionbox = m_selection_box; + collisionbox.MinEdge += v3f(0, BS, 0); + collisionbox.MaxEdge += v3f(0, BS, 0); + player->setCollisionbox(collisionbox); } if ((m_is_player && !m_is_local_player) && m_prop.nametag == "") diff --git a/src/content_sao.cpp b/src/content_sao.cpp index d59f97276..20b0396cd 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -1429,7 +1429,10 @@ bool PlayerSAO::checkMovementCheat() bool PlayerSAO::getCollisionBox(aabb3f *toset) const { - *toset = aabb3f(-BS * 0.30, 0.0, -BS * 0.30, BS * 0.30, BS * 1.75, BS * 0.30); + //update collision box + toset->MinEdge = m_prop.collisionbox.MinEdge * BS + v3f(0, BS, 0); + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS + v3f(0, BS, 0); + toset->MinEdge += m_base_position; toset->MaxEdge += m_base_position; return true; diff --git a/src/localplayer.h b/src/localplayer.h index 9cbefae23..efea8bb86 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -137,6 +137,8 @@ public: v3f getEyePosition() const { return m_position + getEyeOffset(); } v3f getEyeOffset() const; + void setCollisionbox(aabb3f box) { m_collisionbox = box; } + private: void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); void accelerateVertical(const v3f &target_speed, const f32 max_increase); -- cgit v1.2.3 From 2d5bd7f414f8b8107254490af2360d6e29f1a8d2 Mon Sep 17 00:00:00 2001 From: paramat Date: Sun, 7 May 2017 01:48:42 +0100 Subject: Player properties: Set correct default collisionbox Recent commit b6f4a9c7e1a4f0bac66fd6f6ff844425ac775975 removed a hardcoded player collisionbox which resulted on falling back to an incorrect default. This stopped players walking through 2-node high spaces and made the player slightly wider. Improve docs for custom player collisionbox feature and reformat nearby lines. --- doc/lua_api.txt | 24 ++++++++++++++---------- src/content_sao.cpp | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 1f8abd70c..607a13fdd 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3700,25 +3700,29 @@ Definition tables { hp_max = 1, physical = true, - collide_with_objects = true, -- collide with other objects if physical=true + collide_with_objects = true, -- collide with other objects if physical = true weight = 5, - collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, - visual = "cube"/"sprite"/"upright_sprite"/"mesh"/"wielditem", - visual_size = {x=1, y=1}, - mesh = "model", -- for players (0, -1, 0) is ground level, - -- for all other entities (0, 0, 0) is ground level. + collisionbox = {-0.5, 0.0, -0.5, 0.5, 1.0, 0.5}, + -- ^ For players (0, -1, 0) is at object base level, + -- for all other objects (0, 0, 0) is at object base level. + -- For example, Minetest Game player box is (-0.3, -1.0, -0.3, 0.3, 0.75, 0.3). + visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem", + visual_size = {x = 1, y = 1}, + mesh = "model", textures = {}, -- number of required textures depends on visual colors = {}, -- number of required colors depends on visual - spritediv = {x=1, y=1}, - initial_sprite_basepos = {x=0, y=0}, + spritediv = {x = 1, y = 1}, + initial_sprite_basepos = {x = 0, y = 0}, is_visible = true, makes_footstep_sound = false, automatic_rotate = false, stepheight = 0, automatic_face_movement_dir = 0.0, - -- ^ automatically set yaw to movement direction; offset in degrees; false to disable + -- ^ Automatically set yaw to movement direction, offset in degrees, + -- 'false' to disable. automatic_face_movement_max_rotation_per_sec = -1, - -- ^ limit automatic rotation to this value in degrees per second. values < 0 no limit + -- ^ Limit automatic rotation to this value in degrees per second, + -- value < 0 no limit. backface_culling = true, -- false to disable backface_culling for model nametag = "", -- by default empty, for players their name is shown if empty nametag_color = , -- sets color of nametag as ColorSpec diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 20b0396cd..f1a4df056 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -798,7 +798,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id m_prop.hp_max = PLAYER_MAX_HP; m_prop.physical = false; m_prop.weight = PLAYER_DEFAULT_WEIGHT; - m_prop.collisionbox = aabb3f(-1/3.,-1.0,-1/3., 1/3.,1.0,1/3.); + m_prop.collisionbox = aabb3f(-0.3f, -1.0f, -0.3f, 0.3f, 0.75f, 0.3f); // start of default appearance, this should be overwritten by LUA m_prop.visual = "upright_sprite"; m_prop.visual_size = v2f(1, 2); -- cgit v1.2.3 From c1b3ed4180dea16e2fa77663a7d2bf155595dd60 Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 7 May 2017 12:13:15 +0200 Subject: Player attrs: permits to remove an attribute by setting value to nil (#5716) * Player attrs: permits to remove an attribute by setting value to nil When doing player:set_attribute("attr", nil) remove attribute Also remove a useless check on C++ API part (already done by checkplayer) Fix #5709 --- doc/lua_api.txt | 4 +++- games/minimal/mods/default/init.lua | 22 ++++++++++++---------- src/content_sao.h | 10 ++++++++++ src/script/lua_api/l_object.cpp | 7 ++++--- 4 files changed, 29 insertions(+), 14 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 607a13fdd..d8e297f4c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3028,7 +3028,9 @@ This is basically a reference to a C++ `ServerActiveObject` * `0`: player is drowning, * `1`-`10`: remaining number of bubbles * `11`: bubbles bar is not shown -* `set_attribute(attribute, value)`: sets an extra attribute with value on player +* `set_attribute(attribute, value)`: + * Sets an extra attribute with value on player. + * If value is nil, remove attribute from player. * `get_attribute(attribute)`: returns value for extra attribute. Returns nil if no attribute found. * `set_inventory_formspec(formspec)` * Redefine player's inventory form diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua index d36e225a3..64970f922 100644 --- a/games/minimal/mods/default/init.lua +++ b/games/minimal/mods/default/init.lua @@ -18,6 +18,8 @@ dofile(minetest.get_modpath("default").."/mapgen.lua") minetest.register_on_joinplayer(function(player) local cb = function(player) minetest.chat_send_player(player:get_player_name(), "This is the [minimal] \"Minimal Development Test\" game. Use [minetest_game] for the real thing.") + player:set_attribute("test_attribute", "test_me") + player:set_attribute("remove_this", nil) end minetest.after(2.0, cb, player) end) @@ -1387,13 +1389,13 @@ minetest.register_abm({ local srclist = inv:get_list("src") local cooked = nil - + if srclist then cooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist}) end - + local was_active = false - + if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then was_active = true meta:set_float("fuel_time", meta:get_float("fuel_time") + 1) @@ -1413,7 +1415,7 @@ minetest.register_abm({ meta:set_string("src_time", 0) end end - + if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then local percent = math.floor(meta:get_float("fuel_time") / meta:get_float("fuel_totaltime") * 100) @@ -1438,7 +1440,7 @@ minetest.register_abm({ local cooked = nil local fuellist = inv:get_list("fuel") local srclist = inv:get_list("src") - + if srclist then cooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist}) end @@ -1464,7 +1466,7 @@ minetest.register_abm({ meta:set_string("fuel_totaltime", fuel.time) meta:set_string("fuel_time", 0) - + local stack = inv:get_stack("fuel", 1) stack:take_item() inv:set_stack("fuel", 1, stack) @@ -1571,7 +1573,7 @@ function default.grow_tree(data, a, pos, is_apple_tree, seed) y = y+th-1 -- (x, y, z) is now last piece of trunk local leaves_a = VoxelArea:new{MinEdge={x=-2, y=-1, z=-2}, MaxEdge={x=2, y=2, z=2}} local leaves_buffer = {} - + -- Force leaves near the trunk local d = 1 for xi = -d, d do @@ -1581,14 +1583,14 @@ function default.grow_tree(data, a, pos, is_apple_tree, seed) end end end - + -- Add leaves randomly for iii = 1, 8 do local d = 1 local xx = pr:next(leaves_a.MinEdge.x, leaves_a.MaxEdge.x - d) local yy = pr:next(leaves_a.MinEdge.y, leaves_a.MaxEdge.y - d) local zz = pr:next(leaves_a.MinEdge.z, leaves_a.MaxEdge.z - d) - + for xi = 0, d do for yi = 0, d do for zi = 0, d do @@ -1597,7 +1599,7 @@ function default.grow_tree(data, a, pos, is_apple_tree, seed) end end end - + -- Add the leaves for xi = leaves_a.MinEdge.x, leaves_a.MaxEdge.x do for yi = leaves_a.MinEdge.y, leaves_a.MaxEdge.y do diff --git a/src/content_sao.h b/src/content_sao.h index e08795579..0dad54805 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -277,6 +277,16 @@ public: return true; } + inline void removeExtendedAttribute(const std::string &attr) + { + PlayerAttributes::iterator it = m_extra_attributes.find(attr); + if (it == m_extra_attributes.end()) + return; + + m_extra_attributes.erase(it); + m_extended_attributes_modified = true; + } + inline const PlayerAttributes &getExtendedAttributes() { return m_extra_attributes; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 6f61ab55c..c7a31d048 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1202,9 +1202,10 @@ int ObjectRef::l_set_attribute(lua_State *L) } std::string attr = luaL_checkstring(L, 2); - std::string value = luaL_checkstring(L, 3); - - if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + if (lua_isnil(L, 3)) { + co->removeExtendedAttribute(attr); + } else { + std::string value = luaL_checkstring(L, 3); co->setExtendedAttribute(attr, value); } return 1; -- cgit v1.2.3 From da88a186766020762f5c86bc6ba1624e4feaae2c Mon Sep 17 00:00:00 2001 From: paramat Date: Tue, 9 May 2017 01:59:02 +0100 Subject: Revert custom player collision box and step height commits These caused inability to pass through 2 node high spaces or step up onto slabs or steps when a new client connected to an older server. --- doc/lua_api.txt | 3 --- src/constants.h | 6 ------ src/content_cao.cpp | 6 ------ src/content_cao.h | 6 ------ src/content_sao.cpp | 7 ++----- src/localplayer.cpp | 4 ++-- src/localplayer.h | 2 -- 7 files changed, 4 insertions(+), 30 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index d8e297f4c..901dd3c46 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3705,9 +3705,6 @@ Definition tables collide_with_objects = true, -- collide with other objects if physical = true weight = 5, collisionbox = {-0.5, 0.0, -0.5, 0.5, 1.0, 0.5}, - -- ^ For players (0, -1, 0) is at object base level, - -- for all other objects (0, 0, 0) is at object base level. - -- For example, Minetest Game player box is (-0.3, -1.0, -0.3, 0.3, 0.75, 0.3). visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem", visual_size = {x = 1, y = 1}, mesh = "model", diff --git a/src/constants.h b/src/constants.h index 4079d0d8f..fb9e97cb3 100644 --- a/src/constants.h +++ b/src/constants.h @@ -90,12 +90,6 @@ with this program; if not, write to the Free Software Foundation, Inc., // Maximum hit points of a player #define PLAYER_MAX_HP 20 -// Player weight -#define PLAYER_DEFAULT_WEIGHT 75 - -// Player step height -#define PLAYER_DEFAULT_STEPHEIGHT 0.6f - // Maximal breath of a player #define PLAYER_MAX_BREATH 11 diff --git a/src/content_cao.cpp b/src/content_cao.cpp index d0d3eb84f..4dde2bb7b 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -1586,13 +1586,7 @@ void GenericCAO::processMessage(const std::string &data) } if (m_is_local_player) { LocalPlayer *player = m_env->getLocalPlayer(); - player->makes_footstep_sound = m_prop.makes_footstep_sound; - - aabb3f collisionbox = m_selection_box; - collisionbox.MinEdge += v3f(0, BS, 0); - collisionbox.MaxEdge += v3f(0, BS, 0); - player->setCollisionbox(collisionbox); } if ((m_is_player && !m_is_local_player) && m_prop.nametag == "") diff --git a/src/content_cao.h b/src/content_cao.h index dde1dfe6f..3be753529 100644 --- a/src/content_cao.h +++ b/src/content_cao.h @@ -154,12 +154,6 @@ public: scene::IBillboardSceneNode *getSpriteSceneNode(); - - inline f32 getStepheight() const - { - return m_prop.stepheight; - } - inline bool isPlayer() const { return m_is_player; diff --git a/src/content_sao.cpp b/src/content_sao.cpp index f1a4df056..f435fe938 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -797,7 +797,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id m_prop.hp_max = PLAYER_MAX_HP; m_prop.physical = false; - m_prop.weight = PLAYER_DEFAULT_WEIGHT; + m_prop.weight = 75; m_prop.collisionbox = aabb3f(-0.3f, -1.0f, -0.3f, 0.3f, 0.75f, 0.3f); // start of default appearance, this should be overwritten by LUA m_prop.visual = "upright_sprite"; @@ -811,7 +811,6 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id // end of default appearance m_prop.is_visible = true; m_prop.makes_footstep_sound = true; - m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT; m_hp = PLAYER_MAX_HP; } @@ -1429,9 +1428,7 @@ bool PlayerSAO::checkMovementCheat() bool PlayerSAO::getCollisionBox(aabb3f *toset) const { - //update collision box - toset->MinEdge = m_prop.collisionbox.MinEdge * BS + v3f(0, BS, 0); - toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS + v3f(0, BS, 0); + *toset = aabb3f(-0.3f * BS, 0.0f, -0.3f * BS, 0.3f * BS, 1.75f * BS, 0.3f * BS); toset->MinEdge += m_base_position; toset->MaxEdge += m_base_position; diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 20892dee6..b587f7bbb 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -344,8 +344,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, } } - float player_stepheight = (m_cao == 0) ? 0.0f : - ((touching_ground) ? m_cao->getStepheight() : (0.2f * BS)); + // TODO: this shouldn't be hardcoded but transmitted from server + float player_stepheight = (touching_ground) ? (BS * 0.6f) : (BS * 0.2f); #ifdef __ANDROID__ player_stepheight += (0.6f * BS); diff --git a/src/localplayer.h b/src/localplayer.h index efea8bb86..9cbefae23 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -137,8 +137,6 @@ public: v3f getEyePosition() const { return m_position + getEyeOffset(); } v3f getEyeOffset() const; - void setCollisionbox(aabb3f box) { m_collisionbox = box; } - private: void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); void accelerateVertical(const v3f &target_speed, const f32 max_increase); -- cgit v1.2.3 From 071e114ffa945522a7a9acc3259427166992d5ee Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 10 May 2017 15:29:21 +0200 Subject: Private nodemeta (#5702) * Private node metadata that isn't sent to the client --- doc/lua_api.txt | 4 ++ games/minimal/mods/default/init.lua | 2 + games/minimal/mods/experimental/init.lua | 10 ++++- src/mapblock.cpp | 7 ++-- src/nodemetadata.cpp | 63 +++++++++++++++++++++++++------- src/nodemetadata.h | 16 ++++++-- src/rollback_interface.cpp | 4 +- src/script/lua_api/l_nodemeta.cpp | 27 ++++++++++++++ src/script/lua_api/l_nodemeta.h | 3 ++ src/serialization.h | 5 ++- 10 files changed, 114 insertions(+), 27 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 901dd3c46..a295d7d0e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2895,6 +2895,10 @@ Can be obtained via `minetest.get_meta(pos)`. #### Methods * All methods in MetaDataRef * `get_inventory()`: returns `InvRef` +* `mark_as_private(name or {name1, name2, ...})`: Mark specific vars as private + This will prevent them from being sent to the client. Note that the "private" + status will only be remembered if an associated key-value pair exists, meaning + it's best to call this when initializing all other meta (e.g. on_construct). ### `ItemStackMetaRef` ItemStack metadata: reference extra data and functionality stored in a stack. diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua index 64970f922..7d26f38a0 100644 --- a/games/minimal/mods/default/init.lua +++ b/games/minimal/mods/default/init.lua @@ -1228,6 +1228,8 @@ minetest.register_node("default:chest_locked", { meta:set_string("owner", "") local inv = meta:get_inventory() inv:set_size("main", 8*4) + -- this is not really the intended usage but works for testing purposes: + meta:mark_as_private("owner") end, can_dig = function(pos,player) local meta = minetest.get_meta(pos); diff --git a/games/minimal/mods/experimental/init.lua b/games/minimal/mods/experimental/init.lua index 5e98e1a80..6e0fb1738 100644 --- a/games/minimal/mods/experimental/init.lua +++ b/games/minimal/mods/experimental/init.lua @@ -502,10 +502,16 @@ minetest.register_craftitem("experimental:tester_tool_1", { on_use = function(itemstack, user, pointed_thing) --print(dump(pointed_thing)) if pointed_thing.type == "node" then - if minetest.get_node(pointed_thing.under).name == "experimental:tester_node_1" then + local node = minetest.get_node(pointed_thing.under) + if node.name == "experimental:tester_node_1" or node.name == "default:chest" then local p = pointed_thing.under minetest.log("action", "Tester tool used at "..minetest.pos_to_string(p)) - minetest.dig_node(p) + if node.name == "experimental:tester_node_1" then + minetest.dig_node(p) + else + minetest.get_meta(p):mark_as_private({"infotext", "formspec"}) + minetest.chat_send_player(user:get_player_name(), "Verify that chest is unusable now.") + end else local p = pointed_thing.above minetest.log("action", "Tester tool used at "..minetest.pos_to_string(p)) diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 1a0b01f2b..ec10a49bb 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -611,7 +611,7 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) Node metadata */ std::ostringstream oss(std::ios_base::binary); - m_node_metadata.serialize(oss); + m_node_metadata.serialize(oss, version, disk); compressZlib(oss.str(), os); /* @@ -669,11 +669,10 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) u8 flags = readU8(is); is_underground = (flags & 0x01) ? true : false; m_day_night_differs = (flags & 0x02) ? true : false; - if (version < 27) { + if (version < 27) m_lighting_complete = 0xFFFF; - } else { + else m_lighting_complete = readU16(is); - } m_generated = (flags & 0x08) ? false : true; /* diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 9b60cf33e..0e8195c34 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventory.h" #include "log.h" #include "util/serialize.h" +#include "util/basic_macros.h" #include "constants.h" // MAP_BLOCKSIZE #include @@ -39,28 +40,38 @@ NodeMetadata::~NodeMetadata() delete m_inventory; } -void NodeMetadata::serialize(std::ostream &os) const +void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk) const { - int num_vars = m_stringvars.size(); + int num_vars = disk ? m_stringvars.size() : countNonPrivate(); writeU32(os, num_vars); for (StringMap::const_iterator it = m_stringvars.begin(); it != m_stringvars.end(); ++it) { + bool priv = isPrivate(it->first); + if (!disk && priv) + continue; + os << serializeString(it->first); os << serializeLongString(it->second); + if (version >= 2) + writeU8(os, (priv) ? 1 : 0); } m_inventory->serialize(os); } -void NodeMetadata::deSerialize(std::istream &is) +void NodeMetadata::deSerialize(std::istream &is, u8 version) { - m_stringvars.clear(); + clear(); int num_vars = readU32(is); for(int i=0; i= 2) { + if (readU8(is) == 1) + markPrivate(name, true); + } } m_inventory->deSerialize(is); @@ -69,6 +80,7 @@ void NodeMetadata::deSerialize(std::istream &is) void NodeMetadata::clear() { Metadata::clear(); + m_privatevars.clear(); m_inventory->clear(); } @@ -77,11 +89,34 @@ bool NodeMetadata::empty() const return Metadata::empty() && m_inventory->getLists().size() == 0; } + +void NodeMetadata::markPrivate(const std::string &name, bool set) +{ + if (set) + m_privatevars.insert(name); + else + m_privatevars.erase(name); +} + +int NodeMetadata::countNonPrivate() const +{ + // m_privatevars can contain names not actually present + // DON'T: return m_stringvars.size() - m_privatevars.size(); + int n = 0; + for (StringMap::const_iterator + it = m_stringvars.begin(); + it != m_stringvars.end(); ++it) { + if (isPrivate(it->first) == false) + n++; + } + return n; +} + /* NodeMetadataList */ -void NodeMetadataList::serialize(std::ostream &os) const +void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk) const { /* Version 0 is a placeholder for "nothing to see here; go away." @@ -93,7 +128,8 @@ void NodeMetadataList::serialize(std::ostream &os) const return; } - writeU8(os, 1); // version + u8 version = (blockver > 27) ? 2 : 1; + writeU8(os, version); writeU16(os, count); for(std::map::const_iterator @@ -108,7 +144,7 @@ void NodeMetadataList::serialize(std::ostream &os) const u16 p16 = p.Z * MAP_BLOCKSIZE * MAP_BLOCKSIZE + p.Y * MAP_BLOCKSIZE + p.X; writeU16(os, p16); - data->serialize(os); + data->serialize(os, version, disk); } } @@ -123,7 +159,7 @@ void NodeMetadataList::deSerialize(std::istream &is, IItemDefManager *item_def_m return; } - if (version != 1) { + if (version > 2) { std::string err_str = std::string(FUNCTION_NAME) + ": version " + itos(version) + " not supported"; infostream << err_str << std::endl; @@ -132,7 +168,7 @@ void NodeMetadataList::deSerialize(std::istream &is, IItemDefManager *item_def_m u16 count = readU16(is); - for (u16 i=0; i < count; i++) { + for (u16 i = 0; i < count; i++) { u16 p16 = readU16(is); v3s16 p; @@ -143,15 +179,14 @@ void NodeMetadataList::deSerialize(std::istream &is, IItemDefManager *item_def_m p.X = p16; if (m_data.find(p) != m_data.end()) { - warningstream<<"NodeMetadataList::deSerialize(): " - <<"already set data at position" - <<"("<deSerialize(is); + data->deSerialize(is, version); m_data[p] = data; } } diff --git a/src/nodemetadata.h b/src/nodemetadata.h index f46c0fe91..0d72485bc 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define NODEMETADATA_HEADER #include "metadata.h" +#include "util/cpp11_container.h" /* NodeMetadata stores arbitary amounts of data for special blocks. @@ -40,8 +41,8 @@ public: NodeMetadata(IItemDefManager *item_def_mgr); ~NodeMetadata(); - void serialize(std::ostream &os) const; - void deSerialize(std::istream &is); + void serialize(std::ostream &os, u8 version, bool disk=true) const; + void deSerialize(std::istream &is, u8 version); void clear(); bool empty() const; @@ -52,8 +53,17 @@ public: return m_inventory; } + inline bool isPrivate(const std::string &name) const + { + return m_privatevars.count(name) != 0; + } + void markPrivate(const std::string &name, bool set); + private: + int countNonPrivate() const; + Inventory *m_inventory; + UNORDERED_SET m_privatevars; }; @@ -66,7 +76,7 @@ class NodeMetadataList public: ~NodeMetadataList(); - void serialize(std::ostream &os) const; + void serialize(std::ostream &os, u8 blockver, bool disk=true) const; void deSerialize(std::istream &is, IItemDefManager *item_def_mgr); // Add all keys in this list to the vector keys diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp index 40a33a51d..d02d1cb3e 100644 --- a/src/rollback_interface.cpp +++ b/src/rollback_interface.cpp @@ -44,7 +44,7 @@ RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef) NodeMetadata *metap = map->getNodeMetadata(p); if (metap) { std::ostringstream os(std::ios::binary); - metap->serialize(os); + metap->serialize(os, 1); // FIXME: version bump?? meta = os.str(); } } @@ -165,7 +165,7 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam } } std::istringstream is(n_old.meta, std::ios::binary); - meta->deSerialize(is); + meta->deSerialize(is, 1); // FIXME: version bump?? } // Inform other things that the meta data has changed v3s16 blockpos = getContainerPos(p, MAP_BLOCKSIZE); diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 6232112c5..5dfa6d52e 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -94,6 +94,32 @@ int NodeMetaRef::l_get_inventory(lua_State *L) return 1; } +// mark_as_private(self, or {, , ...}) +int NodeMetaRef::l_mark_as_private(lua_State *L) +{ + MAP_LOCK_REQUIRED; + + NodeMetaRef *ref = checkobject(L, 1); + NodeMetadata *meta = dynamic_cast(ref->getmeta(true)); + assert(meta); + + if (lua_istable(L, 2)) { + lua_pushnil(L); + while (lua_next(L, 2) != 0) { + // key at index -2 and value at index -1 + luaL_checktype(L, -1, LUA_TSTRING); + meta->markPrivate(lua_tostring(L, -1), true); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + } else if (lua_isstring(L, 2)) { + meta->markPrivate(lua_tostring(L, 2), true); + } + ref->reportMetadataChange(); + + return 0; +} + void NodeMetaRef::handleToTable(lua_State *L, Metadata *_meta) { // fields @@ -229,6 +255,7 @@ const luaL_Reg NodeMetaRef::methodsServer[] = { luamethod(MetaDataRef, to_table), luamethod(MetaDataRef, from_table), luamethod(NodeMetaRef, get_inventory), + luamethod(NodeMetaRef, mark_as_private), luamethod(MetaDataRef, equals), {0,0} }; diff --git a/src/script/lua_api/l_nodemeta.h b/src/script/lua_api/l_nodemeta.h index 2ac028079..dd4260ff9 100644 --- a/src/script/lua_api/l_nodemeta.h +++ b/src/script/lua_api/l_nodemeta.h @@ -73,6 +73,9 @@ private: // get_inventory(self) static int l_get_inventory(lua_State *L); + // mark_as_private(self, or {, , ...}) + static int l_mark_as_private(lua_State *L); + public: NodeMetaRef(v3s16 p, ServerEnvironment *env); NodeMetaRef(Metadata *meta); diff --git a/src/serialization.h b/src/serialization.h index 52c63098e..c91c3241f 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -63,13 +63,14 @@ with this program; if not, write to the Free Software Foundation, Inc., 25: Improved node timer format 26: Never written; read the same as 25 27: Added light spreading flags to blocks + 28: Added "private" flag to NodeMetadata */ // This represents an uninitialized or invalid format #define SER_FMT_VER_INVALID 255 // Highest supported serialization version -#define SER_FMT_VER_HIGHEST_READ 27 +#define SER_FMT_VER_HIGHEST_READ 28 // Saved on disk version -#define SER_FMT_VER_HIGHEST_WRITE 27 +#define SER_FMT_VER_HIGHEST_WRITE 28 // Lowest supported serialization version #define SER_FMT_VER_LOWEST_READ 0 // Lowest serialization version for writing -- cgit v1.2.3 From f286c54908e007e4861428f98017a156531fe3f0 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Fri, 12 May 2017 10:27:58 +0100 Subject: Replace inconsistent mentions of core.* with minetest.* (#5749) --- doc/lua_api.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index a295d7d0e..4e2ec00c3 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1872,14 +1872,14 @@ Escape sequences Most text can contain escape sequences, that can for example color the text. There are a few exceptions: tab headers, dropdowns and vertical labels can't. The following functions provide escape sequences: -* `core.get_color_escape_sequence(color)`: +* `minetest.get_color_escape_sequence(color)`: * `color` is a ColorString * The escape sequence sets the text color to `color` -* `core.colorize(color, message)`: +* `minetest.colorize(color, message)`: * Equivalent to: - `core.get_color_escape_sequence(color) .. + `minetest.get_color_escape_sequence(color) .. message .. - core.get_color_escape_sequence("#ffffff")` + minetest.get_color_escape_sequence("#ffffff")` * `color.get_background_escape_sequence(color)` * `color` is a ColorString * The escape sequence sets the background of the whole text element to @@ -2026,7 +2026,7 @@ Helper functions reliable or verifyable. Compatible forks will have a different name and version entirely. To check for the presence of engine features, test whether the functions exported by the wanted features exist. For example: - `if core.nodeupdate then ... end`. + `if minetest.nodeupdate then ... end`. ### Logging * `minetest.debug(...)` @@ -2359,8 +2359,8 @@ and `minetest.auth_reload` call the authetification handler. * `function EmergeAreaCallback(blockpos, action, calls_remaining, param)` * - `blockpos` is the *block* coordinates of the block that had been emerged * - `action` could be one of the following constant values: - * `core.EMERGE_CANCELLED`, `core.EMERGE_ERRORED`, `core.EMERGE_FROM_MEMORY`, - * `core.EMERGE_FROM_DISK`, `core.EMERGE_GENERATED` + * `minetest.EMERGE_CANCELLED`, `minetest.EMERGE_ERRORED`, `minetest.EMERGE_FROM_MEMORY`, + * `minetest.EMERGE_FROM_DISK`, `minetest.EMERGE_GENERATED` * - `calls_remaining` is the number of callbacks to be expected after this one * - `param` is the user-defined parameter passed to emerge_area (or nil if the * parameter was absent) @@ -2415,11 +2415,11 @@ and `minetest.auth_reload` call the authetification handler. might be removed. * returns `false` if the area is not fully generated, `true` otherwise -* `core.check_single_for_falling(pos)` +* `minetest.check_single_for_falling(pos)` * causes an unsupported `group:falling_node` node to fall and causes an unattached `group:attached_node` node to fall. * does not spread these updates to neighbours. -* `core.check_for_falling(pos)` +* `minetest.check_for_falling(pos)` * causes an unsupported `group:falling_node` node to fall and causes an unattached `group:attached_node` node to fall. * spread these updates to neighbours and can cause a cascade @@ -3408,9 +3408,9 @@ Note that the node being queried needs to have already been been registered. The following builtin node types have their Content IDs defined as constants: ``` -core.CONTENT_UNKNOWN (ID for "unknown" nodes) -core.CONTENT_AIR (ID for "air" nodes) -core.CONTENT_IGNORE (ID for "ignore" nodes) +minetest.CONTENT_UNKNOWN (ID for "unknown" nodes) +minetest.CONTENT_AIR (ID for "air" nodes) +minetest.CONTENT_IGNORE (ID for "ignore" nodes) ``` ##### Mapgen VoxelManip objects -- cgit v1.2.3 From 0120fe16a761f9e06c8c2877439db6a46d808143 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 13 May 2017 13:14:41 +0200 Subject: CSM: Document forgotten functions --- builtin/common/misc_helpers.lua | 2 +- builtin/mainmenu/common.lua | 2 +- builtin/mainmenu/tab_mods.lua | 2 +- doc/client_lua_api.md | 41 +++++++++++++++++++++++++++++++++++++---- doc/lua_api.txt | 5 ++++- 5 files changed, 44 insertions(+), 8 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 1ca400688..68481f7c8 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -308,7 +308,7 @@ function core.formspec_escape(text) end -function core.splittext(text,charlimit) +function core.wrap_text(text, charlimit) local retval = {} local current_idx = 1 diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index 294e1a621..fa7ae583b 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -250,7 +250,7 @@ end -------------------------------------------------------------------------------- function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transparency) - local textlines = core.splittext(text, textlen) + local textlines = core.wrap_text(text, textlen) local retval = "textlist[" .. xpos .. "," .. ypos .. ";" .. width .. "," .. height .. ";" .. tl_name .. ";" diff --git a/builtin/mainmenu/tab_mods.lua b/builtin/mainmenu/tab_mods.lua index 29afd8a4e..9510a9e18 100644 --- a/builtin/mainmenu/tab_mods.lua +++ b/builtin/mainmenu/tab_mods.lua @@ -75,7 +75,7 @@ local function get_formspec(tabview, name, tabdata) if error == nil then local descriptiontext = descriptionfile:read("*all") - descriptionlines = core.splittext(descriptiontext,42) + descriptionlines = core.wrap_text(descriptiontext, 42) descriptionfile:close() else descriptionlines = {} diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index 19947a525..9f59b4671 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -590,17 +590,24 @@ Helper functions * `math.sign(x, tolerance)` * Get the sign of a number. Optional: Also returns `0` when the absolute value is within the tolerance (default: `0`) -* `string.split(str, separator=",", include_empty=false, max_splits=-1, -* sep_is_pattern=false)` +* `string.split(str, separator=",", include_empty=false, max_splits=-1, sep_is_pattern=false)` * If `max_splits` is negative, do not limit splits. * `sep_is_pattern` specifies if separator is a plain string or a pattern (regex). * e.g. `string:split("a,b", ",") == {"a","b"}` * `string:trim()` * e.g. `string.trim("\n \t\tfoo bar\t ") == "foo bar"` +* `minetest.wrap_text(str, limit)`: returns a string + * Adds new lines to the string to keep it within the specified character limit + * limit: Maximal amount of characters in one line +* `minetest.pos_to_string({x=X,y=Y,z=Z}, decimal_places))`: returns string `"(X,Y,Z)"` + * Convert position to a printable string + Optional: 'decimal_places' will round the x, y and z of the pos to the given decimal place. +* `minetest.string_to_pos(string)`: returns a position + * Same but in reverse. Returns `nil` if the string can't be parsed to a position. +* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)")`: returns two positions + * Converts a string representing an area box into two positions * `minetest.is_yes(arg)` * returns whether `arg` can be interpreted as yes -* `minetest.get_us_time()` - * returns time with microsecond precision. May not return wall time. * `table.copy(table)`: returns a table * returns a deep copy of `table` @@ -649,6 +656,8 @@ Call these functions only at load time! * Return `true` to mark the message as handled, which means that it will not be sent to server * `minetest.register_chatcommand(cmd, chatcommand definition)` * Adds definition to minetest.registered_chatcommands +* `minetest.unregister_chatcommand(name)` + * Unregisters a chatcommands registered with register_chatcommand. * `minetest.register_on_death(func())` * Called when the local player dies * `minetest.register_on_hp_modification(func(hp))` @@ -683,6 +692,12 @@ Call these functions only at load time! * `minetest.after(time, func, ...)` * Call the function `func` after `time` seconds, may be fractional * Optional: Variable number of arguments that are passed to `func` +* `minetest.get_us_time()` + * Returns time with microsecond precision. May not return wall time. +* `minetest.get_day_count()` + * Returns number days elapsed since world was created, accounting for time changes. +* `minetest.get_timeofday()` + * Returns the time of day: `0` for midnight, `0.5` for midday ### Map * `minetest.get_node(pos)` @@ -691,8 +706,17 @@ Call these functions only at load time! for unloaded areas. * `minetest.get_node_or_nil(pos)` * Same as `get_node` but returns `nil` for unloaded areas. +* `minetest.find_node_near(pos, radius, nodenames, [search_center])`: returns pos or `nil` + * `radius`: using a maximum metric + * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` + * `search_center` is an optional boolean (default: `false`) + If true `pos` is also checked for the nodes * `minetest.get_meta(pos)` * Get a `NodeMetaRef` at that position +* `minetest.get_node_level(pos)` + * get level of leveled node (water, snow) +* `minetest.get_node_max_level(pos)` + * get max available level for leveled node ### Player * `minetest.get_wielded_item()` @@ -710,6 +734,13 @@ Call these functions only at load time! * Take a screenshot. * `minetest.get_server_info()` * Returns [server info](#server-info). +* `minetest.send_respawn()` + * Sends a respawn request to the server. + +### Storage API +* `minetest.get_mod_storage()`: + * returns reference to mod private `StorageRef` + * must be called during mod load time ### Misc. * `minetest.parse_json(string[, nullvalue])`: returns something @@ -763,6 +794,8 @@ Call these functions only at load time! * same as fgettext_ne(), but calls minetest.formspec_escape before returning result * `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position * returns the exact position on the surface of a pointed node +* `minetest.global_exists(name)` + * Checks if a global variable has been set, without triggering a warning. ### UI * `minetest.ui.minimap` diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 4e2ec00c3..762446466 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1931,7 +1931,10 @@ Helper functions * e.g. `string:split("a,b", ",") == {"a","b"}` * `string:trim()` * e.g. `string.trim("\n \t\tfoo bar\t ") == "foo bar"` -* `minetest.pos_to_string({x=X,y=Y,z=Z}, decimal_places))`: returns `"(X,Y,Z)"` +* `minetest.wrap_text(str, limit)`: returns a string + * Adds new lines to the string to keep it within the specified character limit + * limit: Maximal amount of characters in one line +* `minetest.pos_to_string({x=X,y=Y,z=Z}, decimal_places))`: returns string `"(X,Y,Z)"` * Convert position to a printable string Optional: 'decimal_places' will round the x, y and z of the pos to the given decimal place. * `minetest.string_to_pos(string)`: returns a position -- cgit v1.2.3 From 30c51a1115539b76aa76091de0adbb083c680b42 Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Fri, 2 Jun 2017 13:57:59 +0000 Subject: Document hardware coloring and soft node overlays (#5876) --- doc/lua_api.txt | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 762446466..e6d856368 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -426,6 +426,167 @@ Result is more like what you'd expect if you put a color on top of another color. Meaning white surfaces get a lot of your new color while black parts don't change very much. +Hardware coloring +----------------- +The goal of hardware coloring is to simplify the creation of +colorful nodes. If your textures use the same pattern, and they only +differ in their color (like colored wool blocks), you can use hardware +coloring instead of creating and managing many texture files. +All of these methods use color multiplication (so a white-black texture +with red coloring will result in red-black color). + +### Static coloring +This method is useful if you wish to create nodes/items with +the same texture, in different colors, each in a new node/item definition. + +#### Global color +When you register an item or node, set its `color` field (which accepts a +`ColorSpec`) to the desired color. + +An `ItemStack`s static color can be overwritten by the `color` metadata +field. If you set that field to a `ColorString`, that color will be used. + +#### Tile color +Each tile may have an individual static color, which overwrites every +other coloring methods. To disable the coloring of a face, +set its color to white (because multiplying with white does nothing). +You can set the `color` property of the tiles in the node's definition +if the tile is in table format. + +### Palettes +For nodes and items which can have many colors, a palette is more +suitable. A palette is a texture, which can contain up to 256 pixels. +Each pixel is one possible color for the node/item. +You can register one node/item, which can have up to 256 colors. + +#### Palette indexing +When using palettes, you always provide a pixel index for the given +node or `ItemStack`. The palette is read from left to right and from +top to bottom. If the palette has less than 256 pixels, then it is +stretched to contain exactly 256 pixels (after arranging the pixels +to one line). The indexing starts from 0. + +Examples: +* 16x16 palette, index = 0: the top left corner +* 16x16 palette, index = 4: the fifth pixel in the first row +* 16x16 palette, index = 16: the pixel below the top left corner +* 16x16 palette, index = 255: the bottom right corner +* 2 (width)x4 (height) palette, index=31: the top left corner. + The palette has 8 pixels, so each pixel is stretched to 32 pixels, + to ensure the total 256 pixels. +* 2x4 palette, index=32: the top right corner +* 2x4 palette, index=63: the top right corner +* 2x4 palette, index=64: the pixel below the top left corner + +#### Using palettes with items +When registering an item, set the item definition's `palette` field to +a texture. You can also use texture modifiers. + +The `ItemStack`'s color depends on the `palette_index` field of the +stack's metadata. `palette_index` is an integer, which specifies the +index of the pixel to use. + +#### Linking palettes with nodes +When registering a node, set the item definition's `palette` field to +a texture. You can also use texture modifiers. +The node's color depends on its `param2`, so you also must set an +appropriate `drawtype`: +* `drawtype = "color"` for nodes which use their full `param2` for + palette indexing. These nodes can have 256 different colors. + The palette should contain 256 pixels. +* `drawtype = "colorwallmounted"` for nodes which use the first + five bits (most significant) of `param2` for palette indexing. + The remaining three bits are describing rotation, as in `wallmounted` + draw type. Division by 8 yields the palette index (without stretching the + palette). These nodes can have 32 different colors, and the palette + should contain 32 pixels. + Examples: + * `param2 = 17` is 2 * 8 + 1, so the rotation is 1 and the third (= 2 + 1) + pixel will be picked from the palette. + * `param2 = 35` is 4 * 8 + 3, so the rotation is 3 and the fifth (= 4 + 1) + pixel will be picked from the palette. +* `drawtype = "colorfacedir"` for nodes which use the first + three bits of `param2` for palette indexing. The remaining + five bits are describing rotation, as in `facedir` draw type. + Division by 32 yields the palette index (without stretching the + palette). These nodes can have 8 different colors, and the + palette should contain 8 pixels. + Examples: + * `param2 = 17` is 0 * 32 + 17, so the rotation is 17 and the + first (= 0 + 1) pixel will be picked from the palette. + * `param2 = 35` is 1 * 32 + 3, so the rotation is 3 and the + second (= 1 + 1) pixel will be picked from the palette. + +To colorize a node on the map, set its `param2` value (according +to the node's draw type). + +### Conversion between nodes in the inventory and the on the map +Static coloring is the same for both cases, there is no need +for conversion. + +If the `ItemStack`'s metadata contains the `color` field, it will be +lost on placement, because nodes on the map can only use palettes. + +If the `ItemStack`'s metadata contains the `palette_index` field, you +currently must manually convert between it and the node's `param2` with +custom `on_place` and `on_dig` callbacks. + +### Colored items in craft recipes +Craft recipes only support item strings, but fortunately item strings +can also contain metadata. Example craft recipe registration: + + local stack = ItemStack("wool:block") + dyed:get_meta():set_int("palette_index", 3) -- add index + minetest.register_craft({ + output = dyed:to_string(), -- convert to string + type = "shapeless", + recipe = { + "wool:block", + "dye:red", + }, + }) + +Metadata field filtering in the `recipe` field are not supported yet, +so the craft output is independent of the color of the ingredients. + +Soft texture overlay +-------------------- +Sometimes hardware coloring is not enough, because it affects the +whole tile. Soft texture overlays were added to Minetest to allow +the dynamic coloring of only specific parts of the node's texture. +For example a grass block may have colored grass, while keeping the +dirt brown. + +These overlays are 'soft', because unlike texture modifiers, the layers +are not merged in the memory, but they are simply drawn on top of each +other. This allows different hardware coloring, but also means that +tiles with overlays are drawn slower. Using too much overlays might +cause FPS loss. + +To define an overlay, simply set the `overlay_tiles` field of the node +definition. These tiles are defined in the same way as plain tiles: +they can have a texture name, color etc. +To skip one face, set that overlay tile to an empty string. + +Example (colored grass block): + + minetest.register_node("default:dirt_with_grass", { + description = "Dirt with Grass", + -- Regular tiles, as usual + -- The dirt tile disables palette coloring + tiles = {{name = "default_grass.png"}, + {name = "default_dirt.png", color = "white"}}, + -- Overlay tiles: define them in the same style + -- The top and bottom tile does not have overlay + overlay_tiles = {"", "", + {name = "default_grass_side.png", tileable_vertical = false}}, + -- Global color, used in inventory + color = "green", + -- Palette in the world + paramtype2 = "color", + palette = "default_foilage.png", + }) + Sounds ------ Only Ogg Vorbis files are supported. -- cgit v1.2.3 From 1b83b0acfde26e1689202bb99659934ed598d705 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Fri, 2 Jun 2017 14:15:49 +0200 Subject: Lua_api.txt: Various edits and Markdown syntax improvements Add minor bits of missing Lua API documentation. Remove L-system lighting bug warning. Clarify 2 lines in node timer documentation. Fix many Markdown syntax errors in lua_api.txt. --- doc/lua_api.txt | 255 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 137 insertions(+), 118 deletions(-) (limited to 'doc/lua_api.txt') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e6d856368..e4ffa7bbe 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -186,7 +186,11 @@ Naming convention for registered textual names ---------------------------------------------- Registered names should generally be in this format: - "modname:" ( can have characters a-zA-Z0-9_) + `modname:` + +`` can have these characters: + + a-zA-Z0-9_ This is to prevent conflicting names from corrupting maps and is enforced by the mod loader. @@ -209,7 +213,7 @@ 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). +`minetest.register_alias_force(name, convert_to)`. This will make Minetest to convert things called name to things called `convert_to`. @@ -309,10 +313,10 @@ Example: default_sandstone.png^[resize:16x16 #### `[opacity:` - Makes the base image transparent according to the given ratio. - r must be between 0 and 255. - 0 means totally transparent. - 255 means totally opaque. +Makes the base image transparent according to the given ratio. + +`r` must be between 0 and 255. +0 means totally transparent. 255 means totally opaque. Example: @@ -676,7 +680,7 @@ the global `minetest.registered_*` tables. * `minetest.unregister_item(name)` * Unregisters the item name from engine, and deletes the entry with key * `name` from `minetest.registered_items` and from the associated item - * table according to its nature: minetest.registered_nodes[] etc + * table according to its nature: `minetest.registered_nodes[]` etc * `minetest.register_biome(biome definition)` * returns an integer uniquely identifying the registered biome @@ -754,9 +758,9 @@ They are represented by a table: {name="name", param1=num, param2=num} -`param1` and `param2` are 8-bit integers. The engine uses them for certain -automated functions. If you don't use these functions, you can use them to -store arbitrary values. +`param1` and `param2` are 8-bit integers ranging from 0 to 255. The engine uses +them for certain automated functions. If you don't use these functions, you can +use them to store arbitrary values. The functions of `param1` and `param2` are determined by certain fields in the node definition: @@ -825,15 +829,6 @@ node definition: ^ Only valid for "glasslike_framed" or "glasslike_framed_optional" drawtypes. param2 defines 64 levels of internal liquid. Liquid texture is defined using `special_tiles = {"modname_tilename.png"},` - collision_box = { - type = "fixed", - fixed = { - {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, - }, - }, - ^ defines list of collision boxes for the node. If empty, collision boxes - will be the same as nodeboxes, in case of any other nodes will be full cube - as in the example above. Nodes can also contain extra data. See "Node Metadata". @@ -984,6 +979,7 @@ If no flags are specified (or defaults is), 2D noise is eased and 3D noise is no Accumulates the absolute value of each noise gradient result. Noise parameters format example for 2D or 3D perlin noise or perlin noise maps: + np_terrain = { offset = 0, scale = 1, @@ -994,8 +990,8 @@ Noise parameters format example for 2D or 3D perlin noise or perlin noise maps: lacunarity = 2.0, flags = "defaults, absvalue" } - ^ A single noise parameter table can be used to get 2D or 3D noise, - when getting 2D noise spread.z is ignored. + ^ A single noise parameter table can be used to get 2D or 3D noise, + when getting 2D noise spread.z is ignored. Ore types @@ -1067,14 +1063,15 @@ to small changes. The following is a decent set of parameters to work from: }, noise_threshold = 1.6 -WARNING: Use this ore type *very* sparingly since it is ~200x more +**WARNING**: Use this ore type *very* sparingly since it is ~200x more computationally expensive than any other ore. Ore attributes -------------- See section "Flag Specifier Format". -Currently supported flags: `absheight` +Currently supported flags: +`absheight`, `puff_cliffs`, `puff_additive_composition`. ### `absheight` Also produce this same ore between the height range of `-y_max` and `-y_min`. @@ -1130,6 +1127,7 @@ in the form of a table. This table specifies the following fields: previous contents (default: false) About probability values: + * A probability value of `0` or `1` means that node will never appear (0% chance). * A probability value of `254` or `255` means the node will always appear (100% chance). * If the probability value `p` is greater than `1`, then there is a @@ -1284,16 +1282,32 @@ There are three kinds of items: nodes, tools and craftitems. things according to `tool_capabilities`. * Craftitem (`register_craftitem`): A miscellaneous item. +### Amount and wear +All item stacks have an amount between 0 to 65535. It is 1 by +default. Tool item stacks can not have an amount greater than 1. + +Tools use a wear (=damage) value ranging from 0 to 65535. The +value 0 is the default and used is for unworn tools. The values +1 to 65535 are used for worn tools, where a higher value stands for +a higher wear. Non-tools always have a wear value of 0. + ### Item formats Items and item stacks can exist in three formats: Serializes, table format and `ItemStack`. #### Serialized -This is called "stackstring" or "itemstring": +This is called "stackstring" or "itemstring". It is a simple string with +1-3 components: the full item identifier, an optional amount and an optional +wear value. Syntax: + + [[ ]] -* e.g. `'default:dirt 5'` -* e.g. `'default:pick_wood 21323'` -* e.g. `'default:apple'` +Examples: + +* `'default:apple'`: 1 apple +* `'default:dirt 5'`: 5 dirt +* `'default:pick_stone'`: a new stone pickaxe +* `'default:pick_wood 1 21323'`: a wooden pickaxe, ca. 1/3 worn out #### Table format Examples: @@ -1387,6 +1401,9 @@ Another example: Make red wool from white wool and red dye: ### Special groups * `immortal`: Disables the group damage system for an entity +* `punch_operable`: For entities; disables the regular damage mechanism for + players punching it by hand or a non-tool item, so that it can do something + else than take damage. * `level`: Can be used to give an additional sense of progression in the game. * A larger level will cause e.g. a weapon of a lower level make much less damage, and get worn out much faster, or not be able to get drops @@ -1425,6 +1442,7 @@ Another example: Make red wool from white wool and red dye: ### Examples of custom groups Item groups are often used for defining, well, _groups of items_. + * `meat`: any meat-kind of a thing (rating might define the size or healing ability or be irrelevant -- it is not defined as of yet) * `eatable`: anything that can be eaten. Rating might define HP gain in half @@ -1645,7 +1663,7 @@ Item metadata only contains a key-value store. Some of the values in the key-value store are handled specially: -* `description`: Set the itemstack's description. Defaults to idef.description +* `description`: Set the item stack's description. Defaults to `idef.description` * `color`: A `ColorString`, which sets the stack's color. * `palette_index`: If the item has a palette, this is used to get the current color from the palette. @@ -1707,7 +1725,7 @@ examples. #### `container[,]` * Start of a container block, moves all physical elements in the container by (X, Y) -* Must have matching container_end +* Must have matching `container_end` * Containers can be nested, in which case the offsets are added (child containers are relative to parent containers) @@ -1792,7 +1810,7 @@ examples. #### `field[,;,;;