Executing all on-dig callbacks "properly" with a fake player

This commit is contained in:
FaceDeer 2019-08-25 17:16:23 -06:00
parent 426cd4d82d
commit b90e5713aa
4 changed files with 217 additions and 15 deletions

145
class_fakeplayer.lua Normal file

@ -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

@ -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

@ -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")

@ -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