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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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 c9492b4d37c11f35cfdc1558f771eef87fc5c972 Mon Sep 17 00:00:00 2001 From: kilbith Date: Mon, 13 Mar 2017 08:07:14 +0100 Subject: GUI: Allow texture packs to customize the progress bar (#5368) --- doc/texture_packs.txt | 3 +++ src/drawscene.cpp | 69 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 24 deletions(-) (limited to 'doc') diff --git a/doc/texture_packs.txt b/doc/texture_packs.txt index 5c535a9f1..1813c29e4 100644 --- a/doc/texture_packs.txt +++ b/doc/texture_packs.txt @@ -76,6 +76,9 @@ by texture packs. All existing fallback textures can be found in the directory * `player.png`: front texture of the 2D upright sprite player * `player_back.png`: back texture of the 2D upright sprite player +* `progress_bar.png`: foreground texture of the loading screen's progress bar +* `progress_bar_bg.png`: background texture of the loading screen's progress bar + * `moon.png`: texture of the moon. Default texture is generated by Minetest * `moon_tonemap.png`: tonemap to be used when `moon.png` was found * `sun.png`: texture of the sun. Default texture is generated by Minetest diff --git a/src/drawscene.cpp b/src/drawscene.cpp index c4ef3cc51..e3e6301a8 100644 --- a/src/drawscene.cpp +++ b/src/drawscene.cpp @@ -592,30 +592,51 @@ void draw_load_screen(const std::wstring &text, IrrlichtDevice* device, // draw progress bar if ((percent >= 0) && (percent <= 100)) { - std::string gamepath = fs::RemoveRelativePathComponents( - porting::path_share + DIR_DELIM + "textures"); - video::ITexture *progress_img = driver->getTexture( - (gamepath + "/base/pack/progress_bar.png").c_str()); - video::ITexture *progress_img_bg = driver->getTexture( - (gamepath + "/base/pack/progress_bar_bg.png").c_str()); - - const core::dimension2d &img_size = progress_img_bg->getSize(); - u32 imgW = MYMAX(208, img_size.Width); - u32 imgH = MYMAX(24, img_size.Height); - v2s32 img_pos((screensize.X - imgW) / 2, (screensize.Y - imgH) / 2); - - draw2DImageFilterScaled( - driver, progress_img_bg, - core::rect(img_pos.X, img_pos.Y, img_pos.X + imgW, img_pos.Y + imgH), - core::rect(0, 0, img_size.Width, img_size.Height), - 0, 0, true); - - draw2DImageFilterScaled( - driver, progress_img, - core::rect(img_pos.X, img_pos.Y, - img_pos.X + (percent * imgW) / 100, img_pos.Y + imgH), - core::rect(0, 0, ((percent * img_size.Width) / 100), img_size.Height), - 0, 0, true); + const std::string &texture_path = g_settings->get("texture_path"); + std::string tp_progress_bar = texture_path + "/progress_bar.png"; + std::string tp_progress_bar_bg = texture_path + "/progress_bar_bg.png"; + + if (!(fs::PathExists(tp_progress_bar) && + fs::PathExists(tp_progress_bar_bg))) { + std::string gamepath = fs::RemoveRelativePathComponents( + porting::path_share + DIR_DELIM + "textures"); + tp_progress_bar = gamepath + "/base/pack/progress_bar.png"; + tp_progress_bar_bg = gamepath + "/base/pack/progress_bar_bg.png"; + } + + video::ITexture *progress_img = + driver->getTexture(tp_progress_bar.c_str()); + video::ITexture *progress_img_bg = + driver->getTexture(tp_progress_bar_bg.c_str()); + + if (progress_img && progress_img_bg) { + const core::dimension2d &img_size = progress_img_bg->getSize(); + u32 imgW = rangelim(img_size.Width, 200, 600); + u32 imgH = rangelim(img_size.Height, 24, 72); + v2s32 img_pos((screensize.X - imgW) / 2, (screensize.Y - imgH) / 2); + + draw2DImageFilterScaled( + driver, progress_img_bg, + core::rect(img_pos.X, + img_pos.Y, + img_pos.X + imgW, + img_pos.Y + imgH), + core::rect(0, 0, + img_size.Width, + img_size.Height), + 0, 0, true); + + draw2DImageFilterScaled( + driver, progress_img, + core::rect(img_pos.X, + img_pos.Y, + img_pos.X + (percent * imgW) / 100, + img_pos.Y + imgH), + core::rect(0, 0, + (percent * img_size.Width) / 100, + img_size.Height), + 0, 0, true); + } } guienv->drawAll(); -- cgit v1.2.3 From 2c19d51409ca903021e0b508e5bc15299c4e51dc Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 22 Jan 2017 11:17:41 +0100 Subject: [CSM] sound_play & sound_stop support + client_lua_api doc (#5096) * squashed: CSM: Implement register_globalstep * Re-use fatal error mechanism from server to disconnect client on CSM error * Little client functions cleanups * squashed: CSM: add core.after function * core.after is shared code between client & server * ModApiUtil get_us_time feature enabled for client --- builtin/client/init.lua | 3 +- builtin/client/preview.lua | 15 +- builtin/client/register.lua | 1 + builtin/common/after.lua | 43 +++ builtin/game/init.lua | 1 + builtin/game/misc.lua | 44 --- doc/client_lua_api.txt | 787 ++++++++++++++++++++++++++++++++++++++ src/client.cpp | 1 + src/client.h | 16 +- src/clientenvironment.cpp | 4 + src/clientenvironment.h | 3 + src/guiEngine.h | 1 + src/script/clientscripting.cpp | 2 + src/script/cpp_api/s_client.cpp | 18 + src/script/cpp_api/s_client.h | 2 + src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_client.h | 3 +- src/script/lua_api/l_mainmenu.cpp | 31 -- src/script/lua_api/l_mainmenu.h | 7 +- src/script/lua_api/l_sound.cpp | 62 +++ src/script/lua_api/l_sound.h | 36 ++ src/script/lua_api/l_util.cpp | 2 + src/script/scripting_mainmenu.cpp | 4 +- 23 files changed, 996 insertions(+), 91 deletions(-) create mode 100644 builtin/common/after.lua create mode 100644 doc/client_lua_api.txt create mode 100644 src/script/lua_api/l_sound.cpp create mode 100644 src/script/lua_api/l_sound.h (limited to 'doc') diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 4797ac4b6..dd218aab6 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -4,9 +4,10 @@ local clientpath = scriptpath.."client"..DIR_DELIM local commonpath = scriptpath.."common"..DIR_DELIM dofile(clientpath .. "register.lua") +dofile(commonpath .. "after.lua") +dofile(commonpath .. "chatcommands.lua") dofile(clientpath .. "preview.lua") core.register_on_death(function() core.display_chat_message("You died.") end) - diff --git a/builtin/client/preview.lua b/builtin/client/preview.lua index c421791f5..22e8bb97f 100644 --- a/builtin/client/preview.lua +++ b/builtin/client/preview.lua @@ -1,6 +1,6 @@ -- This is an example function to ensure it's working properly, should be removed before merge core.register_on_shutdown(function() - print("shutdown client") + print("[PREVIEW] shutdown client") end) -- This is an example function to ensure it's working properly, should be removed before merge @@ -15,17 +15,28 @@ core.register_on_sending_chat_messages(function(message) return false end) +-- This is an example function to ensure it's working properly, should be removed before merge core.register_on_hp_modification(function(hp) print("[PREVIEW] HP modified " .. hp) end) +-- This is an example function to ensure it's working properly, should be removed before merge core.register_on_damage_taken(function(hp) print("[PREVIEW] Damage taken " .. hp) end) +-- This is an example function to ensure it's working properly, should be removed before merge +core.register_globalstep(function(dtime) + -- print("[PREVIEW] globalstep " .. dtime) +end) + -- This is an example function to ensure it's working properly, should be removed before merge core.register_chatcommand("dump", { func = function(name, param) return true, dump(_G) end, -}) \ No newline at end of file +}) + +core.after(2, function() + print("After 2") +end) diff --git a/builtin/client/register.lua b/builtin/client/register.lua index ddaf4f424..8b60c1222 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -55,6 +55,7 @@ local function make_registration() return t, registerfunc end +core.registered_globalsteps, core.register_globalstep = make_registration() core.registered_on_shutdown, core.register_on_shutdown = make_registration() core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration() core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration() diff --git a/builtin/common/after.lua b/builtin/common/after.lua new file mode 100644 index 000000000..30a9c7bad --- /dev/null +++ b/builtin/common/after.lua @@ -0,0 +1,43 @@ +local jobs = {} +local time = 0.0 +local last = core.get_us_time() / 1000000 + +core.register_globalstep(function(dtime) + local new = core.get_us_time() / 1000000 + if new > last then + time = time + (new - last) + else + -- Overflow, we may lose a little bit of time here but + -- only 1 tick max, potentially running timers slightly + -- too early. + time = time + new + end + last = new + + if #jobs < 1 then + return + end + + -- Iterate backwards so that we miss any new timers added by + -- a timer callback, and so that we don't skip the next timer + -- in the list if we remove one. + for i = #jobs, 1, -1 do + local job = jobs[i] + if time >= job.expire then + core.set_last_run_mod(job.mod_origin) + job.func(unpack(job.arg)) + table.remove(jobs, i) + end + end +end) + +function core.after(after, func, ...) + assert(tonumber(after) and type(func) == "function", + "Invalid core.after invocation") + jobs[#jobs + 1] = { + func = func, + expire = time + after, + arg = {...}, + mod_origin = core.get_last_run_mod() + } +end diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 793d9fe2b..3e192a30a 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -17,6 +17,7 @@ if core.setting_getbool("profiler.load") then profiler = dofile(scriptpath.."profiler"..DIR_DELIM.."init.lua") end +dofile(commonpath .. "after.lua") dofile(gamepath.."item_entity.lua") dofile(gamepath.."deprecated.lua") dofile(gamepath.."misc.lua") diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 3419c1980..25376c180 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -4,50 +4,6 @@ -- Misc. API functions -- -local jobs = {} -local time = 0.0 -local last = core.get_us_time() / 1000000 - -core.register_globalstep(function(dtime) - local new = core.get_us_time() / 1000000 - if new > last then - time = time + (new - last) - else - -- Overflow, we may lose a little bit of time here but - -- only 1 tick max, potentially running timers slightly - -- too early. - time = time + new - end - last = new - - if #jobs < 1 then - return - end - - -- Iterate backwards so that we miss any new timers added by - -- a timer callback, and so that we don't skip the next timer - -- in the list if we remove one. - for i = #jobs, 1, -1 do - local job = jobs[i] - if time >= job.expire then - core.set_last_run_mod(job.mod_origin) - job.func(unpack(job.arg)) - table.remove(jobs, i) - end - end -end) - -function core.after(after, func, ...) - assert(tonumber(after) and type(func) == "function", - "Invalid core.after invocation") - jobs[#jobs + 1] = { - func = func, - expire = time + after, - arg = {...}, - mod_origin = core.get_last_run_mod() - } -end - function core.check_player_privs(name, ...) local arg_type = type(name) if (arg_type == "userdata" or arg_type == "table") and diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt new file mode 100644 index 000000000..8ce487bf8 --- /dev/null +++ b/doc/client_lua_api.txt @@ -0,0 +1,787 @@ +Minetest Lua Modding API Reference 0.4.15 +========================================= +* More information at +* Developer Wiki: + +Introduction +------------ +Content and functionality can be added to Minetest 0.4 by using Lua +scripting in run-time loaded mods. + +A mod is a self-contained bunch of scripts, textures and other related +things that is loaded by and interfaces with Minetest. + +Mods are contained and ran solely on the server side. Definitions and media +files are automatically transferred to the client. + +If you see a deficiency in the API, feel free to attempt to add the +functionality in the engine and API. You can send such improvements as +source code patches on GitHub (https://github.com/minetest/minetest). + +Programming in Lua +------------------ +If you have any difficulty in understanding this, please read +[Programming in Lua](http://www.lua.org/pil/). + +Startup +------- +Mods are loaded during client startup from the mod load paths by running +the `init.lua` scripts in a shared environment. + +Paths +----- +* `RUN_IN_PLACE=1` (Windows release, local build) + * `$path_user`: + * Linux: `` + * Windows: `` + * `$path_share` + * Linux: `` + * Windows: `` +* `RUN_IN_PLACE=0`: (Linux release) + * `$path_share` + * Linux: `/usr/share/minetest` + * Windows: `/minetest-0.4.x` + * `$path_user`: + * Linux: `$HOME/.minetest` + * Windows: `C:/users//AppData/minetest` (maybe) + +Mod load path +------------- +Generic: + +* `$path_share/clientmods/` +* `$path_user/clientmods/` (User-installed mods) + +In a run-in-place version (e.g. the distributed windows version): + +* `minetest-0.4.x/clientmods/` (User-installed mods) + +On an installed version on Linux: + +* `/usr/share/minetest/clientmods/` +* `$HOME/.minetest/clientmods/` (User-installed mods) + +Modpack support +---------------- +Mods can be put in a subdirectory, if the parent directory, which otherwise +should be a mod, contains a file named `modpack.txt`. This file shall be +empty, except for lines starting with `#`, which are comments. + +Mod directory structure +------------------------ + + mods + |-- modname + | |-- depends.txt + | |-- screenshot.png + | |-- description.txt + | |-- settingtypes.txt + | |-- init.lua + | |-- models + | |-- textures + | | |-- modname_stuff.png + | | `-- modname_something_else.png + | |-- sounds + | |-- media + | `-- + `-- another + + +### modname +The location of this directory can be fetched by using +`minetest.get_modpath(modname)`. + +### `depends.txt` +List of mods that have to be loaded before loading this mod. + +A single line contains a single modname. + +Optional dependencies can be defined by appending a question mark +to a single modname. Their meaning is that if the specified mod +is missing, that does not prevent this mod from being loaded. + +### `screenshot.png` +A screenshot shown in the mod manager within the main menu. It should +have an aspect ratio of 3:2 and a minimum size of 300×200 pixels. + +### `description.txt` +A File containing description to be shown within mainmenu. + +### `settingtypes.txt` +A file in the same format as the one in builtin. It will be parsed by the +settings menu and the settings will be displayed in the "Mods" category. + +### `init.lua` +The main Lua script. Running this script should register everything it +wants to register. Subsequent execution depends on minetest calling the +registered callbacks. + +`minetest.setting_get(name)` and `minetest.setting_getbool(name)` can be used +to read custom or existing settings at load time, if necessary. + +### `sounds` +Media files (sounds) that will be transferred to the +client and will be available for use by the mod. + +Naming convention for registered textual names +---------------------------------------------- +Registered names should generally be in this format: + + "modname:" ( can have characters a-zA-Z0-9_) + +This is to prevent conflicting names from corrupting maps and is +enforced by the mod loader. + +### Example +In the mod `experimental`, there is the ideal item/node/entity name `tnt`. +So the name should be `experimental:tnt`. + +Enforcement can be overridden by prefixing the name with `:`. This can +be used for overriding the registrations of some other mod. + +Example: Any mod can redefine `experimental:tnt` by using the name + + :experimental:tnt + +when registering it. +(also that mod is required to have `experimental` as a dependency) + +The `:` prefix can also be used for maintaining backwards compatibility. + +### Aliases +Aliases can be added by using `minetest.register_alias(name, convert_to)` or +`minetest.register_alias_force(name, convert_to). + +This will make Minetest to convert things called name to things called +`convert_to`. + +The only difference between `minetest.register_alias` and +`minetest.register_alias_force` is that if an item called `name` exists, +`minetest.register_alias` will do nothing while +`minetest.register_alias_force` will unregister it. + +This can be used for maintaining backwards compatibility. + +This can be also used for setting quick access names for things, e.g. if +you have an item called `epiclylongmodname:stuff`, you could do + + minetest.register_alias("stuff", "epiclylongmodname:stuff") + +and be able to use `/giveme stuff`. + +Sounds +------ +Only Ogg Vorbis files are supported. + +For positional playing of sounds, only single-channel (mono) files are +supported. Otherwise OpenAL will play them non-positionally. + +Mods should generally prefix their sounds with `modname_`, e.g. given +the mod name "`foomod`", a sound could be called: + + foomod_foosound.ogg + +Sounds are referred to by their name with a dot, a single digit and the +file extension stripped out. When a sound is played, the actual sound file +is chosen randomly from the matching sounds. + +When playing the sound `foomod_foosound`, the sound is chosen randomly +from the available ones of the following files: + +* `foomod_foosound.ogg` +* `foomod_foosound.0.ogg` +* `foomod_foosound.1.ogg` +* (...) +* `foomod_foosound.9.ogg` + +Examples of sound parameter tables: + + -- Play locationless on all clients + { + gain = 1.0, -- default + } + -- Play locationless to one player + { + to_player = name, + gain = 1.0, -- default + } + -- Play locationless to one player, looped + { + to_player = name, + gain = 1.0, -- default + loop = true, + } + -- Play in a location + { + pos = {x = 1, y = 2, z = 3}, + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + } + -- Play connected to an object, looped + { + object = , + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + loop = true, + } + +Looped sounds must either be connected to an object or played locationless to +one player using `to_player = name,` + +### `SimpleSoundSpec` +* e.g. `""` +* e.g. `"default_place_node"` +* e.g. `{}` +* e.g. `{name = "default_place_node"}` +* e.g. `{name = "default_place_node", gain = 1.0}` + +Representations of simple things +-------------------------------- + +### Position/vector + + {x=num, y=num, z=num} + +For helper functions see "Vector helpers". + +### `pointed_thing` +* `{type="nothing"}` +* `{type="node", under=pos, above=pos}` +* `{type="object", ref=ObjectRef}` + +Flag Specifier Format +--------------------- +Flags using the standardized flag specifier format can be specified in either of +two ways, by string or table. + +The string format is a comma-delimited set of flag names; whitespace and +unrecognized flag fields are ignored. Specifying a flag in the string sets the +flag, and specifying a flag prefixed by the string `"no"` explicitly +clears the flag from whatever the default may be. + +In addition to the standard string flag format, the schematic flags field can +also be a table of flag names to boolean values representing whether or not the +flag is set. Additionally, if a field with the flag name prefixed with `"no"` +is present, mapped to a boolean of any value, the specified flag is unset. + +E.g. A flag field of value + + {place_center_x = true, place_center_y=false, place_center_z=true} + +is equivalent to + + {place_center_x = true, noplace_center_y=true, place_center_z=true} + +which is equivalent to + + "place_center_x, noplace_center_y, place_center_z" + +or even + + "place_center_x, place_center_z" + +since, by default, no schematic attributes are set. + +Formspec +-------- +Formspec defines a menu. Currently not much else than inventories are +supported. It is a string, with a somewhat strange format. + +Spaces and newlines can be inserted between the blocks, as is used in the +examples. + +### Examples + +#### Chest + + size[8,9] + list[context;main;0,0;8,4;] + list[current_player;main;0,5;8,4;] + +#### Furnace + + size[8,9] + list[context;fuel;2,3;1,1;] + list[context;src;2,1;1,1;] + list[context;dst;5,1;2,2;] + list[current_player;main;0,5;8,4;] + +#### Minecraft-like player inventory + + size[8,7.5] + image[1,0.6;1,2;player.png] + list[current_player;main;0,3.5;8,4;] + list[current_player;craft;3,0;3,3;] + list[current_player;craftpreview;7,1;1,1;] + +### Elements + +#### `size[,,]` +* Define the size of the menu in inventory slots +* `fixed_size`: `true`/`false` (optional) +* deprecated: `invsize[,;]` + +#### `container[,]` +* Start of a container block, moves all physical elements in the container by (X, Y) +* Must have matching container_end +* Containers can be nested, in which case the offsets are added + (child containers are relative to parent containers) + +#### `container_end[]` +* End of a container, following elements are no longer relative to this container + +#### `list[;;,;,;]` +* Show an inventory list + +#### `list[;;,;,;]` +* Show an inventory list + +#### `listring[;]` +* Allows to create a ring of inventory lists +* Shift-clicking on items in one element of the ring + will send them to the next inventory list inside the ring +* The first occurrence of an element inside the ring will + determine the inventory where items will be sent to + +#### `listring[]` +* Shorthand for doing `listring[;]` + for the last two inventory lists added by list[...] + +#### `listcolors[;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering + +#### `listcolors[;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border + +#### `listcolors[;;;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border +* Sets default background color of tooltips +* Sets default font color of tooltips + +#### `tooltip[;;,]` +* Adds tooltip for an element +* `` tooltip background color as `ColorString` (optional) +* `` tooltip font color as `ColorString` (optional) + +#### `image[,;,;]` +* Show an image +* Position and size units are inventory slots + +#### `item_image[,;,;]` +* Show an inventory image of registered item/node +* Position and size units are inventory slots + +#### `bgcolor[;]` +* Sets background color of formspec as `ColorString` +* If `true`, the background color is drawn fullscreen (does not effect the size of the formspec) + +#### `background[,;,;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: image shall be sized + 8 times 16px times 4 times 16px. + +#### `background[,;,;;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: + image shall be sized 8 times 16px times 4 times 16px +* If `true` the background is clipped to formspec size + (`x` and `y` are used as offset values, `w` and `h` are ignored) + +#### `pwdfield[,;,;;