diff --git a/README.md b/README.md index b1d6d53..131502a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,17 @@ Features: - Items are removed after 900 seconds or the time that is specified by remove_items in minetest.conf (-1 disables it) - Particle effects added -- Dropped items keep sliding when on ice +- Dropped items slide on nodes with {slippery} groups - Items stuck inside solid nodes move to nearest empty space +- Added 'dropped_step(self, pos)' function for special features for dropped item + 'self.node_inside' contains node table that item is inside + 'self.def_inside' contains node definition for above + 'self.node_under' contains node table that is below item + 'self.def_under' contains node definition for above + 'self.age' holds age of dropped item in seconds + 'self.itemstring' contains itemstring e.g. "default:dirt", "default:ice 20" + 'pos' holds position of dropped item + + return false to skip further checks by builtin_item License: MIT diff --git a/init.lua b/init.lua index 36bc645..d5926b4 100644 --- a/init.lua +++ b/init.lua @@ -2,16 +2,14 @@ -- override ice to make slippery for 0.4.16 minetest.override_item("default:ice", { - groups = {cracky = 3, puts_out_fire = 1, cools_lava = 1, slippery = 3}, -}) + groups = {cracky = 3, puts_out_fire = 1, cools_lava = 1, slippery = 3}}) + function core.spawn_item(pos, item) - -- Take item in any format local stack = ItemStack(item) local obj = core.add_entity(pos, "__builtin:item") - -- Don't use obj if it couldn't be added to the map. if obj then obj:get_luaentity():set_item(stack:to_string()) end @@ -19,6 +17,7 @@ function core.spawn_item(pos, item) return obj end + -- If item_entity_ttl is not set, enity will have default life time -- Setting it to -1 disables the feature @@ -26,6 +25,7 @@ local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900 local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 local destroy_item = core.settings:get_bool("destroy_item") ~= false + -- water flow functions by QwertyMine3, edited by TenPlus1 local function to_unit_vector(dir_vector) @@ -159,12 +159,9 @@ core.register_entity(":__builtin:item", { self.itemstring = stack:to_string() if self.itemstring == "" then - -- item not yet known return end - -- Backwards compatibility: old clients use the texture - -- to get the type of the item local itemname = stack:is_known() and stack:get_name() or "unknown" local max_count = stack:get_stack_max() local count = math.min(stack:get_count(), max_count) @@ -219,8 +216,7 @@ core.register_entity(":__builtin:item", { try_merge_with = function(self, own_stack, object, entity) if self.age == entity.age then - -- Can not merge with itself - return false + return false -- Can not merge with itself end local stack = ItemStack(entity.itemstring) @@ -230,8 +226,7 @@ core.register_entity(":__builtin:item", { or own_stack:get_meta() ~= stack:get_meta() or own_stack:get_wear() ~= stack:get_wear() or own_stack:get_free_space() == 0 then - -- Can not merge different or full stack - return false + return false -- Can not merge different or full stack end local count = own_stack:get_count() @@ -274,7 +269,27 @@ core.register_entity(":__builtin:item", { return end - local node = core.get_node_or_nil(pos) + -- get nodes every 1/4 second + self.timer = (self.timer or 0) + dtime + + if self.timer > 0.25 or not self.node_inside then + + self.node_inside = minetest.get_node_or_nil(pos) + self.def_inside = self.node_inside + and core.registered_nodes[self.node_inside.name] + + self.node_under = minetest.get_node_or_nil({ + x = pos.x, + y = pos.y + self.object:get_properties().collisionbox[2] - 0.05, + z = pos.z + }) + self.def_under = self.node_under + and core.registered_nodes[self.node_under.name] + + self.timer = 0 + end + + local node = self.node_inside -- Delete in 'ignore' nodes if node and node.name == "ignore" then @@ -285,8 +300,17 @@ core.register_entity(":__builtin:item", { return end + -- do custom step function + local name = ItemStack(self.itemstring):get_name() or "" + local custom = core.registered_items[name] + and core.registered_items[name].dropped_step + + if custom and custom(self, pos) == false then + return -- skip further checks if false + end + local vel = self.object:get_velocity() - local def = node and core.registered_nodes[node.name] + local def = self.def_inside local is_slippery = false local is_moving = (def and not def.walkable) or vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0 @@ -330,17 +354,14 @@ core.register_entity(":__builtin:item", { self.object:moveto(npos) end + self.node_inside = nil -- force get_node + return end - node = core.get_node_or_nil({ - x = pos.x, - y = pos.y + self.object:get_properties().collisionbox[2] - 0.05, - z = pos.z - }) - - def = node and core.registered_nodes[node.name] + node = self.node_under + def = self.def_under if def and def.walkable then @@ -358,6 +379,7 @@ core.register_entity(":__builtin:item", { y = 0, z = -vel.z * slip_factor }) + elseif vel.y == 0 then is_moving = false end @@ -365,8 +387,7 @@ core.register_entity(":__builtin:item", { if self.moving_state == is_moving and self.slippery_state == is_slippery then - -- Do not update anything until the moving state changes - return + return -- No further updates until moving state changes end self.moving_state = is_moving diff --git a/init.lua_ b/init.lua_ new file mode 100644 index 0000000..36bc645 --- /dev/null +++ b/init.lua_ @@ -0,0 +1,431 @@ +-- Minetest: builtin/item_entity.lua + +-- override ice to make slippery for 0.4.16 +minetest.override_item("default:ice", { + groups = {cracky = 3, puts_out_fire = 1, cools_lava = 1, slippery = 3}, +}) + +function core.spawn_item(pos, item) + + -- Take item in any format + local stack = ItemStack(item) + local obj = core.add_entity(pos, "__builtin:item") + + -- Don't use obj if it couldn't be added to the map. + if obj then + obj:get_luaentity():set_item(stack:to_string()) + end + + return obj +end + +-- If item_entity_ttl is not set, enity will have default life time +-- Setting it to -1 disables the feature + +local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900 +local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 +local destroy_item = core.settings:get_bool("destroy_item") ~= false + +-- water flow functions by QwertyMine3, edited by TenPlus1 +local function to_unit_vector(dir_vector) + + local inv_roots = { + [0] = 1, + [1] = 1, + [2] = 0.70710678118655, + [4] = 0.5, + [5] = 0.44721359549996, + [8] = 0.35355339059327 + } + + local sum = dir_vector.x * dir_vector.x + dir_vector.z * dir_vector.z + + return { + x = dir_vector.x * inv_roots[sum], + y = dir_vector.y, + z = dir_vector.z * inv_roots[sum] + } +end + + +local function node_ok(pos) + + local node = minetest.get_node_or_nil(pos) + + if node and minetest.registered_nodes[node.name] then + return node + end + + return minetest.registered_nodes["default:dirt"] +end + + +local function quick_flow_logic(node, pos_testing, direction) + + local node_testing = node_ok(pos_testing) + + if minetest.registered_nodes[node_testing.name].liquidtype ~= "flowing" + and minetest.registered_nodes[node_testing.name].liquidtype ~= "source" then + return 0 + end + + local param2_testing = node_testing.param2 + + if param2_testing < node.param2 then + + if (node.param2 - param2_testing) > 6 then + return -direction + else + return direction + end + + elseif param2_testing > node.param2 then + + if (param2_testing - node.param2) > 6 then + return direction + else + return -direction + end + end + + return 0 +end + + +local function quick_flow(pos, node) + + if not minetest.registered_nodes[node.name].groups.liquid then + return {x = 0, y = 0, z = 0} + end + + local x, z = 0, 0 + + x = x + quick_flow_logic(node, {x = pos.x - 1, y = pos.y, z = pos.z},-1) + x = x + quick_flow_logic(node, {x = pos.x + 1, y = pos.y, z = pos.z}, 1) + z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z - 1},-1) + z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z + 1}, 1) + + return to_unit_vector({x = x, y = 0, z = z}) +end +-- END water flow functions + + +-- particle effects for when item is destroyed +local function add_effects(pos) + + minetest.add_particlespawner({ + amount = 1, + time = 0.25, + minpos = pos, + maxpos = pos, + minvel = {x = -1, y = 2, z = -1}, + maxvel = {x = 1, y = 4, z = 1}, + minacc = {x = 0, y = 0, z = 0}, + maxacc = {x = 0, y = 0, z = 0}, + minexptime = 1, + maxexptime = 3, + minsize = 1, + maxsize = 4, + texture = "tnt_smoke.png", + }) +end + + +core.register_entity(":__builtin:item", { + + initial_properties = { + hp_max = 1, + physical = true, + collide_with_objects = false, + collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3}, + visual = "wielditem", + visual_size = {x = 0.4, y = 0.4}, + textures = {""}, + spritediv = {x = 1, y = 1}, + initial_sprite_basepos = {x = 0, y = 0}, + is_visible = false, + infotext = "", + }, + + itemstring = "", + moving_state = true, + slippery_state = false, + age = 0, + + set_item = function(self, item) + + local stack = ItemStack(item or self.itemstring) + + self.itemstring = stack:to_string() + + if self.itemstring == "" then + -- item not yet known + return + end + + -- Backwards compatibility: old clients use the texture + -- to get the type of the item + local itemname = stack:is_known() and stack:get_name() or "unknown" + local max_count = stack:get_stack_max() + local count = math.min(stack:get_count(), max_count) + local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3) + local coll_height = size * 0.75 + + self.object:set_properties({ + is_visible = true, + visual = "wielditem", + textures = {itemname}, + visual_size = {x = size, y = size}, + collisionbox = {-size, -coll_height, -size, + size, coll_height, size}, + selectionbox = {-size, -size, -size, size, size, size}, + automatic_rotate = 0.314 / size, + wield_item = self.itemstring, + infotext = core.registered_items[itemname].description + }) + + end, + + get_staticdata = function(self) + + return core.serialize({ + itemstring = self.itemstring, + age = self.age, + dropped_by = self.dropped_by + }) + end, + + on_activate = function(self, staticdata, dtime_s) + + if string.sub(staticdata, 1, string.len("return")) == "return" then + + local data = core.deserialize(staticdata) + + if data and type(data) == "table" then + self.itemstring = data.itemstring + self.age = (data.age or 0) + dtime_s + self.dropped_by = data.dropped_by + end + else + self.itemstring = staticdata + end + + self.object:set_armor_groups({immortal = 1}) + self.object:set_velocity({x = 0, y = 2, z = 0}) + self.object:set_acceleration({x = 0, y = -gravity, z = 0}) + self:set_item() + end, + + try_merge_with = function(self, own_stack, object, entity) + + if self.age == entity.age then + -- Can not merge with itself + return false + end + + local stack = ItemStack(entity.itemstring) + local name = stack:get_name() + + if own_stack:get_name() ~= name + or own_stack:get_meta() ~= stack:get_meta() + or own_stack:get_wear() ~= stack:get_wear() + or own_stack:get_free_space() == 0 then + -- Can not merge different or full stack + return false + end + + local count = own_stack:get_count() + local total_count = stack:get_count() + count + local max_count = stack:get_stack_max() + + if total_count > max_count then + return false + end + + -- Merge the remote stack into this one + local pos = object:get_pos() + pos.y = pos.y + ((total_count - count) / max_count) * 0.15 + + self.object:move_to(pos) + self.age = 0 -- Handle as new entity + + own_stack:set_count(total_count) + self:set_item(own_stack) + + entity.itemstring = "" + object:remove() + + return true + end, + + on_step = function(self, dtime) + + local pos = self.object:get_pos() + + self.age = self.age + dtime + + if time_to_live > 0 and self.age > time_to_live then + + self.itemstring = "" + self.object:remove() + + add_effects(pos) + + return + end + + local node = core.get_node_or_nil(pos) + + -- Delete in 'ignore' nodes + if node and node.name == "ignore" then + + self.itemstring = "" + self.object:remove() + + return + end + + local vel = self.object:get_velocity() + local def = node and core.registered_nodes[node.name] + local is_slippery = false + local is_moving = (def and not def.walkable) or + vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0 + + -- destroy item when dropped into lava (if enabled) + if destroy_item and def and def.groups and def.groups.lava then + + minetest.sound_play("builtin_item_lava", { + pos = pos, + max_hear_distance = 6, + gain = 0.5 + }) + + self.itemstring = "" + self.object:remove() + + add_effects(pos) + + return + end + + -- water flowing + if def and def.liquidtype == "flowing" then + + local vec = quick_flow(pos, node) + local v = self.object:get_velocity() + + self.object:set_velocity({x = vec.x, y = v.y, z = vec.z}) + + return + end + + -- item inside block, move to vacant space + if def and not def.liquid + and node.name ~= "air" + and def.drawtype == "normal" then + + local npos = minetest.find_node_near(pos, 1, "air") + + if npos then + self.object:moveto(npos) + end + + return + end + + node = core.get_node_or_nil({ + x = pos.x, + y = pos.y + self.object:get_properties().collisionbox[2] - 0.05, + z = pos.z + }) + + def = node and core.registered_nodes[node.name] + + + if def and def.walkable then + + local slippery = core.get_item_group(node.name, "slippery") + + is_slippery = slippery ~= 0 + + if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then + + -- Horizontal deceleration + local slip_factor = 4.0 / (slippery + 4) + + self.object:set_acceleration({ + x = -vel.x * slip_factor, + y = 0, + z = -vel.z * slip_factor + }) + elseif vel.y == 0 then + is_moving = false + end + end + + if self.moving_state == is_moving + and self.slippery_state == is_slippery then + -- Do not update anything until the moving state changes + return + end + + self.moving_state = is_moving + self.slippery_state = is_slippery + + if is_moving then + self.object:set_acceleration({x = 0, y = -gravity, z = 0}) + else + self.object:set_acceleration({x = 0, y = 0, z = 0}) + self.object:set_velocity({x = 0, y = 0, z = 0}) + end + + --Only collect items if not moving + if is_moving then + return + end + + -- Collect the items around to merge with + local own_stack = ItemStack(self.itemstring) + + if own_stack:get_free_space() == 0 then + return + end + + local objects = core.get_objects_inside_radius(pos, 1.0) + + for k, obj in pairs(objects) do + + local entity = obj:get_luaentity() + + if entity and entity.name == "__builtin:item" then + + if self:try_merge_with(own_stack, obj, entity) then + + own_stack = ItemStack(self.itemstring) + + if own_stack:get_free_space() == 0 then + return + end + end + end + end + end, + + on_punch = function(self, hitter) + + local inv = hitter:get_inventory() + + if inv and self.itemstring ~= "" then + + local left = inv:add_item("main", self.itemstring) + + if left and not left:is_empty() then + self:set_item(left) + return + end + end + + self.itemstring = "" + self.object:remove() + end, +})