diff --git a/class_fakeplayer.lua b/class_fakeplayer.lua new file mode 100644 index 0000000..7976cb4 --- /dev/null +++ b/class_fakeplayer.lua @@ -0,0 +1,145 @@ +-- The purpose of this class is to have something that can be passed into callbacks that +-- demand a "Player" object as a parameter and hopefully prevent the mods that have +-- registered with those callbacks from crashing on a nil dereference or bad function +-- call. This is not supposed to be a remotely functional thing, it's just supposed +-- to provide dummy methods and return values of the correct data type for anything that +-- might ignore the false "is_player()" return and go ahead and try to use this thing +-- anyway. + +-- I'm trying to patch holes in bad mod programming, essentially. If a mod is so badly +-- programmed that it crashes anyway there's not a lot else I can do on my end of things. + +DigtronFakePlayer = {} +DigtronFakePlayer.__index = DigtronFakePlayer + +local function return_value(x) + return (function() return x end) +end + +local function return_nil() + return nil +end + +local function return_empty_string() + return "" +end + +local function return_zero() + return 0 +end + +local function return_empty_table() + return {} +end + +function DigtronFakePlayer.update(self, pos, player_name) + self.is_fake_player = ":digtron " .. player_name + self.get_pos = return_value(pos) +end + +function DigtronFakePlayer.create(pos, player_name) + local self = {} + setmetatable(self, DigtronFakePlayer) + + self.is_fake_player = ":digtron " .. player_name + + -- ObjectRef + self.get_pos = return_value(pos) + self.set_pos = return_nil + self.move_to = return_nil + self.punch = return_nil + self.right_click = return_nil + self.get_hp = return_value(10) + self.set_hp = return_nil + self.get_inventory = return_nil -- returns an `InvRef` + self.get_wield_list = return_empty_string + self.get_wield_index = return_value(1) + self.get_wielded_item = return_value(ItemStack(nil)) + self.set_wielded_item = return_value(false) + self.set_armor_groups = return_nil + self.get_armor_groups = return_empty_table + self.set_animation = return_nil + self.get_animation = return_nil -- a set of values, maybe important? + self.set_attach = return_nil + self.get_attach = return_nil + self.set_detach = return_nil + self.set_bone_position = return_nil + self.get_bone_position = return_nil + self.set_properties = return_nil + self.get_properties = return_empty_table + + self.is_player = return_value(false) + + self.get_nametag_attributes = return_empty_table + self.set_nametag_attributes = return_nil + + --LuaEntitySAO + self.set_velocity = return_nil + self.get_velocity = return_value({x=0,y=0,z=0}) + self.set_acceleration = return_nil + self.get_acceleration = return_value({x=0,y=0,z=0}) + self.set_yaw = return_nil + self.get_yaw = return_zero + self.set_texture_mod = return_nil + self.get_texture_mod = return_nil -- maybe important? + self.set_sprite = return_nil + --self.get_entity_name` (**Deprecated**: Will be removed in a future version) + self.get_luaentity = return_nil + + -- Player object + + self.get_player_name = return_empty_string + self.get_player_velocity = return_nil + self.get_look_dir = return_value({x=0,y=1,z=0}) + self.get_look_horizontal = return_zero + self.set_look_horizontal = return_nil + self.get_look_vertical = return_zero + self.set_look_vertical = return_nil + + --self.get_look_pitch`: pitch in radians - Deprecated as broken. Use `get_look_vertical` + --self.get_look_yaw`: yaw in radians - Deprecated as broken. Use `get_look_horizontal` + --self.set_look_pitch(radians)`: sets look pitch - Deprecated. Use `set_look_vertical`. + --self.set_look_yaw(radians)`: sets look yaw - Deprecated. Use `set_look_horizontal`. + self.get_breath = return_value(10) + self.set_breath = return_nil + self.get_attribute = return_nil + self.set_attribute = return_nil + + self.set_inventory_formspec = return_nil + self.get_inventory_formspec = return_empty_string + self.get_player_control = return_value({jump=false, right=false, left=false, LMB=false, RMB=false, sneak=false, aux1=false, down=false, up=false} ) + self.get_player_control_bits = return_zero + + self.set_physics_override = return_nil + self.get_physics_override = return_value({speed = 1, jump = 1, gravity = 1, sneak = true, sneak_glitch = false, new_move = true,}) + + + self.hud_add = return_nil + self.hud_remove = return_nil + self.hud_change = return_nil + self.hud_get = return_nil -- possibly important return value? + self.hud_set_flags = return_nil + self.hud_get_flags = return_value({ hotbar=true, healthbar=true, crosshair=true, wielditem=true, breathbar=true, minimap=true }) + self.hud_set_hotbar_itemcount = return_nil + self.hud_get_hotbar_itemcount = return_zero + self.hud_set_hotbar_image = return_nil + self.hud_get_hotbar_image = return_empty_string + self.hud_set_hotbar_selected_image = return_nil + self.hud_get_hotbar_selected_image = return_empty_string + self.set_sky = return_nil + self.get_sky = return_empty_table -- may need members on this table + + self.set_clouds = return_nil + self.get_clouds = return_value({density = 0, color = "#fff0f0e5", ambient = "#000000", height = 120, thickness = 16, speed = {x=0, y=-2}}) + + self.override_day_night_ratio = return_nil + self.get_day_night_ratio = return_nil + + self.set_local_animation = return_nil + self.get_local_animation = return_empty_table + + self.set_eye_offset = return_nil + self.get_eye_offset = return_value({x=0,y=0,z=0},{x=0,y=0,z=0}) + + return self +end \ No newline at end of file diff --git a/functions.lua b/functions.lua index 6caa4fc..29ea0c2 100644 --- a/functions.lua +++ b/functions.lua @@ -30,8 +30,7 @@ local create_new_id = function() while mod_meta:get_string(digtron_id..":layout") ~= "" do digtron_id = "digtron" .. tostring(math.random(1, 2^21)) end - local inv = minetest.create_detached_inventory(digtron_id, detached_inventory_callbacks) - return digtron_id, inv + return digtron_id end -- Deletes a Digtron record. Note: just throws everything away, this is not digtron.disassemble. @@ -273,7 +272,8 @@ digtron.assemble = function(root_pos, player_name) -- use this info, but it's small and IMO not worth the complexity. get_all_digtron_nodes(root_pos, digtron_nodes, digtron_adjacent, player_name) - local digtron_id, digtron_inv = create_new_id(root_pos) + local digtron_id = create_new_id(root_pos) + local digtron_inv = retrieve_inventory(digtron_id) local layout = {} @@ -536,7 +536,7 @@ digtron.is_buildable_to = function(digtron_id, root_pos, player_name, ignore_nod if not ( (node_def and node_def.buildable_to) or ignore_hashes[node_hash]) or - minetest.is_protected(target_pos, player_name) + minetest.is_protected(node_pos, player_name) then if return_immediately_on_failure then return false -- no need to test further, don't return node positions @@ -656,27 +656,80 @@ local predict_dig = function(digtron_id, player_name) return leftovers, dug_positions, cost end +-- Removes nodes and records node info so on-dig callbacks can be called later +local get_and_remove_nodes = function(nodes_to_dig) + local ret = {} + for _, pos in ipairs(nodes_to_dig) do + local record = {} + record.pos = pos + record.node = minetest.get_node(pos) + record.meta = minetest.get_meta(pos) + minetest.remove_node(pos) + table.insert(ret, record) + end + return ret +end + digtron.execute_cycle = function(digtron_id, player_name) local leftovers, nodes_to_dig, cost = predict_dig(digtron_id, player_name) - local root_pos = retrieve_pos(digtron_id) - local root_node = minetest.get_node(root_pos) - local new_pos = vector.add(root_pos, cardinal_dirs[facedir_to_dir_index(root_node.param2)]) - local buildable_to, succeeded, failed = digtron.is_buildable_to(digtron_id, new_pos, player_name, nodes_to_dig, return_immediately_on_failure) + local old_root_pos = retrieve_pos(digtron_id) + local root_node = minetest.get_node(old_root_pos) + local new_root_pos = vector.add(old_root_pos, cardinal_dirs[facedir_to_dir_index(root_node.param2)]) + local buildable_to, succeeded, failed = digtron.is_buildable_to(digtron_id, new_root_pos, player_name, nodes_to_dig) if buildable_to then + digtron.fake_player:update(old_root_pos, player_name) local removed = digtron.remove_from_world(digtron_id, player_name) - minetest.bulk_set_node(nodes_to_dig, {name="air"}) - digtron.build_to_world(digtron_id, new_pos, player_name) - minetest.sound_play("digtron_construction", {gain = 0.5, pos=new_pos}) + + local nodes_dug = get_and_remove_nodes(nodes_to_dig) + + local nodes_dug_count = #nodes_to_dig + if nodes_dug_count > 0 then + local pluralized = "node" + if nodes_dug_count > 1 then + pluralized = "nodes" + end + minetest.log("action", nodes_dug_count .. " " .. pluralized .. " dug by " + .. digtron_id .. " near ".. minetest.pos_to_string(new_root_pos) + .. " operated by by " .. player_name) + end + + digtron.build_to_world(digtron_id, new_root_pos, player_name) + minetest.sound_play("digtron_construction", {gain = 0.5, pos=new_root_pos}) + + -- Don't need to do fancy callback checking for digtron nodes since I made all those + -- nodes and I know they don't have anything that needs to be done for them. + -- Just check for falling nodes. for _, removed_pos in ipairs(removed) do minetest.check_for_falling(removed_pos) end - for _, dug_pos in ipairs(nodes_to_dig) do - -- TODO: other on-dug callbacks - minetest.check_for_falling(dug_pos) + + -- Execute various on-dig callbacks for the nodes that Digtron dug + -- Must be called after digtron.build_to_world because it triggers falling nodes + for _, dug_data in ipairs(nodes_dug) do + local old_pos = dug_data.pos + local old_node = dug_data.node + local old_name = old_node.name + + for _, callback in ipairs(minetest.registered_on_dignodes) do + -- Copy pos and node because callback can modify them + local pos_copy = {x=old_pos.x, y=old_pos.y, z=old_pos.z} + local oldnode_copy = {name=old_name, param1=old_node.param1, param2=old_node.param2} + callback(pos_copy, oldnode_copy, digtron.fake_player) + end + + local old_def = minetest.registered_nodes[old_name] + if old_def ~= nil then + local old_after_dig = old_def.after_dig_node + if old_after_dig ~= nil then + old_after_dig(old_pos, old_node, dug_data.meta, digtron.fake_player) + end + end end + commit_predictive_inventory(digtron_id) else + clear_predictive_inventory(digtron_id) digtron.show_buildable_nodes({}, failed) minetest.sound_play("digtron_squeal", {gain = 0.5, pos=new_pos}) end diff --git a/init.lua b/init.lua index ca14db4..52393aa 100644 --- a/init.lua +++ b/init.lua @@ -5,6 +5,10 @@ digtron.mod_meta = minetest.get_mod_storage() local modpath = minetest.get_modpath(minetest.get_current_modname()) + +dofile(modpath .. "/class_fakeplayer.lua") +digtron.fake_player = DigtronFakePlayer.create({x=0,y=0,z=0}, "fake_player") -- since we only need one fake player at a time and it doesn't retain useful state, create a global one and just update it as needed. + dofile(modpath.."/config.lua") dofile(modpath.."/entities.lua") dofile(modpath.."/functions.lua") diff --git a/inventories.lua b/inventories.lua index aa2cdda..b902a7b 100644 --- a/inventories.lua +++ b/inventories.lua @@ -137,7 +137,7 @@ local clear_predictive_inventory = function(digtron_id) minetest.remove_detached_inventory("digtron_predictive_"..digtron_id) predictive_inventory[digtron_id] = nil - if not next(predictive_inventory) then + if next(predictive_inventory) ~= nil then minetest.log("warning", "[Digtron] multiple predictive inventories were in existence, this shouldn't be happening. File an issue with Digtron programmers.") end end