Christopher Head
2019-01-26 4f78a69ffc714886c9d6e812f78d543bb33fe674
technic/machines/register/cables.lua
@@ -1,177 +1,298 @@
local S = technic.getter
local cable_itstr_to_tier = {}
local cable_tier = {}
function technic.register_cable(tier, size)
   local ltier = string.lower(tier)
   for x1 = 0, 1 do
   for x2 = 0, 1 do
   for y1 = 0, 1 do
   for y2 = 0, 1 do
   for z1 = 0, 1 do
   for z2 = 0, 1 do
      local id = technic.get_cable_id({x1, x2, y1, y2, z1, z2})
      cable_itstr_to_tier["technic:"..ltier.."_cable"..id] = tier
      local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2}
      if id ~= 0 then
         groups.not_in_creative_inventory = 1
      end
      minetest.register_node("technic:"..ltier.."_cable"..id, {
         description = S("%s Cable"):format(tier),
         tiles = {"technic_"..ltier.."_cable.png"},
         inventory_image = "technic_"..ltier.."_cable_wield.png",
         wield_image = "technic_"..ltier.."_cable_wield.png",
         groups = groups,
         sounds = default.node_sound_wood_defaults(),
         drop = "technic:"..ltier.."_cable0",
         paramtype = "light",
         sunlight_propagates = true,
         drawtype = "nodebox",
         node_box = {
            type = "fixed",
            fixed = technic.gen_cable_nodebox(x1, y1, z1, x2, y2, z2, size)
         },
         on_construct = function()
            technic.networks = {}
         end,
         on_destruct = function()
            technic.networks = {}
         end,
         after_place_node = function(pos)
            local node = minetest.get_node(pos)
            technic.update_cables(pos, technic.get_cable_tier(node.name))
         end,
         after_dig_node = function(pos, oldnode)
            local tier = technic.get_cable_tier(oldnode.name)
            technic.update_cables(pos, tier, true)
         end
      })
   end
   end
   end
   end
   end
   end
function technic.is_tier_cable(name, tier)
   return cable_tier[name] == tier
end
minetest.register_on_placenode(function(pos, node)
   for tier, machine_list in pairs(technic.machines) do
      if machine_list[node.name] ~= nil then
         technic.update_cables(pos, tier, true)
         technic.networks = {}
      end
   end
end)
minetest.register_on_dignode(function(pos, node)
   for tier, machine_list in pairs(technic.machines) do
      if machine_list[node.name] ~= nil then
         technic.update_cables(pos, tier, true)
         technic.networks = {}
      end
   end
end)
function technic.get_cable_id(links)
   return (links[6] * 1) + (links[5] * 2)
         + (links[4] * 4)  + (links[3] * 8)
         + (links[2] * 16) + (links[1] * 32)
function technic.get_cable_tier(name)
   return cable_tier[name]
