diff --git a/.luacheckrc b/.luacheckrc index 6d89a3f..b356748 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,5 +1,7 @@ read_globals = { + "vector", + "screwdriver", "minetest", "default", "pipeworks", diff --git a/init.lua b/init.lua index d07ed1d..fe856c1 100644 --- a/init.lua +++ b/init.lua @@ -64,20 +64,20 @@ minetest.register_craft({ } }) --- former submods -if minetest.is_yes(minetest.setting_get("digilines_enable_inventory") or true) then +-- For minetest 0.4 support returned nil are also tested: ~= false +if minetest.settings:get_bool("digilines_enable_inventory", true) ~= false then dofile(modpath .. "/inventory.lua") end -if minetest.is_yes(minetest.setting_get("digilines_enable_lcd") or true) then +if minetest.settings:get_bool("digilines_enable_lcd", true) ~= false then dofile(modpath .. "/lcd.lua") end -if minetest.is_yes(minetest.setting_get("digilines_enable_lightsensor") or true) then +if minetest.settings:get_bool("digilines_enable_lightsensor", true) ~= false then dofile(modpath .. "/lightsensor.lua") end -if minetest.is_yes(minetest.setting_get("digilines_enable_rtc") or true) then +if minetest.settings:get_bool("digilines_enable_rtc", true) ~= false then dofile(modpath .. "/rtc.lua") end diff --git a/internal.lua b/internal.lua index 2528f35..05c93fa 100644 --- a/internal.lua +++ b/internal.lua @@ -1,6 +1,7 @@ function digilines.getspec(node) - if not minetest.registered_nodes[node.name] then return false end - return minetest.registered_nodes[node.name].digiline + local def = minetest.registered_nodes[node.name] + if not def then return false end + return def.digilines or def.digiline end function digilines.importrules(spec, node) @@ -86,6 +87,12 @@ local function queue_dequeue(queue) end function digilines.transmit(pos, channel, msg, checked) + local checkedID = minetest.hash_node_position(pos) + if checked[checkedID] then + return + end + checked[checkedID] = true + digilines.vm_begin() local queue = queue_new() queue_enqueue(queue, pos) diff --git a/inventory.lua b/inventory.lua index 693f882..86b1536 100644 --- a/inventory.lua +++ b/inventory.lua @@ -1,33 +1,56 @@ local pipeworks_enabled = minetest.get_modpath("pipeworks") ~= nil -local function sendMessage(pos, msg, channel) - if channel == nil then - channel = minetest.get_meta(pos):get_string("channel") - end - digilines.receptor_send(pos,digilines.rules.default,channel,msg) +-- Sends a message onto the Digilines network. +-- pos: the position of the Digilines chest node. +-- action: the action string indicating what happened. +-- stack: the ItemStack that the action acted on (optional). +-- from_slot: the slot number that is taken from (optional). +-- to_slot: the slot number that is put into (optional). +-- side: which side of the chest the action occurred (optional). +local function send_message(pos, action, stack, from_slot, to_slot, side) + local channel = minetest.get_meta(pos):get_string("channel") + local msg = { + action = action, + stack = stack and stack:to_table(), + from_slot = from_slot, + to_slot = to_slot, + -- Duplicate the vector in case the caller expects it not to change. + side = side and vector.new(side) + } + digilines.receptor_send(pos, digilines.rules.default, channel, msg) end -local function maybeString(stack) - if type(stack)=='string' then return stack - elseif type(stack)=='table' then return dump(stack) - else return stack:to_string() +-- Checks if the inventory has become empty and, if so, sends an empty message. +local function check_empty(pos) + if minetest.get_meta(pos):get_inventory():is_empty("main") then + send_message(pos, "empty") end end -local function can_insert(pos, stack) - local can = minetest.get_meta(pos):get_inventory():room_for_item("main", stack) - if can then - sendMessage(pos,"put "..maybeString(stack)) - else - -- overflow and lost means that items are gonna be out as entities :/ - sendMessage(pos,"lost "..maybeString(stack)) +-- Checks if the inventory has become full for a particular type of item and, +-- if so, sends a full message. +local function check_full(pos, stack) + local one_item_stack = ItemStack(stack) + one_item_stack:set_count(1) + if not minetest.get_meta(pos):get_inventory():room_for_item("main", one_item_stack) then + send_message(pos, "full", one_item_stack) end - return can end local tubeconn = pipeworks_enabled and "^pipeworks_tube_connection_wooden.png" or "" local tubescan = pipeworks_enabled and function(pos) pipeworks.scan_for_tube_objects(pos) end or nil +-- A place to remember things from allow_metadata_inventory_put to +-- on_metadata_inventory_put. This is a hack due to issue +-- minetest/minetest#6534 that should be removed once that’s fixed. +local last_inventory_put_index +local last_inventory_put_stack + +-- A place to remember things from allow_metadata_inventory_take to +-- tube.remove_items. This is a hack due to issue minetest-mods/pipeworks#205 +-- that should be removed once that’s fixed. +local last_inventory_take_index + minetest.register_alias("digilines_inventory:chest", "digilines:chest") minetest.register_node("digilines:chest", { description = "Digiline Chest", @@ -86,63 +109,203 @@ minetest.register_node("digilines:chest", { return not pipeworks.connects.facingFront(i,param2) end, input_inventory = "main", - can_insert = function(pos, _, stack) - return can_insert(pos, stack) - end, - insert_object = function(pos, _, stack) - local inv = minetest.get_meta(pos):get_inventory() - local leftover = inv:add_item("main", stack) - local count = leftover:get_count() - if count == 0 then - local derpstack = stack:get_name()..' 1' - if not inv:room_for_item("main", derpstack) then - -- when you can't put a single more of whatever you just put, - -- you'll get a put for it, then a full - sendMessage(pos,"full "..maybeString(stack)..' '..tostring(count)) - end - else - -- this happens when the chest has received two stacks in a row and - -- filled up exactly with the first one. - -- You get a put for the first stack, a put for the second - -- and then a overflow with the first in stack and the second in leftover - -- and NO full? - sendMessage(pos,"overflow "..maybeString(stack)..' '..tostring(count)) + can_insert = function(pos, _, stack, direction) + local ret = minetest.get_meta(pos):get_inventory():room_for_item("main", stack) + if not ret then + -- The stack cannot be accepted. It will never be passed to + -- insert_object, but it should be reported as a toverflow. + -- Here, direction = direction item is moving, which is into + -- side. + local side = vector.multiply(direction, -1) + send_message(pos, "toverflow", stack, nil, nil, side) end - return leftover + return ret + end, + insert_object = function(pos, _, original_stack, direction) + -- Here, direction = direction item is moving, which is into side. + local side = vector.multiply(direction, -1) + local inv = minetest.get_meta(pos):get_inventory() + local inv_contents = inv:get_list("main") + local any_put = false + local stack = original_stack + local stack_name = stack:get_name() + local stack_count = stack:get_count() + -- Walk the inventory, adding items to existing stacks of the same + -- type. + for i = 1, #inv_contents do + local existing_stack = inv_contents[i] + if not existing_stack:is_empty() and existing_stack:get_name() == stack_name then + local leftover = existing_stack:add_item(stack) + local leftover_count = leftover:get_count() + if leftover_count ~= stack_count then + -- We put some items into the slot. Update the slot in + -- the inventory, tell Digilines listeners about it, + -- and keep looking for the a place to put the + -- leftovers if any. + any_put = true + inv:set_stack("main", i, existing_stack) + local stack_that_was_put + if leftover_count == 0 then + stack_that_was_put = stack + else + stack_that_was_put = ItemStack(stack) + stack_that_was_put:set_count(stack_count - leftover_count) + end + send_message(pos, "tput", stack_that_was_put, nil, i, side) + stack = leftover + stack_count = leftover_count + if stack_count == 0 then + break + end + end + end + end + if stack_count ~= 0 then + -- Walk the inventory, adding items to empty slots. + for i = 1, #inv_contents do + local existing_stack = inv_contents[i] + if existing_stack:is_empty() then + local leftover = existing_stack:add_item(stack) + local leftover_count = leftover:get_count() + if leftover_count ~= stack_count then + -- We put some items into the slot. Update the slot in + -- the inventory, tell Digilines listeners about it, + -- and keep looking for the a place to put the + -- leftovers if any. + any_put = true + inv:set_stack("main", i, existing_stack) + local stack_that_was_put + if leftover_count == 0 then + stack_that_was_put = stack + else + stack_that_was_put = ItemStack(stack) + stack_that_was_put:set_count(stack_count - leftover_count) + end + send_message(pos, "tput", stack_that_was_put, nil, i, side) + stack = leftover + stack_count = leftover_count + if stack_count == 0 then + break + end + end + end + end + end + if any_put then + check_full(pos, original_stack) + end + if stack_count ~= 0 then + -- Some items could not be added and bounced back. Report them. + send_message(pos, "toverflow", stack, nil, nil, side) + end + return stack + end, + remove_items = function(pos, _, stack, dir, count) + -- Here, stack is the ItemStack in our own inventory that is being + -- pulled from, NOT the stack that is actually pulled out. + -- Combining it with count gives the stack that is pulled out. + -- Also, note that Pipeworks doesn’t pass the index to this + -- function, so we use the one recorded in + -- allow_metadata_inventory_take; because we don’t implement + -- tube.can_remove, Pipeworks will call + -- allow_metadata_inventory_take instead and will pass it the + -- index. + local taken = stack:take_item(count) + minetest.get_meta(pos):get_inventory():set_stack("main", last_inventory_take_index, stack) + send_message(pos, "ttake", taken, last_inventory_take_index, nil, dir) + check_empty(pos) + return taken end, }, - allow_metadata_inventory_put = function(pos, _, _, stack) - if not can_insert(pos, stack) then - sendMessage(pos,"uoverflow "..maybeString(stack)) - end + allow_metadata_inventory_put = function(pos, _, index, stack) + -- Remember what was in the target slot before the put; see + -- on_metadata_inventory_put for why we care. + last_inventory_put_index = index + last_inventory_put_stack = minetest.get_meta(pos):get_inventory():get_stack("main", index) return stack:get_count() end, - on_metadata_inventory_move = function(pos, _, _, _, _, _, player) + allow_metadata_inventory_take = function(_, _, index, stack) + -- Remember the index value; see tube.remove_items for why we care. + last_inventory_take_index = index + return stack:get_count() + end, + on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + -- See what would happen if we were to move the items back from in the + -- opposite direction. In the event of a normal move, this must + -- succeed, because a normal move subtracts some items from the from + -- stack and adds them to the to stack; the two stacks naturally must + -- be compatible and so the reverse operation must succeed. However, if + -- the user *swaps* the two stacks instead, then due to issue + -- minetest/minetest#6534, this function is only called once; however, + -- when it is called, the stack that used to be in the to stack has + -- already been moved to the from stack, so we can detect the situation + -- by the fact that the reverse move will fail due to the from stack + -- being incompatible with its former contents. + local inv = minetest.get_meta(pos):get_inventory() + local from_stack = inv:get_stack("main", from_index) + local to_stack = inv:get_stack("main", to_index) + local reverse_move_stack = ItemStack(to_stack) + reverse_move_stack:set_count(count) + local swapped = from_stack:add_item(reverse_move_stack):get_count() == count + if swapped then + local channel = minetest.get_meta(pos):get_string("channel") + to_stack:set_count(count) + local msg = { + action = "uswap", + -- The slot and stack do not match because this function is + -- called after the action has taken place, but the Digilines + -- message is from the perspective of a viewer who hasn’t + -- observed the movement yet. + x_stack = to_stack:to_table(), + x_slot = from_index, + y_stack = from_stack:to_table(), + y_slot = to_index, + } + digilines.receptor_send(pos, digilines.rules.default, channel, msg) + else + to_stack:set_count(count) + send_message(pos, "umove", to_stack, from_index, to_index) + end minetest.log("action", player:get_player_name().." moves stuff in chest at "..minetest.pos_to_string(pos)) end, - on_metadata_inventory_put = function(pos, _, _, stack, player) - local channel = minetest.get_meta(pos):get_string("channel") - local send = function(msg) - sendMessage(pos,msg,channel) - end - -- direction is only for furnaces - -- as the item has already been put, can_insert should return false if the chest is now full. - local derpstack = stack:get_name()..' 1' - if can_insert(pos,derpstack) then - send("uput "..maybeString(stack)) + on_metadata_inventory_put = function(pos, _, index, stack, player) + -- Get what was in the target slot before the put; it has disappeared + -- by now (been replaced by the result of the put action) but we saved + -- it in allow_metadata_inventory_put. This should always work + -- (allow_metadata_inventory_put should AFAICT always be called + -- immediately before on_metadata_inventory_put), but in case of + -- something weird happening, just fall back to using an empty + -- ItemStack rather than crashing. + local old_stack + if last_inventory_put_index == index then + old_stack = last_inventory_put_stack + last_inventory_put_index = nil + last_inventory_put_stack = nil else - send("ufull "..maybeString(stack)) + old_stack = ItemStack(nil) end + -- If the player tries to place a stack into an inventory, there’s + -- already a stack there, and the existing stack is either of a + -- different item type or full, then obviously the stacks can’t be + -- merged; instead the stacks are swapped. This information is not + -- reported to mods (Minetest core neither tells us that a particular + -- action was a swap, nor tells us a take followed by a put). In core, + -- the condition for swapping is that you try to add the new stack to + -- the existing stack and the leftovers are as big as the original + -- stack to put. Replicate that logic here using the old stack saved in + -- allow_metadata_inventory_put. If a swap happened, report it to the + -- Digilines network as a utake followed by a uput. + local leftovers = old_stack:add_item(stack) + if leftovers:get_count() == stack:get_count() then + send_message(pos, "utake", old_stack, index) + end + send_message(pos, "uput", stack, nil, index) + check_full(pos, stack) minetest.log("action", player:get_player_name().." puts stuff into chest at "..minetest.pos_to_string(pos)) end, - on_metadata_inventory_take = function(pos, listname, _, stack, player) - local meta = minetest.get_meta(pos) - local channel = meta:get_string("channel") - local inv = meta:get_inventory() - if inv:is_empty(listname) then - sendMessage(pos, "empty", channel) - end - sendMessage(pos,"utake "..maybeString(stack)) + on_metadata_inventory_take = function(pos, _, index, stack, player) + send_message(pos, "utake", stack, index) + check_empty(pos) minetest.log("action", player:get_player_name().." takes stuff from chest at "..minetest.pos_to_string(pos)) end }) diff --git a/lcd.lua b/lcd.lua index ce17dac..37ce3e0 100644 --- a/lcd.lua +++ b/lcd.lua @@ -128,16 +128,59 @@ local clearscreen = function(pos) end end +local set_texture = function(ent) + local meta = minetest.get_meta(ent.object:get_pos()) + local text = meta:get_string("text") + ent.object:set_properties({ + textures = { + generate_texture(create_lines(text)) + } + }) +end + +local get_entity = function(pos) + local lcd_entity + local objects = minetest.get_objects_inside_radius(pos, 0.5) + for _, o in ipairs(objects) do + local o_entity = o:get_luaentity() + if o_entity and o_entity.name == "digilines_lcd:text" then + if not lcd_entity then + lcd_entity = o_entity + else + -- Remove extras, if any + o:remove() + end + end + end + return lcd_entity +end + +local rotate_text = function(pos, param) + local entity = get_entity(pos) + if not entity then + return + end + local lcd_info = lcds[param or minetest.get_node(pos).param2] + if not lcd_info then + return + end + entity.object:set_pos(vector.add(pos, lcd_info.delta)) + entity.object:set_yaw(lcd_info.yaw or 0) +end + local prepare_writing = function(pos) - local lcd_info = lcds[minetest.get_node(pos).param2] - if lcd_info == nil then return end - local text = minetest.add_entity( - {x = pos.x + lcd_info.delta.x, - y = pos.y + lcd_info.delta.y, - z = pos.z + lcd_info.delta.z}, "digilines_lcd:text") - text:setyaw(lcd_info.yaw or 0) - --* text:setpitch(lcd_info.yaw or 0) - return text + local entity = get_entity(pos) + if entity then + set_texture(entity) + rotate_text(pos) + end +end + +local spawn_entity = function(pos) + if not get_entity(pos) then + minetest.add_entity(pos, "digilines_lcd:text") + rotate_text(pos) + end end local on_digiline_receive = function(pos, _, channel, msg) @@ -147,7 +190,7 @@ local on_digiline_receive = function(pos, _, channel, msg) meta:set_string("text", msg) meta:set_string("infotext", msg) - clearscreen(pos) + if msg ~= "" then prepare_writing(pos) end @@ -165,30 +208,34 @@ minetest.register_node("digilines:lcd", { inventory_image = "lcd_lcd.png", wield_image = "lcd_lcd.png", tiles = {"lcd_anyside.png"}, - paramtype = "light", sunlight_propagates = true, + light_source = 6, paramtype2 = "wallmounted", node_box = lcd_box, selection_box = lcd_box, groups = {choppy = 3, dig_immediate = 2}, - - after_place_node = function (pos) + after_place_node = function(pos) local param2 = minetest.get_node(pos).param2 if param2 == 0 or param2 == 1 then minetest.add_node(pos, {name = "digilines:lcd", param2 = 3}) end - prepare_writing (pos) + spawn_entity(pos) + prepare_writing(pos) end, - - on_construct = function(pos) - reset_meta(pos) + on_construct = reset_meta, + on_destruct = clearscreen, + on_punch = function(pos, node, puncher, pointed_thing) + if minetest.is_player(puncher) then + spawn_entity(pos) + end end, - - on_destruct = function(pos) - clearscreen(pos) + on_rotate = function(pos, node, user, mode, new_param2) + if mode ~= screwdriver.ROTATE_FACE then + return false + end + rotate_text(pos, new_param2) end, - on_receive_fields = function(pos, _, fields, sender) local name = sender:get_player_name() if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then @@ -199,28 +246,27 @@ minetest.register_node("digilines:lcd", { minetest.get_meta(pos):set_string("channel", fields.channel) end end, - - digiline = - { + digiline = { receptor = {}, effector = { action = on_digiline_receive }, }, +}) - light_source = 6, +minetest.register_lbm({ + label = "Replace Missing Text Entities", + name = "digilines:replace_text", + nodenames = {"digilines:lcd"}, + run_at_every_load = true, + action = spawn_entity, }) minetest.register_entity(":digilines_lcd:text", { collisionbox = { 0, 0, 0, 0, 0, 0 }, visual = "upright_sprite", textures = {}, - - on_activate = function(self) - local meta = minetest.get_meta(self.object:getpos()) - local text = meta:get_string("text") - self.object:set_properties({textures={generate_texture(create_lines(text))}}) - end + on_activate = set_texture, }) minetest.register_craft({