end
function technic.update_cables(pos, tier, no_set, secondrun)
   local link_positions = {
local function check_connections(pos)
   -- Build a table of all machines
   local machines = {}
   for tier,list in pairs(technic.machines) do
      for k,v in pairs(list) do
         machines[k] = v
      end
   end
   local connections = {}
   local positions = {
      {x=pos.x+1, y=pos.y,   z=pos.z},
      {x=pos.x-1, y=pos.y,   z=pos.z},
      {x=pos.x,   y=pos.y+1, z=pos.z},
      {x=pos.x,   y=pos.y-1, z=pos.z},
      {x=pos.x,   y=pos.y,   z=pos.z+1},
      {x=pos.x,   y=pos.y,   z=pos.z-1}}
   for _,connected_pos in pairs(positions) do
      local name = minetest.get_node(connected_pos).name
      if machines[name] or technic.get_cable_tier(name) then
         table.insert(connections,connected_pos)
      end
   end
   return connections
end
   local links = {0, 0, 0, 0, 0, 0}
local function clear_networks(pos)
   local node = minetest.get_node(pos)
   local meta = minetest.get_meta(pos)
   local placed = node.name ~= "air"
   local positions = check_connections(pos)
   if #positions < 1 then return end
   local dead_end = #positions == 1
   for _,connected_pos in pairs(positions) do
      local net = technic.cables[minetest.hash_node_position(connected_pos)]
      if net and technic.networks[net] then
         if dead_end and placed then
            -- Dead end placed, add it to the network
            -- Get the network
            local network_id = technic.cables[minetest.hash_node_position(positions[1])]
            if not network_id then
               -- We're evidently not on a network, nothing to add ourselves to
               return
            end
            local sw_pos = minetest.get_position_from_hash(network_id)
            sw_pos.y = sw_pos.y + 1
            local network = technic.networks[network_id]
            local tier = network.tier
   for i, link_pos in pairs(link_positions) do
      local connect_type = technic.cables_should_connect(pos, link_pos, tier)
      if connect_type then
         links[i] = 1
         -- Have cables next to us update theirselves,
         -- but only once. (We don't want to update the entire
         -- network or start an infinite loop of updates)
         if not secondrun and connect_type == "cable" then
            technic.update_cables(link_pos, tier, false, true)
            -- Actually add it to the (cached) network
            -- This is similar to check_node_subp
            technic.cables[minetest.hash_node_position(pos)] = network_id
            pos.visited = 1
            if technic.is_tier_cable(name, tier) then
               table.insert(network.all_nodes,pos)
            elseif technic.machines[tier][node.name] then
               meta:set_string(tier.."_network",minetest.pos_to_string(sw_pos))
               if     technic.machines[tier][node.name] == technic.producer then
                  table.insert(network.PR_nodes,pos)
               elseif technic.machines[tier][node.name] == technic.receiver then
                  table.insert(network.RE_nodes,pos)
               elseif technic.machines[tier][node.name] == technic.producer_receiver then
                  table.insert(network.PR_nodes,pos)
                  table.insert(network.RE_nodes,pos)
               elseif technic.machines[tier][node.name] == "SPECIAL" and
                     (pos.x ~= sw_pos.x or pos.y ~= sw_pos.y or pos.z ~= sw_pos.z) and
                     from_below then
                  table.insert(network.SP_nodes,pos)
               elseif technic.machines[tier][node.name] == technic.battery then
                  table.insert(network.BA_nodes,pos)
               end
            end
         elseif dead_end and not placed then
            -- Dead end removed, remove it from the network
            -- Get the network
            local network_id = technic.cables[minetest.hash_node_position(positions[1])]
            if not network_id then
               -- We're evidently not on a network, nothing to add ourselves to
               return
            end
            local network = technic.networks[network_id]
            -- Search for and remove machine
            technic.cables[minetest.hash_node_position(pos)] = nil
            for tblname,table in pairs(network) do
               if tblname ~= "tier" then
                  for machinenum,machine in pairs(table) do
                     if machine.x == pos.x
                     and machine.y == pos.y
                     and machine.z == pos.z then
                        table[machinenum] = nil
                     end
                  end
               end
            end
         else
            -- Not a dead end, so the whole network needs to be recalculated
            for _,v in pairs(technic.networks[net].all_nodes) do
               local pos1 = minetest.hash_node_position(v)
               technic.cables[pos1] = nil
            end
            technic.networks[net] = nil
         end
      end
   end
   -- We don't want to set ourselves if we have been removed or we are
   -- updating a machine
   if not no_set then
      minetest.set_node(pos, {name="technic:"..string.lower(tier)
            .."_cable"..technic.get_cable_id(links)})
end
local function item_place_override_node(itemstack, placer, pointed, node)
   -- Call the default on_place function with a fake itemstack
   local temp_itemstack = ItemStack(itemstack)
   temp_itemstack:set_name(node.name)
   local original_count = temp_itemstack:get_count()
   temp_itemstack =
      minetest.item_place(temp_itemstack, placer, pointed, node.param2) or
      temp_itemstack
   -- Remove the same number of items from the real itemstack
   itemstack:take_item(original_count - temp_itemstack:get_count())
   return itemstack
end
function technic.register_cable(tier, size)
   local ltier = string.lower(tier)
   cable_tier["technic:"..ltier.."_cable"] = tier
   local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
         ["technic_"..ltier.."_cable"] = 1}
   local node_box = {
      type = "connected",
      fixed          = {-size, -size, -size, size,  size, size},
      connect_top    = {-size, -size, -size, size,  0.5,  size}, -- y+
      connect_bottom = {-size, -0.5,  -size, size,  size, size}, -- y-
      connect_front  = {-size, -size, -0.5,  size,  size, size}, -- z-
      connect_back   = {-size, -size,  size, size,  size, 0.5 }, -- z+
      connect_left   = {-0.5,  -size, -size, size,  size, size}, -- x-
      connect_right  = {-size, -size, -size, 0.5,   size, size}, -- x+
   }
   minetest.register_node("technic:"..ltier.."_cable", {
      description = S("%s Cable"):format(tier),
      tiles = {"technic_"..ltier.."_cable.png"},
      inventory_image = "technic_"..ltier.."_cable_wield.png",
      wield_image = "technic_"..ltier.."_cable_wield.png",
      groups = groups,
      sounds = default.node_sound_wood_defaults(),
      drop = "technic:"..ltier.."_cable",
      paramtype = "light",
      sunlight_propagates = true,
      drawtype = "nodebox",
      node_box = node_box,
      connects_to = {"group:technic_"..ltier.."_cable",
         "group:technic_"..ltier, "group:technic_all_tiers"},
      on_construct = clear_networks,
      on_destruct = clear_networks,
   })
   local xyz = {
      ["-x"] = 1,
      ["-y"] = 2,
      ["-z"] = 3,
      ["x"] = 4,
      ["y"] = 5,
      ["z"] = 6,
   }
   local notconnects = {
      [1] = "left",
      [2] = "bottom",
      [3] = "front",
      [4] = "right",
      [5] = "top",
      [6] = "back",
   }
   local function s(p)
      if p:find("-") then
         return p:sub(2)
      else
         return "-"..p
      end
   end
   for p, i in pairs(xyz) do
      local def = {
         description = S("%s Cable Plate"):format(tier),
         tiles = {"technic_"..ltier.."_cable.png"},
         groups = table.copy(groups),
         sounds = default.node_sound_wood_defaults(),
         drop = "technic:"..ltier.."_cable_plate_1",
         paramtype = "light",
         sunlight_propagates = true,
         drawtype = "nodebox",
         node_box = table.copy(node_box),
         connects_to = {"group:technic_"..ltier.."_cable",
            "group:technic_"..ltier, "group:technic_all_tiers"},
         on_construct = clear_networks,
         on_destruct = clear_networks,
      }
      def.node_box.fixed = {
         {-size, -size, -size, size, size, size},
         {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
      }
      def.node_box.fixed[1][xyz[p]] = 7/16 * (i-3.5)/math.abs(i-3.5)
      def.node_box.fixed[2][xyz[s(p)]] = 3/8 * (i-3.5)/math.abs(i-3.5)
      def.node_box["connect_"..notconnects[i]] = nil
      if i == 1 then
         def.on_place = function(itemstack, placer, pointed_thing)
            local pointed_thing_diff = vector.subtract(pointed_thing.above, pointed_thing.under)
            local num
            local changed
            for k, v in pairs(pointed_thing_diff) do
               if v ~= 0 then
                  changed = k
                  num = xyz[s(tostring(v):sub(-2, -2)..k)]
                  break
               end
            end
            local crtl = placer:get_player_control()
            if (crtl.aux1 or crtl.sneak) and not (crtl.aux1 and crtl.sneak) then
               local fine_pointed = minetest.pointed_thing_to_face_pos(placer, pointed_thing)
               fine_pointed = vector.subtract(fine_pointed, pointed_thing.above)
               fine_pointed[changed] = nil
               local ps = {}
               for p, _ in pairs(fine_pointed) do
                  ps[#ps+1] = p
               end
               local bigger = (math.abs(fine_pointed[ps[1]]) > math.abs(fine_pointed[ps[2]]) and ps[1]) or ps[2]
               if math.abs(fine_pointed[bigger]) < 0.3 then
                  num = num + 3
                  num = (num <= 6 and num) or num - 6
               else
                  num = xyz[((fine_pointed[bigger] < 0 and "-") or "") .. bigger]
               end
            end
            return item_place_override_node(
               itemstack, placer, pointed_thing,
               {name = "technic:"..ltier.."_cable_plate_"..num}
            )
         end
      else
         def.groups.not_in_creative_inventory = 1
      end
      def.on_rotate = function(pos, node, user, mode, new_param2)
         local dir = 0
         if mode == screwdriver.ROTATE_FACE then -- left-click
            dir = 1
         elseif mode == screwdriver.ROTATE_AXIS then -- right-click
            dir = -1
         end
         local num = tonumber(node.name:sub(-1))
         num = num + dir
         num = (num >= 1 and num) or num + 6
         num = (num <= 6 and num) or num - 6
         minetest.swap_node(pos, {name = "technic:"..ltier.."_cable_plate_"..num})
      end
      minetest.register_node("technic:"..ltier.."_cable_plate_"..i, def)
      cable_tier["technic:"..ltier.."_cable_plate_"..i] = tier
   end
   local c = "technic:"..ltier.."_cable"
   minetest.register_craft({
      output = "technic:"..ltier.."_cable_plate_1 5",
      recipe = {
         {"", "", c},
         {c , c , c},
         {"", "", c},
      }
   })
   minetest.register_craft({
      output = c,
      recipe = {
         {"technic:"..ltier.."_cable_plate_1"},
      }
   })
end
function technic.is_tier_cable(name, tier)
   return cable_itstr_to_tier[name] and cable_itstr_to_tier[name] == tier
local function clear_nets_if_machine(pos, node)
   for tier, machine_list in pairs(technic.machines) do
      if machine_list[node.name] ~= nil then
         return clear_networks(pos)
      end
   end
end
function technic.get_cable_tier(name)
   return cable_itstr_to_tier[name]
end
function technic.cables_should_connect(pos1, pos2, tier)
   local name = minetest.get_node(pos2).name
   if name == "technic:switching_station" then
      return pos2.y == pos1.y + 1 and "machine" or false
   elseif name == "technic:supply_converter" then
      return math.abs(pos2.y - pos1.y) == 1 and "machine" or false
   elseif technic.is_tier_cable(name, tier) then
      return "cable"
   elseif technic.machines[tier][name] then
      return "machine"
   end
   return false
end
function technic.gen_cable_nodebox(x1, y1, z1, x2, y2, z2, size)
   -- Nodeboxes
   local box_center = {-size, -size, -size, size,  size, size}
   local box_y1 =     {-size, -size, -size, size,  0.5,  size} -- y+
   local box_x1 =     {-size, -size, -size, 0.5,   size, size} -- x+
   local box_z1 =     {-size, -size,  size, size,  size, 0.5}  -- z+
   local box_z2 =     {-size, -size, -0.5,  size,  size, size} -- z-
   local box_y2 =     {-size, -0.5,  -size, size,  size, size} -- y-
   local box_x2 =     {-0.5,  -size, -size, size,  size, size} -- x-
   local box = {box_center}
   if x1 == 1 then
      table.insert(box, box_x1)
   end
   if y1 == 1 then
      table.insert(box, box_y1)
   end
   if z1 == 1 then
      table.insert(box, box_z1)
   end
   if x2 == 1 then
      table.insert(box, box_x2)
   end
   if y2 == 1 then
      table.insert(box, box_y2)
   end
   if z2 == 1 then
      table.insert(box, box_z2)
   end
   return box
end
minetest.register_on_placenode(clear_nets_if_machine)
minetest.register_on_dignode(clear_nets_if_machine)