From 1b13e8a9a178dc23669644103172f75e308e54dd Mon Sep 17 00:00:00 2001 From: whats_his_face Date: Thu, 12 May 2022 14:46:42 -0500 Subject: [PATCH 1/3] Rename subcommand "player" to "players" and extend its functionality --- mods/lobby/tdc.lua | 309 ++++++++++++++++++++++++++++++++------------- 1 file changed, 221 insertions(+), 88 deletions(-) diff --git a/mods/lobby/tdc.lua b/mods/lobby/tdc.lua index a13c4fc..7897e6c 100644 --- a/mods/lobby/tdc.lua +++ b/mods/lobby/tdc.lua @@ -13,6 +13,10 @@ local function _info(msg) return minetest.colorize('#ffff80', msg) end +local function _stdout(msg) + return minetest.colorize('#80ddee', msg) +end + local function _lsitem(item) return TAB .. '- ' .. item end @@ -47,63 +51,158 @@ local function list_corpses(ctab) end -tdc = { - fix_corpses = function(plr_name, map) - local pmap = map or '*' - local count = 0 +--[[ -------------------- tdc namespace -------------------- ]]-- +tdc = {} - for mapid, cplist in pairs(lobby.corpses) do - if pmap == '*' then +function tdc.fix_corpses(td_user, map) + local pmap = map or '*' + local count = 0 + + for mapid, cplist in pairs(lobby.corpses) do + if pmap == '*' then + lobby.corpse_removal(mapid) + lobby.corpses[mapid] = {} + count = count + 1 + else + if mapid:find(pmap) then lobby.corpse_removal(mapid) lobby.corpses[mapid] = {} count = count + 1 - else - if mapid:find(pmap) then - lobby.corpse_removal(mapid) - lobby.corpses[mapid] = {} - count = count + 1 - minetest.chat_send_player(plr_name, - _info('Corpses in ' .. _keyw(mapid) .. ' fixed.')) - break - end + minetest.chat_send_player(td_user, + _info('Corpses in ' .. _keyw(mapid) .. ' fixed.')) + break end end - if count == 0 then - minetest.chat_send_player(plr_name, _info('No maps found')) - elseif pmap == '*' then - minetest.chat_send_player(plr_name, - _info('Corpses in ' .. tostring(count) .. ' map(s) fixed.')) - end - end, + end + if count == 0 then + minetest.chat_send_player(td_user, _info('No maps found')) + elseif pmap == '*' then + minetest.chat_send_player(td_user, + _info('Corpses in ' .. tostring(count) .. ' map(s) fixed.')) + end +end - fix_map = function(plr_name, map) - local pmap = map or '*' - local count = 0 +function tdc.fix_map(td_user, map) + local pmap = map or '*' + local count = 0 - for mapid, count in pairs(lobby.map) do - if pmap == '*' then + for mapid, count in pairs(lobby.map) do + if pmap == '*' then + lobby.map[mapid] = 0 + lobby.update_maps(mapid) + count = count + 1 + else + if mapid:find(pmap) then lobby.map[mapid] = 0 lobby.update_maps(mapid) count = count + 1 - else - if mapid:find(pmap) then - lobby.map[mapid] = 0 - lobby.update_maps(mapid) - count = count + 1 - minetest.chat_send_player(plr_name, - _info('Map status of ' .. _keyw(mapid) .. ' fixed.')) - break - end + minetest.chat_send_player(td_user, + _info('Map status of ' .. _keyw(mapid) .. ' fixed.')) + break end end - if count == 0 then - minetest.chat_send_player(plr_name, _info('No maps found')) - elseif pmap == '*' then - minetest.chat_send_player(plr_name, - _info('Status data for ' .. tostring(count) .. ' map(s) fixed.')) + end + if count == 0 then + minetest.chat_send_player(td_user, _info('No maps found')) + elseif pmap == '*' then + minetest.chat_send_player(td_user, + _info('Status data for ' .. tostring(count) .. ' map(s) fixed.')) + end +end + +-- Return active players whose player name matches `expr` +function tdc.active_players_matching(expr) + local players = {} + + for _, player in pairs(minetest.get_connected_players()) do + local pl_name = player:get_player_name() + + if pl_name:find(expr) then + table.insert(players, player) end - end, -} + end + return players +end + +-- Return active map ids matching `expr` +function tdc.active_mapids_matching(expr) + local mapids = {} + + for mapid, count in pairs(lobby.map) do + minetest.log('action', 'map "' .. mapid .. '": ' .. tostring(count)) + if mapid:find(expr) and count ~= nil and count > 0 then + mapids[mapid] = true + end + end + + -- The lobby itself never gets into lobby.map, so we insert it manually, + -- since we want to treat it just like a map. + local mapid_lobby = 'lobby' + if mapid_lobby:find(expr) then mapids[mapid_lobby] = true end + + return mapids +end + +-- Return players currently visiting a map whose id is in the list `mapids` +function tdc.players_visiting(mapids) + local players = {} + + for pl_name, mapid in pairs(lobby.game) do + local mid = mapid + + if mapid:find("_solo$") then + mid = mapid:sub(1, -6) + elseif mapid:find("_ghost$") then + mid = mapid:sub(1, -7) + end + minetest.log('action', pl_name .. ' in map ' .. mid) + if mapids[mid] then + table.insert(players, minetest.get_player_by_name(pl_name)) + end + end + return players +end + +-- For each player in the `players` list, create an entry in the `index` table, +-- having `player:get_player_name()` as keyword. +function tdc.index_by_name(players, index) + if players == nil or index == nil then return end + + for _, player in ipairs(players) do + local pl_name = player:get_player_name() + index[pl_name] = player + end +end + +-- Return a list of currently active players where either the player name +-- or the map id they're currently visiting matches `expr`. +function tdc.list_players_matching(expr) + local players = tdc.active_players_matching(expr) + local mapids = tdc.active_mapids_matching(expr) + local visitors = tdc.players_visiting(mapids) + local matches = {} + + -- index all matching players by their name + tdc.index_by_name(players, matches) + tdc.index_by_name(visitors, matches) + + return matches +end + +-- Return a footprint representation of the player privileges `privs` +function tdc.privs_footprint(privs) + local p = '' + + if privs.server then p = p .. 's' else p = p .. '-' end + if privs.traitor_dev then p = p .. 't' else p = p .. '-' end + if privs.builder then p = p .. 'b' else p = p .. '-' end + if privs.creative then p = p .. 'c' else p = p .. '-' end + if privs.worldeditor then p = p .. 'w' else p = p .. '-' end + if privs.pro_player then p = p .. 'p' else p = p .. '-' end + if privs.multihome then p = p .. 'm' else p = p .. '-' end + return p +end + -- tdc.actions: enumerates the possible /td commands -- tdc.actions = { cmd_1 = def_1, cmd_2 = def_2, ... } @@ -131,20 +230,20 @@ tdc.actions = { table.insert(msgtab, 'Type "' .. _keyw('/td help ') .. '" to get help for a specific command.') return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) if params then local par1 = params:match('[%w_]+') if par1 and tdc.actions[par1] then - minetest.chat_send_player(plr_name, tdc.actions[par1].help()) + minetest.chat_send_player(td_user, tdc.actions[par1].help()) elseif par1 then -- TODO: find par1 as cmd prefix in tdc.actions local msg = _info('Unknown command "' .. par1 .. '"\n') .. list_actions() - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) else - minetest.chat_send_player(plr_name, tdc.actions['help'].help()) + minetest.chat_send_player(td_user, tdc.actions['help'].help()) end else - minetest.chat_send_player(plr_name, tdc.actions['help'].help()) + minetest.chat_send_player(td_user, tdc.actions['help'].help()) end end, }, @@ -160,7 +259,7 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local map = params:match('[%w_]+') or '*' local hdr, msg, ccount, clist if params == '' then @@ -191,7 +290,7 @@ tdc.actions = { else msg = _info('No corpses yet.') end - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) minetest.log('action', minetest.strip_colors(msg)) end, }, @@ -205,7 +304,7 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local msgtab = {_info('Active maps:')} local plr_count = 0 local msg @@ -233,7 +332,7 @@ tdc.actions = { table.insert(msgtab, _lsitem('lobby: ' .. tostring(clobby) .. ' player(s)')) end msg = table.concat(msgtab, '\n') - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) minetest.log('action', minetest.strip_colors(msg)) end, }, @@ -247,7 +346,7 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local msg = '' for mapid, traitor in pairs(lobby.traitors) do -- table value could be nil if entry is to be GC'd @@ -260,47 +359,81 @@ tdc.actions = { else msg = _info('Active traitors:\n') .. msg end - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) minetest.log('action', minetest.strip_colors(msg)) end, }, - -- CMD: /td player - player = { + -- CMD: /td players + players = { info = 'Show player attributes', help = function() local msgtab = { - _info('Usage: /td player '), - 'List some player metadata.', + _info('Usage: /td players '), + 'Show metadata of one or more players.', 'Params:', - TAB .. ' player name (substring suffices).', - '\nNote: is restricted to alphanumeric chars and "_", for the sake of security.' + TAB .. ' Part of a player name or map ID used as search expression.', + TAB .. ' Any matching player name will be included in the result list.', + TAB .. ' If is part of a map ID, lists all players currently', + TAB .. ' visiting that map.', + '\nNote: is restricted to alphanumeric chars and "_", for the sake of security.', + '\nThe metadata of matching players is listed in a table with the following columns:', + TAB .. 'Player displays the player\'s login name', + TAB .. 'Map shows the map id the player is currently visiting', + TAB .. 'Privs shows the state of some more widely used traitor privileges, which is', + TAB .. ' a short string where each letter symbolizes a certain privilege', + TAB .. ' (' .. + _stdout('s') .. 'erver, ' .. + _stdout('t') .. 'raitor_dev, ' .. + _stdout('b') .. 'uilder, ' .. + _stdout('c') .. 'reative, ' .. + _stdout('w') .. 'orldeditor, ' .. + _stdout('p') .. 'ro_player, ' .. + _stdout('m') .. 'ultihome)', + TAB .. ' If a player does not have a certain privilege, the privilege\'s letter', + TAB .. ' is replaced by a \'-\' instead.', + TAB .. 'Mode displays the player mode', + TAB .. 'Tone shows the player\'s current tone color', + TAB .. 'Spawn prints the player\'s current spawn position (if any)', } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) if not params or not params:find("[%w_]+") then - minetest.chat_send_player(plr_name, 'Missing argument, type "/td help player" for help.') + minetest.chat_send_player(td_user, 'Missing argument, type "/td help players" for help.') else - local p1, p2, plid = params:find('([%w_]+)') + local p1, p2, expr = params:find('([%w_]+)') + local matches = tdc.list_players_matching(expr) local count = 0 + local mtab = { + _info('Player Map Privs Mode Tone Spawn'), + '--------------------------------------------------------------------------------------', + } + -- sort list by player name + local sorted = {} + for name in pairs(matches) do table.insert(sorted, name) end + table.sort(sorted) - for _, player in pairs(minetest.get_connected_players()) do - local pname = player:get_player_name() + for _, name in pairs(sorted) do + count = count + 1 + local player = matches[name] + local attr = player:get_meta() + local pl_name = _keyw(name) + local padding = 20 - name:len() + local pl_padding = string.rep(' ', padding) + local pl_map = lobby.game[name] or '' + local pl_privs = tdc.privs_footprint(minetest.get_player_privs(name)) + local pl_mode = attr:get_string('mode') or '' + local pl_tone = attr:get_int('tone') + local pl_spawn = attr:get_string('spawn_pos') or '' - if pname:find(plid) then - count = count + 1 - local attr = player:get_meta() - local mtab = { - _info('Attributes of ') .. _keyw(pname) .. ':', - TAB .. 'ghost: ' .. (attr:get_string('ghost') or 'false'), - TAB .. 'spawn: ' .. (attr:get_string('spawn_pos') or ''), - TAB .. 'voting: ' .. (attr:get_string('voting') or 'false') - } - minetest.chat_send_player(plr_name, table.concat(mtab, '\n')) - end + table.insert(mtab, string.format('%s%s %-20s %-7s %-7s %5d %-20s', + pl_name, pl_padding, pl_map, pl_privs, pl_mode, pl_tone, pl_spawn) + ) end if count == 0 then - minetest.chat_send_player(plr_name, _info('No matching player')) + minetest.chat_send_player(td_user, _info('No match')) + else + minetest.chat_send_player(td_user, table.concat(mtab, '\n')) end end end, @@ -326,23 +459,23 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local helpcmd = 'type "/td help fix" for help.' if not params then - minetest.chat_send_player(plr_name, _info('Missing arguments, ' .. helpcmd)) + minetest.chat_send_player(td_user, _info('Missing arguments, ' .. helpcmd)) else local p1, p2, fix, map p1, p2, fix, map = params:find('(%w+)%s+([%w_*]+)') if not fix or not map then - minetest.chat_send_player(plr_name, _info('Missing arguments, ' .. helpcmd)) + minetest.chat_send_player(td_user, _info('Missing arguments, ' .. helpcmd)) else if string.find('corpses', fix) then - tdc.fix_corpses(plr_name, map) + tdc.fix_corpses(td_user, map) elseif string.find('map', fix) then - tdc.fix_map(plr_name, map) + tdc.fix_map(td_user, map) else - minetest.chat_send_player(plr_name, _info('Unknown fix action, ' .. helpcmd)) + minetest.chat_send_player(td_user, _info('Unknown fix action, ' .. helpcmd)) end end end @@ -350,7 +483,7 @@ tdc.actions = { } } -function tdc.exec(plr_name, params) +function tdc.exec(td_user, params) local cmd = nil if params and params ~= '' then local par1 = params:match('[%w_]+') @@ -369,14 +502,14 @@ function tdc.exec(plr_name, params) end end if cname then - minetest.log('action', plr_name .. ' runs "/td ' .. cname .. parN .. '"') - cmd.exec(plr_name, parN) + minetest.log('action', td_user .. ' runs "/td ' .. cname .. parN .. '"') + cmd.exec(td_user, parN) else - minetest.chat_send_player(plr_name, + minetest.chat_send_player(td_user, _info('Unknown command "' .. par1 .. '", type "/td help" for possible commands.')) end else - minetest.chat_send_player(plr_name, list_actions()) + minetest.chat_send_player(td_user, list_actions()) end return true end From 9dd4a8921b6d35d0621591e6e50b03acd73d04c1 Mon Sep 17 00:00:00 2001 From: whats_his_face Date: Thu, 12 May 2022 15:09:33 -0500 Subject: [PATCH 2/3] Remove temporary logging; rewrite tdc.privs_footprint() --- mods/lobby/tdc.lua | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/mods/lobby/tdc.lua b/mods/lobby/tdc.lua index 7897e6c..baff33f 100644 --- a/mods/lobby/tdc.lua +++ b/mods/lobby/tdc.lua @@ -129,7 +129,6 @@ function tdc.active_mapids_matching(expr) local mapids = {} for mapid, count in pairs(lobby.map) do - minetest.log('action', 'map "' .. mapid .. '": ' .. tostring(count)) if mapid:find(expr) and count ~= nil and count > 0 then mapids[mapid] = true end @@ -155,7 +154,6 @@ function tdc.players_visiting(mapids) elseif mapid:find("_ghost$") then mid = mapid:sub(1, -7) end - minetest.log('action', pl_name .. ' in map ' .. mid) if mapids[mid] then table.insert(players, minetest.get_player_by_name(pl_name)) end @@ -189,18 +187,19 @@ function tdc.list_players_matching(expr) return matches end --- Return a footprint representation of the player privileges `privs` +-- Return a "footprint" representation of the player privileges `privs` function tdc.privs_footprint(privs) - local p = '' + local s, t, b, c, w, p, m - if privs.server then p = p .. 's' else p = p .. '-' end - if privs.traitor_dev then p = p .. 't' else p = p .. '-' end - if privs.builder then p = p .. 'b' else p = p .. '-' end - if privs.creative then p = p .. 'c' else p = p .. '-' end - if privs.worldeditor then p = p .. 'w' else p = p .. '-' end - if privs.pro_player then p = p .. 'p' else p = p .. '-' end - if privs.multihome then p = p .. 'm' else p = p .. '-' end - return p + if privs.server then s = 's' else s = '-' end + if privs.traitor_dev then t = 't' else t = '-' end + if privs.builder then b = 'b' else b = '-' end + if privs.creative then c = 'c' else c = '-' end + if privs.worldeditor then w = 'w' else w = '-' end + if privs.pro_player then p = 'p' else p = '-' end + if privs.multihome then m = 'm' else m = '-' end + + return table.concat({s, t, b, c, w, p, m}) end From 8b628ad26534241f895aa53c303d2b6abb6524f4 Mon Sep 17 00:00:00 2001 From: Nathan Salapat Date: Mon, 16 May 2022 21:46:22 -0500 Subject: [PATCH 3/3] Added /td mode chat command to change own playing mode. --- mods/lobby/buttons.lua | 9 +- mods/lobby/lobby.lua | 4 +- mods/lobby/tdc.lua | 37 +- mods/tubelib2/COPYING.txt | 28 + mods/tubelib2/LICENSE.txt | 502 ++++++++++++++++ mods/tubelib2/README.md | 94 +++ mods/tubelib2/description.txt | 2 + mods/tubelib2/design.txt | 70 +++ mods/tubelib2/i18n.py | 476 +++++++++++++++ mods/tubelib2/init.lua | 14 + mods/tubelib2/internal1.lua | 252 ++++++++ mods/tubelib2/internal2.lua | 430 ++++++++++++++ mods/tubelib2/intllib.sh | 3 + mods/tubelib2/locale/template.txt | 8 + mods/tubelib2/locale/tubelib2.de.tr | 8 + mods/tubelib2/mod.conf | 3 + mods/tubelib2/settingtypes.txt | 3 + mods/tubelib2/storage.lua | 152 +++++ mods/tubelib2/textures/tubelib2_conn.png | Bin 0 -> 158 bytes mods/tubelib2/textures/tubelib2_hole.png | Bin 0 -> 178 bytes .../textures/tubelib2_marker_cube.png | Bin 0 -> 195 bytes mods/tubelib2/textures/tubelib2_source.png | Bin 0 -> 168 bytes mods/tubelib2/textures/tubelib2_tele.png | Bin 0 -> 193 bytes mods/tubelib2/textures/tubelib2_tool.png | Bin 0 -> 934 bytes mods/tubelib2/textures/tubelib2_tube.png | Bin 0 -> 175 bytes mods/tubelib2/tube_api.lua | 542 ++++++++++++++++++ mods/tubelib2/tube_test.lua | 443 ++++++++++++++ 27 files changed, 3072 insertions(+), 8 deletions(-) create mode 100644 mods/tubelib2/COPYING.txt create mode 100644 mods/tubelib2/LICENSE.txt create mode 100644 mods/tubelib2/README.md create mode 100644 mods/tubelib2/description.txt create mode 100644 mods/tubelib2/design.txt create mode 100644 mods/tubelib2/i18n.py create mode 100644 mods/tubelib2/init.lua create mode 100644 mods/tubelib2/internal1.lua create mode 100644 mods/tubelib2/internal2.lua create mode 100755 mods/tubelib2/intllib.sh create mode 100644 mods/tubelib2/locale/template.txt create mode 100644 mods/tubelib2/locale/tubelib2.de.tr create mode 100644 mods/tubelib2/mod.conf create mode 100644 mods/tubelib2/settingtypes.txt create mode 100644 mods/tubelib2/storage.lua create mode 100644 mods/tubelib2/textures/tubelib2_conn.png create mode 100644 mods/tubelib2/textures/tubelib2_hole.png create mode 100644 mods/tubelib2/textures/tubelib2_marker_cube.png create mode 100644 mods/tubelib2/textures/tubelib2_source.png create mode 100644 mods/tubelib2/textures/tubelib2_tele.png create mode 100644 mods/tubelib2/textures/tubelib2_tool.png create mode 100644 mods/tubelib2/textures/tubelib2_tube.png create mode 100644 mods/tubelib2/tube_api.lua create mode 100644 mods/tubelib2/tube_test.lua diff --git a/mods/lobby/buttons.lua b/mods/lobby/buttons.lua index 587cdb2..3684ed4 100644 --- a/mods/lobby/buttons.lua +++ b/mods/lobby/buttons.lua @@ -303,6 +303,10 @@ minetest.register_node('lobby:button_1', { lobby.corpses[map_id] = {} for i=1,player_count do minetest.chat_send_player(map_players[i], 'Hold tight, loading the level.') + local player = minetest.get_player_by_name(map_players[i]) + local player_attributes = player:get_meta() + player_attributes:set_string('voting', 'false') + player_attributes:set_string('mode', 'player') lobby.voted[map_players[i]] = true if i == traitor then minetest.chat_send_player(map_players[i], 'You\'re the imposter. Try to kill all the other players.') @@ -313,11 +317,8 @@ minetest.register_node('lobby:button_1', { minetest.after(10, function() player_inv:add_item('main', 'lobby:shank') end) + player_attributes:set_string('mode', 'traitor') end - local player = minetest.get_player_by_name(map_players[i]) - local player_attributes = player:get_meta() - player_attributes:set_string('voting', 'false') - player_attributes:set_string('mode', 'player') local privs = minetest.get_player_privs(map_players[i]) local player_inv = player:get_inventory() player_inv:set_list('main', {}) diff --git a/mods/lobby/lobby.lua b/mods/lobby/lobby.lua index 7317a66..12cd8cf 100644 --- a/mods/lobby/lobby.lua +++ b/mods/lobby/lobby.lua @@ -33,10 +33,12 @@ This is set anytime a player goes to a level they have build access on. player_attributes:set_string('mode', 'player') This is set when a player is playing a level with other people, as it's meant to be played. +player_attributes:set_string('mode', 'traitor') +This is set when a player is playing a level with other people, and is the traitor. + player_attributes:set_string('mode', 'solo') This is set when a player plays a level solo, usually to earn XP, but could also be to explore levels. player_attributes:set_string('mode', 'ghost') This is set when a player, playing with others, dies on a level. ]] - diff --git a/mods/lobby/tdc.lua b/mods/lobby/tdc.lua index baff33f..6358127 100644 --- a/mods/lobby/tdc.lua +++ b/mods/lobby/tdc.lua @@ -439,7 +439,7 @@ tdc.actions = { }, -- CMD: /td fix (corpses|map) --[[ - fix corpses: call lobby.corpse_removal(), then reset its poslist + fix corpses: call lobby.corpse_removal(), then reset its poslist fix maps: set lobby.map[] = 0, then call lobby.update_maps() --]] fix = { @@ -479,7 +479,39 @@ tdc.actions = { end end end, - } + }, + --CMD: /td mode(mode) + mode = { + info = 'Switch your play mode', + help = function() + local msgtab = { + _info('Usage /td mode '), + 'Changes your playing mode.', + 'Params:', + TAB .. ' One of the following:', + TAB .. 'builder, ghost, player, solo, traitor' + } + return table.concat(msgtab, '\n') + end, + exec = function(td_user, params) + local helpcmd = 'type "/td help mode" for help.' + + if not params then + minetest.chat_send_player(td_user, _info('Missing arguments, ' .. helpcmd)) + else + local modes = 'builder, ghost, player, solo, traitor' + if string.find(modes, params) then + local player = minetest.get_player_by_name(td_user) + local player_attributes = player:get_meta() + player_attributes:set_string('mode', params) + minetest.chat_send_player(td_user, _info('Switched you to '..params..' mode.')) + else + minetest.chat_send_player(td_user, _info('Missing or bad arguments, ' .. helpcmd)) + end + end + end, + } + } function tdc.exec(td_user, params) @@ -519,4 +551,3 @@ minetest.register_chatcommand('td', { description = 'Traitor debugging commands. Type "/td help" for a list of possible commands.', func = tdc.exec }) - diff --git a/mods/tubelib2/COPYING.txt b/mods/tubelib2/COPYING.txt new file mode 100644 index 0000000..988f481 --- /dev/null +++ b/mods/tubelib2/COPYING.txt @@ -0,0 +1,28 @@ +The tubelib2 mod for Minetest is + +Copyright (C) 2017-2018 Joachim Stolberg + +License of source code +---------------------- + +This mod is free software; you can redistribute and/or +modify it under the terms of the GNU Lesser General Public License version 2.1 or later +published by the Free Software Foundation. + +This mod is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this mod; if not, write to the +Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301, USA. + + +License of media (textures, sounds and documentation) +----------------------------------------------------- + +All textures, sounds and documentation files are licensed under the +Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) +http://creativecommons.org/licenses/by-sa/3.0/ diff --git a/mods/tubelib2/LICENSE.txt b/mods/tubelib2/LICENSE.txt new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/mods/tubelib2/LICENSE.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/mods/tubelib2/README.md b/mods/tubelib2/README.md new file mode 100644 index 0000000..e6e69da --- /dev/null +++ b/mods/tubelib2/README.md @@ -0,0 +1,94 @@ +# Tube Library 2 [tubelib2] + +A library for mods which need connecting tubes / pipes / cables or similar. + +![tubelib2](https://github.com/joe7575/tubelib2/blob/master/screenshot.png) + + +This mod is not useful for its own. It does not even have any nodes. +It only comes with a few test nodes to play around with the tubing algorithm. + +Browse on: [GitHub](https://github.com/joe7575/tubelib2) + +Download: [GitHub](https://github.com/joe7575/tubelib2/archive/master.zip) + + +## Description + +Tubelib2 distinguished two kinds of nodes: +- primary nodes are tube like nodes (pipes, cables, ...) +- secondary nodes are all kind of nodes, which can be connected by means of primary nodes + +Tubelib2 specific 6D directions (1 = North, 2 = East, 3 = South, 4 = West, 5 = Down, 6 = Up) + +All 6D dirs are the view from the node to the outer side +Tubes are based on two node types, "angled" and "straight" tubes. + + + +-------+ + / /| +-------+ + / / | / /| + / / + / / | + / / / +-------+ | + +-------+ / | | | + | | / | |/ | + | |/ +-------+| + + +-------+ | |/ + +------+ + + +All other nodes are build by means of axis/rotation variants based on param2 + (paramtype2 == "facedir"). + +The 3 free MSB bits of param2 of tube nodes are used to store the number of connections (0..2). + +The data of the peer head tube are stored in memory 'self.connCache' + +Tubelib2 provides an update mechanism for connected "secondary" nodes. A callback function +func(node, pos, out_dir, peer_pos, peer_in_dir) will be called for every change on the connected tubes. + + + +## Dependencies +optional: default + + +## License +Copyright (C) 2017-2022 Joachim Stolberg +Code: Licensed under the GNU LGPL version 2.1 or later. + See LICENSE.txt and http://www.gnu.org/licenses/lgpl-2.1.txt +Textures: CC0 + + +## Credits + +### Contributors +- oversword (PR #5, PR #7, PR #8) + + +## History +- 2018-10-20 v0.1 * Tested against hyperloop elevator. +- 2018-10-27 v0.2 * Tested against and enhanced for the hyperloop mod. +- 2018-10-27 v0.3 * Further improvements. +- 2018-11-09 v0.4 * on_update function for secondary nodes introduced +- 2018-12-16 v0.5 * meta data removed, memory cache added instead of +- 2018-12-20 v0.6 * intllib support added, max tube length bugfix +- 2019-01-06 v0.7 * API function replace_tube_line added, bug fixed +- 2019-01-11 v0.8 * dir_to_facedir bugfix +- 2019-02-09 v0.9 * storage.lua added, code partly restructured +- 2019-02-17 v1.0 * released +- 2019-03-02 v1.1 * API function 'switch_tube_line' added, secondary node placement bugfix +- 2019-04-18 v1.2 * 'force_to_use_tubes' added +- 2019-05-01 v1.3 * API function 'compatible_node' added +- 2019-05-09 v1.4 * Attribute 'tube_type' added +- 2019-07-12 v1.5 * internal handling of secondary nodes changed +- 2019-10-25 v1.6 * callback 'tubelib2_on_update2' added +- 2020-01-03 v1.7 * max_tube_length bugfix +- 2020-02-02 v1.8 * 'special nodes' as alternative to 'secondary nodes' introduced +- 2020-05-31 v1.9 * Generator function 'get_tube_line' added, storage improvements +- 2021-01-23 v2.0 * Add functions for easy & fast 'valid side' checking (PR #8) +- 2021-05-24 v2.1 * Add API functions 'register_on_tube_update2' +- 2022-01-05 v2.2 * Extend the 'node.param2' support for all 24 possible values +- 2022-03-11 v2.2.1 * Changed to minetest 5.0 translation (#12) + + diff --git a/mods/tubelib2/description.txt b/mods/tubelib2/description.txt new file mode 100644 index 0000000..ad31204 --- /dev/null +++ b/mods/tubelib2/description.txt @@ -0,0 +1,2 @@ +A library for mods which need connecting tubes / pipes / cables or similar. + diff --git a/mods/tubelib2/design.txt b/mods/tubelib2/design.txt new file mode 100644 index 0000000..be9dd1b --- /dev/null +++ b/mods/tubelib2/design.txt @@ -0,0 +1,70 @@ +View to the north + + + dir1/dir2: axis dir: + 6 + A 1 + | / 0 + +--|-----+ A 1 + / o /| | / + +--------+ | |/ + 4 <----| |o----> 2 4 <-------+-------> 3 + | o | | /| + | / | + / | + | / |/ 2 V + +-/------+ 5 + / | + 3 | + V + 5 + + + +-------+ + / /| +-------+ + / / | / /| + / / + / / | + / / / +-------+ | + +-------+ / | | | + | | / | |/ | + | |/ +-------+| + + +-------+ | |/ + +------+ + + + +------+ + | | + +------+ +---+------+---+ +------+ + |XXXXXX| | | | | | | + |XXXXXX| | | | | | | + +------+ +---+------+---+ +------+ + | | + +------+ +dir1/dir2 1/3 2/4 5/6 +axis/rot 3/0 5/1 1/0 + + +------+ +---+------+ +------+ +------+---+ + |XXXXXX| | | | | | | | | + |XXXXXX| | | | | | | | | + +------+ +---+------+ +------+ +------+---+ + | | | | | | | | + +------+ +------+ +------+ +------+ +dir1/dir2 3/5 4/5 1/5 2/5 +axis/rot 0/0 3/3 2/0 0/3 + + +------+ +------+ +------+ +------+ + | | | | | | | | + +------+ +---+------+ +------+ +------+---+ + |XXXXXX| | | | | | | | | + |XXXXXX| | | | | | | | | + +------+ +---+------+ +------+ +------+---+ +axis/rot 3/6 4/6 1/6 2/6 +dir1/dir2 5/0 3/1 2/2 4/3 + + +------+---+ +---+------+ +------+---+ +---+------+ + | | | | | | |XXXXXX| | | |XXXXXX| + | | | | | | |XXXXXX| | | |XXXXXX| + +------+---+ +---+------+ +------+---+ +---+------+ +dir1/dir2 1/2 1/4 2/3 3/4 +axis/rot 2/3 3/2 1/3 1/1 + + diff --git a/mods/tubelib2/i18n.py b/mods/tubelib2/i18n.py new file mode 100644 index 0000000..5390ab9 --- /dev/null +++ b/mods/tubelib2/i18n.py @@ -0,0 +1,476 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. +# +# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer +# LGPLv2.1+ +# +# See https://github.com/minetest-tools/update_translations for +# potential future updates to this script. + +from __future__ import print_function +import os, fnmatch, re, shutil, errno +from sys import argv as _argv +from sys import stderr as _stderr + +# Running params +params = {"recursive": False, + "help": False, + "mods": False, + "verbose": False, + "folders": [], + "no-old-file": False, + "break-long-lines": False, + "sort": False, + "print-source": False, + "truncate-unused": False, +} +# Available CLI options +options = {"recursive": ['--recursive', '-r'], + "help": ['--help', '-h'], + "mods": ['--installed-mods', '-m'], + "verbose": ['--verbose', '-v'], + "no-old-file": ['--no-old-file', '-O'], + "break-long-lines": ['--break-long-lines', '-b'], + "sort": ['--sort', '-s'], + "print-source": ['--print-source', '-p'], + "truncate-unused": ['--truncate-unused', '-t'], +} + +# Strings longer than this will have extra space added between +# them in the translation files to make it easier to distinguish their +# beginnings and endings at a glance +doublespace_threshold = 80 + +def set_params_folders(tab: list): + '''Initialize params["folders"] from CLI arguments.''' + # Discarding argument 0 (tool name) + for param in tab[1:]: + stop_param = False + for option in options: + if param in options[option]: + stop_param = True + break + if not stop_param: + params["folders"].append(os.path.abspath(param)) + +def set_params(tab: list): + '''Initialize params from CLI arguments.''' + for option in options: + for option_name in options[option]: + if option_name in tab: + params[option] = True + break + +def print_help(name): + '''Prints some help message.''' + print(f'''SYNOPSIS + {name} [OPTIONS] [PATHS...] +DESCRIPTION + {', '.join(options["help"])} + prints this help message + {', '.join(options["recursive"])} + run on all subfolders of paths given + {', '.join(options["mods"])} + run on locally installed modules + {', '.join(options["no-old-file"])} + do not create *.old files + {', '.join(options["sort"])} + sort output strings alphabetically + {', '.join(options["break-long-lines"])} + add extra line breaks before and after long strings + {', '.join(options["print-source"])} + add comments denoting the source file + {', '.join(options["verbose"])} + add output information + {', '.join(options["truncate-unused"])} + delete unused strings from files +''') + + +def main(): + '''Main function''' + set_params(_argv) + set_params_folders(_argv) + if params["help"]: + print_help(_argv[0]) + elif params["recursive"] and params["mods"]: + print("Option --installed-mods is incompatible with --recursive") + else: + # Add recursivity message + print("Running ", end='') + if params["recursive"]: + print("recursively ", end='') + # Running + if params["mods"]: + print(f"on all locally installed modules in {os.path.expanduser('~/.minetest/mods/')}") + run_all_subfolders(os.path.expanduser("~/.minetest/mods")) + elif len(params["folders"]) >= 2: + print("on folder list:", params["folders"]) + for f in params["folders"]: + if params["recursive"]: + run_all_subfolders(f) + else: + update_folder(f) + elif len(params["folders"]) == 1: + print("on folder", params["folders"][0]) + if params["recursive"]: + run_all_subfolders(params["folders"][0]) + else: + update_folder(params["folders"][0]) + else: + print("on folder", os.path.abspath("./")) + if params["recursive"]: + run_all_subfolders(os.path.abspath("./")) + else: + update_folder(os.path.abspath("./")) + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +pattern_lua_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_bracketed_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) +pattern_lua_bracketed_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.*?[^@])=(.*)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file or folder name. Returns None on failure +def get_modname(folder): + try: + with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + if not os.path.isfile(os.path.join(folder, "modpack.txt")): + folder_name = os.path.basename(folder) + # Special case when run in Minetest's builtin directory + if folder_name == "builtin": + return "__builtin" + else: + return folder_name + else: + return None + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = f'{modname}.{language_code}.tr' + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + if params["verbose"]: + print(f"{tr_name} already exists, ignoring {name}") + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + if params["verbose"]: + print(f"Importing translations from {name}") + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Converts the template dictionary to a text to be written as a file +# dKeyStrings is a dictionary of localized string to source file sets +# dOld is a dictionary of existing translations and comments from +# the previous version of this text +def strings_to_text(dkeyStrings, dOld, mod_name, header_comments): + lOut = [f"# textdomain: {mod_name}"] + if header_comments is not None: + lOut.append(header_comments) + + dGroupedBySource = {} + + for key in dkeyStrings: + sourceList = list(dkeyStrings[key]) + if params["sort"]: + sourceList.sort() + sourceString = "\n".join(sourceList) + listForSource = dGroupedBySource.get(sourceString, []) + listForSource.append(key) + dGroupedBySource[sourceString] = listForSource + + lSourceKeys = list(dGroupedBySource.keys()) + lSourceKeys.sort() + for source in lSourceKeys: + localizedStrings = dGroupedBySource[source] + if params["sort"]: + localizedStrings.sort() + if params["print-source"]: + if lOut[-1] != "": + lOut.append("") + lOut.append(source) + for localizedString in localizedStrings: + val = dOld.get(localizedString, {}) + translation = val.get("translation", "") + comment = val.get("comment") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None and comment != "" and not comment.startswith("# textdomain:"): + lOut.append(comment) + lOut.append(f"{localizedString}={translation}") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold: + lOut.append("") + + + unusedExist = False + if not params["truncate-unused"]: + for key in dOld: + if key not in dkeyStrings: + val = dOld[key] + translation = val.get("translation") + comment = val.get("comment") + # only keep an unused translation if there was translated + # text or a comment associated with it + if translation != None and (translation != "" or comment): + if not unusedExist: + unusedExist = True + lOut.append("\n\n##### not used anymore #####\n") + if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{key}={translation}") + if params["break-long-lines"] and len(key) > doublespace_threshold: + lOut.append("") + return "\n".join(lOut) + '\n' + +# Writes a template.txt file +# dkeyStrings is the dictionary returned by generate_template +def write_template(templ_file, dkeyStrings, mod_name): + # read existing template file to preserve comments + existing_template = import_tr_file(templ_file) + + text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2]) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write(text) + + +# Gets all translatable strings from a lua file +def read_lua_file_strings(lua_file): + lOut = [] + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + #TODO remove comments here + + text = re.sub(pattern_concat, "", text) + + strings = [] + for s in pattern_lua_s.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_s.findall(text): + strings.append(s) + for s in pattern_lua_fs.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_fs.findall(text): + strings.append(s) + + for s in strings: + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) + return lOut + +# Gets strings from an existing translation file +# returns both a dictionary of translations +# and the full original source text so that the new text +# can be compared to it for changes. +# Returns also header comments in the third return value. +def import_tr_file(tr_file): + dOut = {} + text = None + header_comment = None + if os.path.exists(tr_file): + with open(tr_file, "r", encoding='utf-8') as existing_file : + # save the full text to allow for comparison + # of the old version with the new output + text = existing_file.read() + existing_file.seek(0) + # a running record of the current comment block + # we're inside, to allow preceeding multi-line comments + # to be retained for a translation line + latest_comment_block = None + for line in existing_file.readlines(): + line = line.rstrip('\n') + if line.startswith("###"): + if header_comment is None and not latest_comment_block is None: + # Save header comments + header_comment = latest_comment_block + # Strip textdomain line + tmp_h_c = "" + for l in header_comment.split('\n'): + if not l.startswith("# textdomain:"): + tmp_h_c += l + '\n' + header_comment = tmp_h_c + + # Reset comment block if we hit a header + latest_comment_block = None + continue + elif line.startswith("#"): + # Save the comment we're inside + if not latest_comment_block: + latest_comment_block = line + else: + latest_comment_block = latest_comment_block + "\n" + line + continue + match = pattern_tr.match(line) + if match: + # this line is a translated line + outval = {} + outval["translation"] = match.group(2) + if latest_comment_block: + # if there was a comment, record that. + outval["comment"] = latest_comment_block + latest_comment_block = None + dOut[match.group(1)] = outval + return (dOut, text, header_comment) + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +# Returns a dictionary of localized strings to source file sets +# that can be used with the strings_to_text function. +def generate_template(folder, mod_name): + dOut = {} + for root, dirs, files in os.walk(folder): + for name in files: + if fnmatch.fnmatch(name, "*.lua"): + fname = os.path.join(root, name) + found = read_lua_file_strings(fname) + if params["verbose"]: + print(f"{fname}: {str(len(found))} translatable strings") + + for s in found: + sources = dOut.get(s, set()) + sources.add(f"### {os.path.basename(fname)} ###") + dOut[s] = sources + + if len(dOut) == 0: + return None + templ_file = os.path.join(folder, "locale/template.txt") + write_template(templ_file, dOut, mod_name) + return dOut + +# Updates an existing .tr file, copying the old one to a ".old" file +# if any changes have happened +# dNew is the data used to generate the template, it has all the +# currently-existing localized strings +def update_tr_file(dNew, mod_name, tr_file): + if params["verbose"]: + print(f"updating {tr_file}") + + tr_import = import_tr_file(tr_file) + dOld = tr_import[0] + textOld = tr_import[1] + + textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2]) + + if textOld and textOld != textNew: + print(f"{tr_file} has changed.") + if not params["no-old-file"]: + shutil.copyfile(tr_file, f"{tr_file}.old") + + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write(textNew) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) + else: + print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr) + exit(1) + +# Determines if the folder being pointed to is a mod or a mod pack +# and then runs update_mod accordingly +def update_folder(folder): + is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')] + for subfolder in subfolders: + update_mod(subfolder) + else: + update_mod(folder) + print("Done.") + +def run_all_subfolders(folder): + for modfolder in [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]: + update_folder(modfolder) + + +main() diff --git a/mods/tubelib2/init.lua b/mods/tubelib2/init.lua new file mode 100644 index 0000000..e44736f --- /dev/null +++ b/mods/tubelib2/init.lua @@ -0,0 +1,14 @@ +tubelib2 = {} + +-- Load support for I18n. +tubelib2.S = minetest.get_translator("tubelib2") + +local MP = minetest.get_modpath("tubelib2") +dofile(MP .. "/internal2.lua") +dofile(MP .. "/internal1.lua") +dofile(MP .. "/tube_api.lua") +dofile(MP .. "/storage.lua") +-- Only for testing/demo purposes +if minetest.settings:get_bool("tubelib2_testingblocks_enabled") == true then + dofile(MP .. "/tube_test.lua") +end diff --git a/mods/tubelib2/internal1.lua b/mods/tubelib2/internal1.lua new file mode 100644 index 0000000..7fcd603 --- /dev/null +++ b/mods/tubelib2/internal1.lua @@ -0,0 +1,252 @@ +--[[ + + Tube Library 2 + ============== + + Copyright (C) 2018-2020 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + internal1.lua + + First level functions behind the API + +]]-- + +-- for lazy programmers +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos +local M = minetest.get_meta + +-- Load support for I18n. +local S = tubelib2.S + +local Tube = tubelib2.Tube +local Turn180Deg = tubelib2.Turn180Deg +local Dir6dToVector = tubelib2.Dir6dToVector +local tValidNum = {[0] = true, true, true} -- 0..2 are valid + +local function get_pos(pos, dir) + return vector.add(pos, Dir6dToVector[dir or 0]) +end + +local function fdir(self, player) + local pitch = player:get_look_vertical() + if pitch > 1.0 and self.valid_dirs[6] then -- up? + return 6 + elseif pitch < -1.0 and self.valid_dirs[5] then -- down? + return 5 + elseif not self.valid_dirs[1] then + return 6 + else + return minetest.dir_to_facedir(player:get_look_dir()) + 1 + end +end + +local function get_player_data(self, placer, pointed_thing) + if placer and pointed_thing and pointed_thing.type == "node" then + if placer:get_player_control().sneak then + return pointed_thing.under, fdir(self, placer) + else + return nil, fdir(self, placer) + end + end +end + + +-- Used to determine the node side to the tube connection. +-- Function returns the first found dir value +-- to a primary node. +-- Only used by convert.set_pairing() +function Tube:get_primary_dir(pos) + -- Check all valid positions + for dir = 1,6 do + if self:is_primary_node(pos, dir) then + return dir + end + end +end + +-- pos/dir are the pos of the stable secondary node pointing to the head tube node. +function Tube:del_from_cache(pos, dir) + local key = P2S(pos) + if self.connCache[key] and self.connCache[key][dir] then + local pos2 = self.connCache[key][dir].pos2 + local dir2 = self.connCache[key][dir].dir2 + local key2 = P2S(pos2) + if self.connCache[key2] and self.connCache[key2][dir2] then + self.connCache[key2][dir2] = nil + if self.debug_info then self.debug_info(pos2, "del") end + end + self.connCache[key][dir] = nil + if self.debug_info then self.debug_info(pos, "del") end + else + if self.debug_info then self.debug_info(pos, "noc") end + end +end + +-- pos/dir are the pos of the secondary nodes pointing to the head tube nodes. +function Tube:add_to_cache(pos1, dir1, pos2, dir2) + local key = P2S(pos1) + if not self.connCache[key] then + self.connCache[key] = {} + end + self.connCache[key][dir1] = {pos2 = pos2, dir2 = dir2} + if self.debug_info then self.debug_info(pos1, "add") end +end + +-- pos/dir are the pos of the secondary nodes pointing to the head tube nodes. +function Tube:update_secondary_node(pos1, dir1, pos2, dir2) + local node,_ = self:get_secondary_node(pos1) + if node then + local ndef = minetest.registered_nodes[node.name] or {} + -- New functions + if ndef.tubelib2_on_update2 then + ndef.tubelib2_on_update2(pos1, dir1, self, node) + elseif self.clbk_update_secondary_node2 then + self.clbk_update_secondary_node2(pos1, dir1, self, node) + -- Legacy functions + elseif ndef.tubelib2_on_update then + ndef.tubelib2_on_update(node, pos1, dir1, pos2, Turn180Deg[dir2]) + elseif self.clbk_update_secondary_node then + self.clbk_update_secondary_node(node, pos1, dir1, pos2, Turn180Deg[dir2]) + end + end +end + +function Tube:infotext(pos1, pos2) + if self.show_infotext then + if pos1 and pos2 then + if vector.equals(pos1, pos2) then + M(pos1):set_string("infotext", S("Not connected!")) + else + M(pos1):set_string("infotext", S("Connected to @1", P2S(pos2))) + end + end + end +end + +-------------------------------------------------------------------------------------- +-- pairing functions +-------------------------------------------------------------------------------------- + +-- Pairing helper function. NOT USED (see internal2.lua)!!! +function Tube:store_teleport_data(pos, peer_pos) + local meta = M(pos) + meta:set_string("tele_pos", P2S(peer_pos)) + meta:set_string("channel", nil) + meta:set_string("formspec", nil) + meta:set_string("infotext", S("Paired with @1", P2S(peer_pos))) + return meta:get_int("tube_dir") +end + +------------------------------------------------------------------------------- +-- update-after/get-dir functions +------------------------------------------------------------------------------- + +function Tube:update_after_place_node(pos, dirs) + -- Check all valid positions + local lRes= {} + dirs = dirs or self.dirs_to_check + for _,dir in ipairs(dirs) do + local npos, d1, d2, num = self:add_tube_dir(pos, dir) + if npos and self.valid_dirs[d1] and self.valid_dirs[d2] and num < 2 then + self.clbk_after_place_tube(self:get_tube_data(npos, d1, d2, num+1)) + lRes[#lRes+1] = dir + end + end + return lRes +end + +function Tube:update_after_dig_node(pos, dirs) + -- Check all valid positions + local lRes= {} + dirs = dirs or self.dirs_to_check + for _,dir in ipairs(dirs) do + local npos, d1, d2, num = self:del_tube_dir(pos, dir) + if npos and self.valid_dirs[d1] and self.valid_dirs[d2] and tValidNum[num] then + self.clbk_after_place_tube(self:get_tube_data(npos, d1, d2, num)) + lRes[#lRes+1] = dir + end + end + return lRes +end + +function Tube:update_after_place_tube(pos, placer, pointed_thing) + local preferred_pos, fdir = get_player_data(self, placer, pointed_thing) + local dir1, dir2, num_tubes = self:determine_tube_dirs(pos, preferred_pos, fdir) + if dir1 == nil then + return false + end + if self.valid_dirs[dir1] and self.valid_dirs[dir2] and tValidNum[num_tubes] then + self.clbk_after_place_tube(self:get_tube_data(pos, dir1, dir2, num_tubes)) + end + + if num_tubes >= 1 then + local npos, d1, d2, num = self:add_tube_dir(pos, dir1) + if npos and self.valid_dirs[d1] and self.valid_dirs[d2] and num < 2 then + self.clbk_after_place_tube(self:get_tube_data(npos, d1, d2, num+1)) + end + end + + if num_tubes >= 2 then + local npos, d1, d2, num = self:add_tube_dir(pos, dir2) + if npos and self.valid_dirs[d1] and self.valid_dirs[d2] and num < 2 then + self.clbk_after_place_tube(self:get_tube_data(npos, d1, d2, num+1)) + end + end + return true, dir1, dir2, num_tubes +end + +function Tube:update_after_dig_tube(pos, param2) + local dir1, dir2 = self:decode_param2(pos, param2) + + local npos, d1, d2, num = self:del_tube_dir(pos, dir1) + if npos and self.valid_dirs[d1] and self.valid_dirs[d2] and tValidNum[num] then + self.clbk_after_place_tube(self:get_tube_data(npos, d1, d2, num)) + else + dir1 = nil + end + + npos, d1, d2, num = self:del_tube_dir(pos, dir2) + if npos and self.valid_dirs[d1] and self.valid_dirs[d2] and tValidNum[num] then + self.clbk_after_place_tube(self:get_tube_data(npos, d1, d2, num)) + else + dir2 = nil + end + + return dir1, dir2 +end + +-- Used by chat commands, when tubes are placed e.g. via WorldEdit +function Tube:replace_nodes(pos1, pos2, dir1, dir2) + self.clbk_after_place_tube(self:get_tube_data(pos1, dir1, dir2, 1)) + local pos = get_pos(pos1, dir1) + while not vector.equals(pos, pos2) do + self.clbk_after_place_tube(self:get_tube_data(pos, dir1, dir2, 2)) + pos = get_pos(pos, dir1) + end + self.clbk_after_place_tube(self:get_tube_data(pos2, dir1, dir2, 1)) +end + +function Tube:switch_nodes(pos, dir, state) + pos = get_pos(pos, dir) + local old_dir = dir + while pos do + local param2 = self:get_primary_node_param2(pos) + if param2 then + local dir1, dir2, num_conn = self:decode_param2(pos, param2) + self.clbk_after_place_tube(self:get_tube_data(pos, dir1, dir2, num_conn, state)) + if dir1 == Turn180Deg[old_dir] then + pos = get_pos(pos, dir2) + old_dir = dir2 + else + pos = get_pos(pos, dir1) + old_dir = dir1 + end + else + break + end + end +end diff --git a/mods/tubelib2/internal2.lua b/mods/tubelib2/internal2.lua new file mode 100644 index 0000000..5a641d0 --- /dev/null +++ b/mods/tubelib2/internal2.lua @@ -0,0 +1,430 @@ +--[[ + + Tube Library 2 + ============== + + Copyright (C) 2018-2021 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + internal.lua + +]]-- + +-- for lazy programmers +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos +local M = minetest.get_meta + +-- Load support for I18n. +local S = tubelib2.S + +local Tube = {} + +local Turn180Deg = {[0]=0,3,4,1,2,6,5} + +-- To calculate param2 based on dir6d information +local DirToParam2 = { + -- dir1 / dir2 ==> param2 / type (Angled/Straight) + [12] = {11, "A"}, + [13] = {12, "S"}, + [14] = {14, "A"}, + [15] = { 8, "A"}, + [16] = {10, "A"}, + [23] = { 7, "A"}, + [24] = {21, "S"}, + [25] = { 3, "A"}, + [26] = {19, "A"}, + [34] = { 5, "A"}, + [35] = { 0, "A"}, + [36] = {20, "A"}, + [45] = {15, "A"}, + [46] = {13, "A"}, + [56] = { 4, "S"}, +} + +-- To retrieve dir6d values from the nodes param2 +local Param2ToDir = {} +for k,item in pairs(DirToParam2) do + Param2ToDir[item[1]] = k +end + +-- For neighbour position calculation +local Dir6dToVector = {[0] = + {x=0, y=0, z=0}, + {x=0, y=0, z=1}, + {x=1, y=0, z=0}, + {x=0, y=0, z=-1}, + {x=-1, y=0, z=0}, + {x=0, y=-1, z=0}, + {x=0, y=1, z=0}, +} + +tubelib2.Tube = Tube +tubelib2.Turn180Deg = Turn180Deg +tubelib2.DirToParam2 = DirToParam2 +tubelib2.Param2ToDir = Param2ToDir +tubelib2.Dir6dToVector = Dir6dToVector + + +-- +-- Tubelib2 Methods +-- + +function Tube:get_node_lvm(pos) + local node = minetest.get_node_or_nil(pos) + if node then + return node + end + local vm = minetest.get_voxel_manip() + local MinEdge, MaxEdge = vm:read_from_map(pos, pos) + local data = vm:get_data() + local param2_data = vm:get_param2_data() + local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge}) + local idx = area:index(pos.x, pos.y, pos.z) + node = { + name = minetest.get_name_from_content_id(data[idx]), + param2 = param2_data[idx] + } + return node +end + +-- Read param2 from a primary node at the given position. +-- If dir == nil then node_pos = pos +-- Function returns param2, new_pos or nil +function Tube:get_primary_node_param2(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + self.node = self:get_node_lvm(npos) + if self.primary_node_names[self.node.name] then + return self.node.param2, npos + end +end + +-- Check if node at given position is a tube node +-- If dir == nil then node_pos = pos +-- Function returns true/false +function Tube:is_primary_node(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + local node = self:get_node_lvm(npos) + return self.primary_node_names[node.name] +end + +-- Get secondary node at given position +-- If dir == nil then node_pos = pos +-- Function returns node and new_pos or nil +function Tube:get_secondary_node(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + local node = self:get_node_lvm(npos) + if self.secondary_node_names[node.name] then + return node, npos + end +end + +-- Get special registered nodes at given position +-- If dir == nil then node_pos = pos +-- Function returns node and new_pos or nil +function Tube:get_special_node(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + local node = self:get_node_lvm(npos) + if self.special_node_names[node.name] then + return node, npos + end +end + +-- Check if node at given position is a secondary node +-- If dir == nil then node_pos = pos +-- Function returns true/false +function Tube:is_secondary_node(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + local node = self:get_node_lvm(npos) + return self.secondary_node_names[node.name] +end + +-- Check if node at given position is a special node +-- If dir == nil then node_pos = pos +-- Function returns true/false +function Tube:is_special_node(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + local node = self:get_node_lvm(npos) + return self.special_node_names[node.name] +end + +-- Check if node has a connection on the given dir +function Tube:connected(pos, dir) + return self:is_primary_node(pos, dir) or self:is_secondary_node(pos, dir) +end + +function Tube:get_next_tube(pos, dir) + local param2, npos = self:get_primary_node_param2(pos, dir) + if param2 then + local val = Param2ToDir[param2 % 32] or 0 + local dir1, dir2 = math.floor(val / 10), val % 10 + local num_conn = math.floor(param2 / 32) or 0 + local odir = Turn180Deg[dir] + if odir == dir1 then + return npos, dir2, num_conn + elseif odir == dir2 then + return npos, dir1, num_conn + end + return + end + return self:get_next_teleport_node(pos, dir) +end + +-- Return param2 and tube type ("A"/"S") +function Tube:encode_param2(dir1, dir2, num_conn) + if dir1 and dir2 and num_conn then + if dir1 > dir2 then + dir1, dir2 = dir2, dir1 + end + local param2, _type = unpack(DirToParam2[dir1 * 10 + dir2] or {0, "S"}) + return (num_conn * 32) + param2, _type + end + return 0, "S" +end + + +-- Return dir1, dir2, num_conn +function Tube:decode_param2(pos, param2) + local val = Param2ToDir[param2 % 32] + if val then + local dir1, dir2 = math.floor(val / 10), val % 10 + local num_conn = math.floor(param2 / 32) + return dir1, dir2, num_conn + end +end + +function Tube:repair_tube(pos, dir) + local param2, npos = self:get_primary_node_param2(pos, dir) + if param2 then + local dir1, dir2 = self:decode_param2(npos, param2) + local param2, tube_type = self:encode_param2(dir1, dir2, 2) + self.clbk_after_place_tube(npos, param2, tube_type, 2) + end +end + +-- Return node next to pos in direction 'dir' +function Tube:get_node(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + return npos, self:get_node_lvm(npos) +end + +-- format and return given data as table +function Tube:get_tube_data(pos, dir1, dir2, num_tubes, state) + local param2, tube_type = self:encode_param2(dir1, dir2, num_tubes) + return pos, param2, tube_type, num_tubes, state +end + +-- Return pos for a primary_node and true if num_conn < 2, else false +function Tube:friendly_primary_node(pos, dir) + local param2, npos = self:get_primary_node_param2(pos, dir) + if param2 then + local _,_,num_conn = self:decode_param2(npos, param2) + -- tube node with max one connection? + return npos, (num_conn or 2) < 2 + end +end + +function Tube:vector_to_dir(v) + if v.y > 0 then + return 6 + elseif v.y < 0 then + return 5 + else + return minetest.dir_to_facedir(v) + 1 + end +end + +-- Check all 6 possible positions for known nodes considering preferred_pos +-- and the players fdir and return dir1, dir2 and the number of tubes to connect to (0..2). +function Tube:determine_tube_dirs(pos, preferred_pos, fdir) + local tbl = {} + local allowed = table.copy(self.valid_dirs) + + -- If the node at players "prefered position" is a tube, + -- then the other side of the new tube shall point to the player. + if preferred_pos then + local _, friendly = self:friendly_primary_node(preferred_pos) + if friendly then + local v = vector.direction(pos, preferred_pos) + local dir1 = self:vector_to_dir(v) + local dir2 = Turn180Deg[fdir] + return dir1, dir2, 1 + end + end + -- Check for primary nodes (tubes) + for dir = 1,6 do + if allowed[dir] then + local npos, friendly = self:friendly_primary_node(pos, dir) + if npos then + if not friendly then + allowed[dir] = false + else + if preferred_pos and vector.equals(npos, preferred_pos) then + preferred_pos = nil + table.insert(tbl, 1, dir) + else + table.insert(tbl, dir) + end + end + end + end + end + + -- If no tube around the pointed pos and player prefers a position, + -- then the new tube shall point to the player. + if #tbl == 0 and preferred_pos and fdir and allowed[Turn180Deg[fdir]] then + tbl[1] = Turn180Deg[fdir] + -- Already 2 dirs found? + elseif #tbl >= 2 then + return tbl[1], tbl[2], 2 + end + + -- Check for secondary nodes (chests and so on) + for dir = 1,6 do + if allowed[dir] then + local node,npos = self:get_secondary_node(pos, dir) + if npos then + if self:is_valid_dir(node, Turn180Deg[dir]) == false then + allowed[dir] = false + else + if preferred_pos and vector.equals(npos, preferred_pos) then + preferred_pos = nil + table.insert(tbl, 2, dir) + else + table.insert(tbl, dir) + end + end + end + end + end + + -- player pointed to an unknown node to force the tube orientation? + if preferred_pos and fdir then + if tbl[1] == Turn180Deg[fdir] and allowed[fdir] then + tbl[2] = fdir + elseif allowed[Turn180Deg[fdir]] then + tbl[2] = Turn180Deg[fdir] + end + end + + -- dir1, dir2 still unknown? + if fdir then + if #tbl == 0 and allowed[Turn180Deg[fdir]] then + tbl[1] = Turn180Deg[fdir] + end + if #tbl == 1 and allowed[Turn180Deg[tbl[1]]] then + tbl[2] = Turn180Deg[tbl[1]] + elseif #tbl == 1 and tbl[1] ~= Turn180Deg[fdir] and allowed[Turn180Deg[fdir]] then + tbl[2] = Turn180Deg[fdir] + end + end + + if #tbl >= 2 and tbl[1] ~= tbl[2] then + local num_tubes = (self:connected(pos, tbl[1]) and 1 or 0) + + (self:connected(pos, tbl[2]) and 1 or 0) + return tbl[1], tbl[2], math.min(2, num_tubes) + end +end + +-- Determine a tube side without connection, increment the number of connections +-- and return the new data to be able to update the node: +-- new_pos, dir1, dir2, num_connections (1, 2) +function Tube:add_tube_dir(pos, dir) + local param2, npos = self:get_primary_node_param2(pos, dir) + if param2 then + local d1, d2, num = self:decode_param2(npos, param2) + if not num then return end + -- if invalid face, do nothing + if self:is_valid_dir_pos(pos, dir) == false then return end + -- not already connected to the new tube? + dir = Turn180Deg[dir] + if d1 ~= dir and dir ~= d2 then + if num == 0 then + d1 = dir + elseif num == 1 then + -- determine, which of d1, d2 has already a connection + if self:connected(npos, d1) then + d2 = dir + else + d1 = dir + end + end + end + return npos, d1, d2, num + end +end + +-- Decrement the number of tube connections +-- and return the new data to be able to update the node: +-- new_pos, dir1, dir2, num_connections (0, 1) +function Tube:del_tube_dir(pos, dir) + local param2, npos = self:get_primary_node_param2(pos, dir) + if param2 then + local d1, d2, num = self:decode_param2(npos, param2) + -- check if node is connected to the given pos + dir = Turn180Deg[dir] + if d1 == dir or dir == d2 then + return npos, d1, d2, math.max((num or 1) - 1, 0) + end + end +end + +-- Pairing helper function +function Tube:store_teleport_data(pos, peer_pos) + local meta = M(pos) + meta:set_string("tele_pos", P2S(peer_pos)) + meta:set_string("channel", nil) + meta:set_string("formspec", nil) + meta:set_string("infotext", S("Connected to @1", P2S(peer_pos))) + return meta:get_int("tube_dir") +end + +-- Jump over the teleport nodes to the next tube node +function Tube:get_next_teleport_node(pos, dir) + if pos then + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + if self:is_valid_dir_pos(npos, Turn180Deg[dir]) == false then + return + end + local meta = M(npos) + local s = meta:get_string("tele_pos") + if s ~= "" then + local tele_pos = S2P(s) + local tube_dir = M(tele_pos):get_int("tube_dir") + if tube_dir ~= 0 then + return tele_pos, tube_dir + end + end + end +end + +function Tube:dbg_out() + for pos1,item1 in pairs(self.connCache) do + for dir1,item2 in pairs(item1) do + print("pos1="..pos1..", dir1="..dir1..", pos2="..P2S(item2.pos2)..", dir2="..item2.dir2) + end + end +end + +-- Walk to the end of the tube line and return pos and outdir of both head tube nodes. +-- If no tube is available, return nil +function Tube:walk_tube_line(pos, dir) + local cnt = 0 + if dir then + while cnt <= self.max_tube_length do + local new_pos, new_dir, num = self:get_next_tube(pos, dir) + if not new_pos then break end + if cnt > 0 and num ~= 2 and self:is_primary_node(new_pos, new_dir) then + self:repair_tube(new_pos, new_dir) + end + pos, dir = new_pos, new_dir + cnt = cnt + 1 + end + if cnt > 0 then + return pos, dir, cnt + end + end + return table.copy(pos), dir, 0 +end diff --git a/mods/tubelib2/intllib.sh b/mods/tubelib2/intllib.sh new file mode 100755 index 0000000..5b9294f --- /dev/null +++ b/mods/tubelib2/intllib.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +../intllib/tools/xgettext.sh ./tube_api.lua ./internal1.lua ./internal2.lua \ No newline at end of file diff --git a/mods/tubelib2/locale/template.txt b/mods/tubelib2/locale/template.txt new file mode 100644 index 0000000..3c47e84 --- /dev/null +++ b/mods/tubelib2/locale/template.txt @@ -0,0 +1,8 @@ +# textdomain: tubelib2 +Not connected!= +Paired with @1= +Connected to @1= +Maximum length reached!= +Pairing is missing= +Connection to a tube is missing!= +Pairing is missing (@1)= diff --git a/mods/tubelib2/locale/tubelib2.de.tr b/mods/tubelib2/locale/tubelib2.de.tr new file mode 100644 index 0000000..b41fa5e --- /dev/null +++ b/mods/tubelib2/locale/tubelib2.de.tr @@ -0,0 +1,8 @@ +# textdomain: tubelib2 +Not connected!=Nicht verbunden! +Paired with @1=Gepaart mit @1 +Connected to @1=erbunden mit @1 +Maximum length reached!=Maximale Länge erreicht! +Pairing is missing=Das Pairing fehlt +Connection to a tube is missing!=Eine Verbindung zu einer Röhre fehlt! +Pairing is missing (@1)=Das Pairing fehlt (@1) diff --git a/mods/tubelib2/mod.conf b/mods/tubelib2/mod.conf new file mode 100644 index 0000000..4c89e04 --- /dev/null +++ b/mods/tubelib2/mod.conf @@ -0,0 +1,3 @@ +name=tubelib2 +description = A library for mods which need connecting tubes / pipes / cables or similar. +optional_depends = default, screwdriver diff --git a/mods/tubelib2/settingtypes.txt b/mods/tubelib2/settingtypes.txt new file mode 100644 index 0000000..f6860e1 --- /dev/null +++ b/mods/tubelib2/settingtypes.txt @@ -0,0 +1,3 @@ +# Enable/disable test blocks +tubelib2_testingblocks_enabled (enable test blocks) bool false + diff --git a/mods/tubelib2/storage.lua b/mods/tubelib2/storage.lua new file mode 100644 index 0000000..6a88bd0 --- /dev/null +++ b/mods/tubelib2/storage.lua @@ -0,0 +1,152 @@ +--[[ + + Tube Library 2 + ============== + + Copyright (C) 2017-2020 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + storage.lua + +]]-- + +-- +-- Data maintenance +-- +local MemStore = {} +local storage = minetest.get_mod_storage() + +local function update_mod_storage() + local gametime = minetest.get_gametime() + for k,v in pairs(MemStore) do + if v.used then + v.used = false + v.best_before = gametime + (60 * 30) -- 30 min + storage:set_string(k, minetest.serialize(v)) + elseif v.best_before < gametime then + storage:set_string(k, minetest.serialize(v)) + MemStore[k] = nil -- remove from memory + end + end + -- run every 10 minutes + minetest.after(600, update_mod_storage) +end + +minetest.register_on_shutdown(function() + for k,v in pairs(MemStore) do + storage:set_string(k, minetest.serialize(v)) + end +end) + +minetest.after(600, update_mod_storage) + +local function empty_block(block) + local empty = true + local tbl = minetest.deserialize(block) + for k,v in pairs(tbl) do + if k ~= "used" and k ~= "best_before" then + empty = false + end + end + return empty +end + +minetest.after(1, function() + local tbl = storage:to_table() + for k,v in pairs(tbl.fields) do + if empty_block(v) then + storage:set_string(k, "") + end + end +end) + +-- +-- Local helper functions +-- +local function new_block(block_key) + local data = storage:get_string(block_key) + if data == nil or data == "" then -- TODO: change for v5 + MemStore[block_key] = {} + else + MemStore[block_key] = minetest.deserialize(data) + end + return MemStore[block_key] +end + +local function new_node(block, node_key) + block[node_key] = {} + return block[node_key] +end + +local function unlock(pos) + local block_key = math.floor((pos.z+32768)/16)*4096*4096 + + math.floor((pos.y+32768)/16)*4096 + math.floor((pos.x+32768)/16) + local node_key = (pos.z%16)*16*16 + (pos.y%16)*16 + (pos.x%16) + local block = MemStore[block_key] or new_block(block_key) + block.used = true + return block, node_key +end + +local function keys_to_pos(block_key, node_key) + local f = math.floor + block_key = tonumber(block_key) or 0 + node_key = tonumber(node_key) or 0 + local x = ((f(block_key % 0x1000) * 0x10) - 32768) + (node_key % 0x10) + block_key, node_key = f(block_key / 0x1000), f(node_key / 0x10) + local y = ((f(block_key % 0x1000) * 0x10) - 32768) + (node_key % 0x10) + block_key, node_key = f(block_key / 0x1000), f(node_key / 0x10) + local z = ((f(block_key % 0x1000) * 0x10) - 32768) + (node_key % 0x10) + return {x = x, y = y, z = z} +end + +------------------------------------------------------------------------------- +-- API functions for a node related and high efficient storage table +-- for all kind of node data. +------------------------------------------------------------------------------- + +-- To be called when a node is placed +function tubelib2.init_mem(pos) + local block, node_key = unlock(pos) + return new_node(block, node_key) +end + +-- To get the node data table +function tubelib2.get_mem(pos) + local block, node_key = unlock(pos) + return block[node_key] or new_node(block, node_key) +end + +-- To be called when a node is removed +function tubelib2.del_mem(pos) + local block, node_key = unlock(pos) + block[node_key] = nil +end + +-- Read a value, or return the default value if not available +function tubelib2.get_mem_data(pos, key, default) + return tubelib2.get_mem(pos)[key] or default +end + +function tubelib2.walk_over_all(clbk, key) + local data = storage:to_table() + for block_key,sblock in pairs(data.fields) do + local block = minetest.deserialize(sblock) + for node_key,mem in pairs(block) do + if mem and node_key ~= "used" and node_key ~= "best_before" then + if key == nil or (type(mem) == "table" and mem[key] ~= nil) then + local pos = keys_to_pos(block_key, node_key) + local node = tubelib2.get_node_lvm(pos) + if key ~= nil then + -- only specified 'key' + clbk(pos, node, {[key] = mem[key]}) + else + -- without specified 'key' + clbk(pos, node, mem) + end + end + end + end + end +end diff --git a/mods/tubelib2/textures/tubelib2_conn.png b/mods/tubelib2/textures/tubelib2_conn.png new file mode 100644 index 0000000000000000000000000000000000000000..a2723962cafce818e71482d55dcdad81c65b3cf6 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9Ea{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaMM3p^r=85p>QL70(Y)*K0tV1Q4E>x3!&K<5Ad|L5FU`v=I9^K@|x;kcfh uz`(dRbaj}5Fb|J|lL?DFV_4HFUKWPLU!t!smN0@WWbkzLb6Mw<&;$UjgD4CD literal 0 HcmV?d00001 diff --git a/mods/tubelib2/textures/tubelib2_hole.png b/mods/tubelib2/textures/tubelib2_hole.png new file mode 100644 index 0000000000000000000000000000000000000000..96193dd4d45b0940001ff714e600f92a83b30af6 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9Ea{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaMM3p^r=85p>QL70(Y)*K0-AWwi#h^tR#;e;vu|Ns973fwhoP6p{L3GxeO zaCmkj4aiaQba4#fxSpKA!JC?pa3ZSHr-g~pIqdKKsXI6~2$|$6EM{PilRSB=kLx#3 OHG`+CpUXO@geCwmkuT=} literal 0 HcmV?d00001 diff --git a/mods/tubelib2/textures/tubelib2_marker_cube.png b/mods/tubelib2/textures/tubelib2_marker_cube.png new file mode 100644 index 0000000000000000000000000000000000000000..132145138709c484888462feae86a83a85fb4f08 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9Ea{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaMM1AIbUh3qG|7x=j4?=t-zdIl)SToU9L+{w_{@a=vmkn8E`;uyklo%fg_ zSA&57%YnZ~SaVqC3uP;BXMZ_)Imb4qcl+OkG{=6JAa3^Qh?2OD?S!q?Yr@Y4b6>u7 mCGE0~Osme16OGM#KAz&&>J+uCj?@P^j=|H_&t;ucLK6UT20xVm literal 0 HcmV?d00001 diff --git a/mods/tubelib2/textures/tubelib2_source.png b/mods/tubelib2/textures/tubelib2_source.png new file mode 100644 index 0000000000000000000000000000000000000000..84944d15d897f39b4e5f3c5041f6edc9cd353d68 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|uDZ?(Crs)8|Np;Lc^eN% zu)rg-n1O*?7=#%aX3dcR3bL1Y`ns||;$r7A=l2!hZ~_V$dAc};a9mI3U}b7s6S_J~ zNl=nU;To5&z;texl12_yk!xLi3KtI?I5}Z!gQM^w-V_mr&$Hzc`7)9tfVvqxUHx3v IIVCg!0AI-G!lpRn`NfqQE1vn1O*?7=#%aX3dcR3bL1Y`ns||;t~*)v^p#K>=#hT&eO#) zgyVWL2P;$Cn$Xo@PMxeV4o!`Ts?KcJnS9$?M5E^LMDqGL7_MV=Fy!U+T`_5$t3y(& gSP#QXR!LR{oyXF$R)=gC15IM^boFyt=akR{0QyofmH+?% literal 0 HcmV?d00001 diff --git a/mods/tubelib2/textures/tubelib2_tool.png b/mods/tubelib2/textures/tubelib2_tool.png new file mode 100644 index 0000000000000000000000000000000000000000..594dc1f1c33efe18ab51b07058d6ac7f93774985 GIT binary patch literal 934 zcmV;X16lluP)dQ@0+Qek%> zaB^>EX>4U6ba`-PAZ2)IW&i+q+MQKdk{clm{pS=p0$Nyf9I;j9204BnbldT`oT*fV zq2YC41h`8GT$(&G8`=?TkkP{eizI9ysw= z{HgahLwd7Q`>J^0T;B}w^^r}0Iq7Z4?glH^I7H%q3~AC51dPa}?nz z8J#XRi9%AvF-D9Lx(jttNeABsk1?W)4fgcCtj{FOLsI~&Z+P1>G zE0Hh7d(d&1&-g9j98ZADi#C7G%y)?i-(}{w^%7{+d&Xb!jfB_Ztu^Xe^XyR9i~`Mf zg+08XdmqYF<(QVl{HWJAVAxQ_Rj7dd*)FW3JH}h{ zJj*NXCX)cJge~RR_5W|-UpKDRcZg_T=sQ?A7w@z%ofygfU>mYPkI=ps$*b&`*tLIK zIK^{kEnk)UCRar(9FNECN<>V8--9;2l7_$VA}>!?x+`a+>EmwNCsNu~o!~C*E6c2u z$-z4Iv(``5Uta7WL|F&yyR}hs>h0XSkgc>aYZE+|T$9Z@d+9MDqq7^ITd~F@YSve8 zWyCVaDEOo1C3SJUuT4e`IkV(hq8JRfL<`H>N=oMH#p1BuG;+a_)nJL(yTF=Pj*XUN zf^jW5Ok9gu@?1Vf&$i6)JeU`6s}HGFr0HsPB(uTAL9f#!H8rnw%m&X#^;SZ~TUYQ1 zsXItqxo}1(|A-I-;b4G>usKY#t;V_^B_Y7IH@Eb98f;F)SHzWW600eVF zNmK|32nc)#WQYI&0JBL%K~y-)V_+Z>ER~ZJ+_P}u|LGYS|B=O+h&6!K-2Bt+!lvI6;x#X;^) z4C~IxyaaMM3p^r=85p>QL70(Y)*K0tV1Q4Et50U(gem?1|Nq}4m1h9bRubeF%;50s zMjDW#l++r%wwLqjT7`Q&V?vZV)obRanfhaE^Ge^!)wZK-CPM Lu6{1-oD!M= self.max_tube_length then -- line to long? + -- reset next tube(s) to head tube(s) again + local param2 = self:encode_param2(dir1, dir2, 2) + self:update_after_dig_tube(pos1, param2) + M(get_pos(pos1, dir1)):set_string("infotext", S("Maximum length reached!")) + M(get_pos(pos1, dir2)):set_string("infotext", S("Maximum length reached!")) + return false + end + self:infotext(fpos1, fpos2) + self:infotext(fpos2, fpos1) + -- Translate fpos/fdir pointing to the secondary node into + -- spos/sdir of the secondary node pointing to the tube. + local spos1, sdir1 = get_pos(fpos1,fdir1), Turn180Deg[fdir1] + local spos2, sdir2 = get_pos(fpos2,fdir2), Turn180Deg[fdir2] + self:del_from_cache(spos1, sdir1) + self:del_from_cache(spos2, sdir2) + self:add_to_cache(spos1, sdir1, spos2, sdir2) + self:add_to_cache(spos2, sdir2, spos1, sdir1) + self:update_secondary_node(spos1, sdir1, spos2, sdir2) + self:update_secondary_node(spos2, sdir2, spos1, sdir1) + return true +end + +local function update3(self, pos, dir1, dir2) + if pos and dir1 and dir2 then + local fpos1,fdir1,cnt1 = self:walk_tube_line(pos, dir1) + local fpos2,fdir2,cnt2 = self:walk_tube_line(pos, dir2) + self:infotext(fpos1, fpos2) + self:infotext(fpos2, fpos1) + -- Translate fpos/fdir pointing to the secondary node into + -- spos/sdir of the secondary node pointing to the tube. + if fpos1 and fpos2 and fdir1 and fdir2 then + local spos1, sdir1 = get_pos(fpos1,fdir1), Turn180Deg[fdir1] + local spos2, sdir2 = get_pos(fpos2,fdir2), Turn180Deg[fdir2] + self:del_from_cache(spos1, sdir1) + self:del_from_cache(spos2, sdir2) + self:add_to_cache(spos1, sdir1, spos2, sdir2) + self:add_to_cache(spos2, sdir2, spos1, sdir1) + self:update_secondary_node(spos1, sdir1, spos2, sdir2) + self:update_secondary_node(spos2, sdir2, spos1, sdir1) + return dir1, dir2, fpos1, fpos2, fdir1, fdir2, cnt1 or 0, cnt2 or 0 + end + return dir1, dir2, pos, pos, dir1, dir2, cnt1 or 0, cnt2 or 0 + end +end + +local function update_secondary_nodes_after_node_placed(self, pos, dirs) + dirs = dirs or self.dirs_to_check + -- check surrounding for secondary nodes + for _,dir in ipairs(dirs) do + local tmp, npos + if self.force_to_use_tubes then + tmp, npos = self:get_special_node(pos, dir) + else + tmp, npos = self:get_secondary_node(pos, dir) + end + if npos then + self:update_secondary_node(npos, Turn180Deg[dir], pos, dir) + self:update_secondary_node(pos, dir, npos, Turn180Deg[dir]) + end + end +end + +local function update_secondary_nodes_after_node_dug(self, pos, dirs) + dirs = dirs or self.dirs_to_check + -- check surrounding for secondary nodes + for _,dir in ipairs(dirs) do + local tmp, npos + if self.force_to_use_tubes then + tmp, npos = self:get_special_node(pos, dir) + else + tmp, npos = self:get_secondary_node(pos, dir) + end + if npos then + self:del_from_cache(npos, Turn180Deg[dir]) + self:del_from_cache(pos, dir) + self:update_secondary_node(npos, Turn180Deg[dir]) + self:update_secondary_node(pos, dir) + end + end +end + +-- +-- API Functions +-- + +function Tube:new(attr) + local o = { + dirs_to_check = attr.dirs_to_check or {1,2,3,4,5,6}, + max_tube_length = attr.max_tube_length or 1000, + primary_node_names = Tbl(attr.primary_node_names or {}), + secondary_node_names = Tbl(attr.secondary_node_names or {}), + valid_node_contact_sides = {}, + show_infotext = attr.show_infotext or false, + force_to_use_tubes = attr.force_to_use_tubes or false, + clbk_after_place_tube = attr.after_place_tube, + tube_type = attr.tube_type or "unknown", + pairingList = {}, -- teleporting nodes + connCache = {}, -- connection cache {pos1 = {dir1 = {pos2 = pos2, dir2 = dir2},...} + special_node_names = {}, -- use add_special_node_names() to register nodes + debug_info = attr.debug_info, -- debug_info(pos, text) + } + o.valid_dirs = Tbl(o.dirs_to_check) + setmetatable(o, self) + self.__index = self + if attr.valid_node_contact_sides then + o:set_valid_sides_multiple(attr.valid_node_contact_sides) + end + return o +end + +-- Register (foreign) tubelib compatible nodes. +function Tube:add_secondary_node_names(names) + for _,name in ipairs(names) do + self.secondary_node_names[name] = true + end +end + + +-- Defaults for valid sides configuration +local function invert_booleans(tab) + local inversion = {} + for key, value in pairs(tab) do + inversion[key] = not value + end + return inversion +end +local valid_sides_default_true = Tbl({"B", "R", "F", "L", "D", "U"}) +local valid_sides_default_false = invert_booleans(valid_sides_default_true) +local function complete_valid_sides(valid_sides, existing_defaults) + local valid_sides_complete = {} + for side, default_value in pairs(existing_defaults) do + local new_value = valid_sides[side] + if new_value == nil then + valid_sides_complete[side] = default_value + else + valid_sides_complete[side] = new_value + end + end + return valid_sides_complete +end + +-- Set sides which are valid +-- with a table of name = valid_sides pairs +function Tube:set_valid_sides_multiple(names) + for name, valid_sides in pairs(names) do + self:set_valid_sides(name, valid_sides) + end +end + +-- Set sides which are invalid +-- with a table of name = valid_sides pairs +function Tube:set_invalid_sides_multiple(names) + for name, invalid_sides in pairs(names) do + self:set_invalid_sides(name, invalid_sides) + end +end + +-- Set sides which are valid +-- will assume all sides not given are invalid +-- Only sets new sides, existing sides will remain +function Tube:set_valid_sides(name, valid_sides) + local existing_defaults = self.valid_node_contact_sides[name] or valid_sides_default_false + self.valid_node_contact_sides[name] = complete_valid_sides(Tbl(valid_sides), existing_defaults) +end + +-- Set sides which are invalid +-- will assume all sides not given are valid +-- Only sets new sides, existing sides will remain +function Tube:set_invalid_sides(name, invalid_sides) + local existing_defaults = self.valid_node_contact_sides[name] or valid_sides_default_true + self.valid_node_contact_sides[name] = complete_valid_sides(invert_booleans(Tbl(invalid_sides)), existing_defaults) +end + +-- Checks the list of valid node connection sides to +-- see if a given side can be connected to +function Tube:is_valid_side(name, side) + local valid_sides = self.valid_node_contact_sides[name] + if valid_sides then + return valid_sides[side] or false + end +end + +-- Checks if a particular node can be connected to +-- from a particular direction, taking into account orientation +function Tube:is_valid_dir(node, dir) + if node and dir ~= nil and self.valid_node_contact_sides[node.name] then + local side = tubelib2.dir_to_side(dir, node.param2) + return self:is_valid_side(node.name, side) + end +end + +-- Checks if a node at a particular position can be connected to +-- from a particular direction, taking into account orientation +function Tube:is_valid_dir_pos(pos, dir) + local node = self:get_node_lvm(pos) + return self:is_valid_dir(node, dir) +end + +-- Register further nodes, which should be updated after +-- a node/tube is placed/dug +function Tube:add_special_node_names(names) + for _,name in ipairs(names) do + self.special_node_names[name] = true + end +end + +-- Called for each connected node when the tube connection has been changed. +-- func(node, pos, out_dir, peer_pos, peer_in_dir) +function Tube:register_on_tube_update(update_secondary_node) + self.clbk_update_secondary_node = update_secondary_node +end + +-- Called for each connected node when the tube connection has been changed. +-- func(pos1, out_dir, self, node) +function Tube:register_on_tube_update2(update_secondary_node2) + self.clbk_update_secondary_node2 = update_secondary_node2 +end + +function Tube:get_pos(pos, dir) + return vector.add(pos, Dir6dToVector[dir or 0]) +end + +-- To be called after a secondary node is placed. +-- dirs is a list with valid dirs, like: {1,2,3,4} +function Tube:after_place_node(pos, dirs) + -- [s][f]----[n] x + -- s..secondary, f..far, n..near, x..node to be placed + for _,dir in ipairs(self:update_after_place_node(pos, dirs)) do + if pos and dir then + update1(self, pos, dir) + end + end + update_secondary_nodes_after_node_placed(self, pos, dirs) +end + +-- To be called after a tube/primary node is placed. +-- Returns false, if placing is not allowed +function Tube:after_place_tube(pos, placer, pointed_thing) + -- [s1][f1]----[n1] x [n2]-----[f2][s2] + -- s..secondary, f..far, n..near, x..node to be placed + local res,dir1,dir2 = self:update_after_place_tube(pos, placer, pointed_thing) + if res then -- node placed? + return update2(self, pos, dir1, pos, dir2) + end + return res +end + +function Tube:after_dig_node(pos, dirs) + -- [s][f]----[n] x + -- s..secondary, f..far, n..near, x..node to be removed + for _,dir in ipairs(self:update_after_dig_node(pos, dirs)) do + update1(self, pos, dir) + end + update_secondary_nodes_after_node_dug(self, pos, dirs) +end + +-- To be called after a tube/primary node is removed. +function Tube:after_dig_tube(pos, oldnode) + -- [s1][f1]----[n1] x [n2]-----[f2][s2] + -- s..secondary, f..far, n..near, x..node to be removed + + -- update tubes + if oldnode and oldnode.param2 then + local dir1, dir2 = self:update_after_dig_tube(pos, oldnode.param2) + if dir1 then update1(self, pos, dir1) end + if dir2 then update1(self, pos, dir2) end + + -- Update secondary nodes, if right beside + dir1, dir2 = self:decode_param2(pos, oldnode.param2) + local npos1,ndir1 = get_pos(pos, dir1),Turn180Deg[dir1] + local npos2,ndir2 = get_pos(pos, dir2),Turn180Deg[dir2] + self:del_from_cache(npos1,ndir1) + self:update_secondary_node(npos1,ndir1) + self:update_secondary_node(npos2,ndir2) + end +end + + +-- From source node to destination node via tubes. +-- pos is the source node position, dir the output dir +-- The returned pos is the destination position, dir +-- is the direction into the destination node. +function Tube:get_connected_node_pos(pos, dir) + local key = P2S(pos) + if self.connCache[key] and self.connCache[key][dir] then + local item = self.connCache[key][dir] + return item.pos2, Turn180Deg[item.dir2] + end + local fpos,fdir = self:walk_tube_line(pos, dir) + local spos = get_pos(fpos,fdir) + self:add_to_cache(pos, dir, spos, Turn180Deg[fdir]) + self:add_to_cache(spos, Turn180Deg[fdir], pos, dir) + return spos, fdir +end + +-- Check if node at given position is a tubelib2 compatible node, +-- able to receive and/or deliver items. +-- If dir == nil then node_pos = pos +-- Function returns the result (true/false), new pos, and the node +function Tube:compatible_node(pos, dir) + local npos = vector.add(pos, Dir6dToVector[dir or 0]) + local node = self:get_node_lvm(npos) + return self.secondary_node_names[node.name], npos, node +end + + +-- To be called from a repair tool in the case of a "WorldEdit" or with +-- legacy nodes corrupted tube line. +function Tube:tool_repair_tube(pos) + local param2 = self:get_primary_node_param2(pos) + if param2 then + local dir1, dir2 = self:decode_param2(pos, param2) + return update3(self, pos, dir1, dir2) + end +end + +-- To be called from a repair tool in the case, tube nodes are "unbreakable". +function Tube:tool_remove_tube(pos, sound) + if self:is_primary_node(pos) then + local _,node = self:get_node(pos) + minetest.sound_play({name=sound},{ + pos=pos, + gain=1, + max_hear_distance=5, + loop=false}) + minetest.remove_node(pos) + self:after_dig_tube(pos, node) + return true + end + return false +end + + +function Tube:prepare_pairing(pos, tube_dir, sFormspec) + local meta = M(pos) + if meta:get_int("tube_dir") ~= 0 then -- already prepared? + -- update tube_dir only + meta:set_int("tube_dir", tube_dir) + elseif tube_dir then + meta:set_int("tube_dir", tube_dir) + meta:set_string("channel", nil) + meta:set_string("infotext", S("Pairing is missing")) + meta:set_string("formspec", sFormspec) + else + meta:set_string("infotext", S("Connection to a tube is missing!")) + end +end + +function Tube:pairing(pos, channel) + if self.pairingList[channel] and not vector.equals(pos, self.pairingList[channel]) then + -- store peer position on both nodes + local peer_pos = self.pairingList[channel] + local tube_dir1 = self:store_teleport_data(pos, peer_pos) + local tube_dir2 = self:store_teleport_data(peer_pos, pos) + update2(self, pos, tube_dir1, peer_pos, tube_dir2) + self.pairingList[channel] = nil + return true + else + self.pairingList[channel] = pos + local meta = M(pos) + meta:set_string("channel", channel) + meta:set_string("infotext", S("Pairing is missing (@1)", channel)) + return false + end +end + +function Tube:stop_pairing(pos, oldmetadata, sFormspec) + -- unpair peer node + if oldmetadata and oldmetadata.fields then + if oldmetadata.fields.tele_pos then + local tele_pos = minetest.string_to_pos(oldmetadata.fields.tele_pos) + local peer_meta = M(tele_pos) + if peer_meta then + self:after_dig_node(tele_pos, {peer_meta:get_int("tube_dir")}) + peer_meta:set_string("channel", nil) + peer_meta:set_string("tele_pos", nil) + peer_meta:set_string("formspec", sFormspec) + peer_meta:set_string("infotext", S("Pairing is missing")) + end + elseif oldmetadata.fields.channel then + self.pairingList[oldmetadata.fields.channel] = nil + end + end +end + + +-- Used by chat commands, when tubes are placed e.g. via WorldEdit +function Tube:replace_tube_line(pos1, pos2) + if pos1 and pos2 and not vector.equals(pos1, pos2) then + local check = (((pos1.x == pos2.x) and 1) or 0) + + (((pos1.y == pos2.y) and 1) or 0) + + (((pos1.z == pos2.z) and 1) or 0) + if check == 2 then + local v = vector.direction(pos1, pos2) + local dir1 = self:vector_to_dir(v) + local dir2 = Turn180Deg[dir1] + + self:replace_nodes(pos1, pos2, dir1, dir2) + update3(self, pos1, dir1, dir2) + end + end +end + +-- Used to change the tube nodes texture (e.g. on/off state) +function Tube:switch_tube_line(pos, dir, state) + self:switch_nodes(pos, dir, state) +end + +-- Generator function to iterate over a tube line +-- Returns for each tube: i , pos, node +function Tube:get_tube_line(pos, dir) + if pos and dir then + self.ref = {pos = pos, dir = dir} + return function(self, i) + if i < self.max_tube_length then + local new_pos, new_dir, num = self:get_next_tube(self.ref.pos, self.ref.dir) + if new_pos then + self.ref.pos, self.ref.dir = new_pos, new_dir + i = i + 1 + return i, self.ref.pos, self.node + end + end + end, self, 0 + end +end diff --git a/mods/tubelib2/tube_test.lua b/mods/tubelib2/tube_test.lua new file mode 100644 index 0000000..576f967 --- /dev/null +++ b/mods/tubelib2/tube_test.lua @@ -0,0 +1,443 @@ +--[[ + + Tube Library 2 + ============== + + Copyright (C) 2017-2020 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + tube_test.lua + + THIS FILE IS ONLY FOR TESTING PURPOSES + +]]-- + +-- for lazy programmers +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos +local M = minetest.get_meta + + +-- Marker entities for debugging purposes +local function debug_info(pos, text) + local marker = minetest.add_entity(pos, "tubelib2:marker_cube") + if marker ~= nil then + if text == "add" then + marker:set_nametag_attributes({color = "#FF0000", text = "add__________"}) + elseif text == "del" then + marker:set_nametag_attributes({color = "#00FF00", text = "_____del_____"}) + elseif text == "noc" then + marker:set_nametag_attributes({color = "#0000FF", text = "__________noc"}) + end + minetest.after(6, marker.remove, marker) + end +end + +minetest.register_entity(":tubelib2:marker_cube", { + initial_properties = { + visual = "cube", + textures = { + "tubelib2_marker_cube.png", + "tubelib2_marker_cube.png", + "tubelib2_marker_cube.png", + "tubelib2_marker_cube.png", + "tubelib2_marker_cube.png", + "tubelib2_marker_cube.png", + }, + physical = false, + visual_size = {x = 1.1, y = 1.1}, + collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55}, + glow = 8, + }, + on_punch = function(self) + self.object:remove() + end, +}) + +-- Test tubes +local Tube = tubelib2.Tube:new({ + -- North, East, South, West, Down, Up + -- dirs_to_check = {1,2,3,4}, -- horizontal only + -- dirs_to_check = {5,6}, -- vertical only + dirs_to_check = {1,2,3,4,5,6}, + max_tube_length = 10, + show_infotext = true, + primary_node_names = {"tubelib2:tubeS", "tubelib2:tubeA"}, + secondary_node_names = {"default:chest", "default:chest_open", + "tubelib2:source", "tubelib2:junction", "tubelib2:teleporter"}, + after_place_tube = function(pos, param2, tube_type, num_tubes, tbl) + minetest.swap_node(pos, {name = "tubelib2:tube"..tube_type, param2 = param2}) + end, + --debug_info = debug_info, +}) + +Tube:set_valid_sides("tubelib2:source", {"F"}) +Tube:set_valid_sides("tubelib2:teleporter", {"F"}) + +Tube:register_on_tube_update(function(node, pos, out_dir, peer_pos, peer_in_dir) + local sdir = tubelib2.dir_to_string(out_dir) + if not peer_pos then + print(P2S(pos).." to the "..sdir..": Not connected") + elseif Tube:is_secondary_node(peer_pos) then + local node = minetest.get_node(peer_pos) + print(P2S(pos).." to the "..sdir..": Connected to "..node.name.." at "..P2S(peer_pos).."/"..peer_in_dir) + else + print(P2S(pos).." to the "..sdir..": Connected to "..P2S(peer_pos).."/"..peer_in_dir) + for i, pos, node in Tube:get_tube_line(pos, out_dir) do + print("walk", P2S(pos), node.name) + end + end +end) + + +minetest.register_node("tubelib2:tubeS", { + description = "Tubelib2 Test tube", + tiles = { -- Top, base, right, left, front, back + "tubelib2_tube.png", + "tubelib2_tube.png", + "tubelib2_tube.png", + "tubelib2_tube.png", + "tubelib2_hole.png", + "tubelib2_hole.png", + }, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + --local t = minetest.get_us_time() + if not Tube:after_place_tube(pos, placer, pointed_thing) then + minetest.remove_node(pos) + return true + end + --print("place time", minetest.get_us_time() - t) + return false + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + Tube:after_dig_tube(pos, oldnode, oldmetadata) + end, + + paramtype2 = "facedir", -- important! + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-2/8, -2/8, -4/8, 2/8, 2/8, 4/8}, + }, + }, + on_rotate = screwdriver.disallow, -- important! + paramtype = "light", + sunlight_propagates = true, + is_ground_content = false, + groups = {crumbly = 3, cracky = 3, snappy = 3}, +}) + +minetest.register_node("tubelib2:tubeA", { + description = "Tubelib2 Test tube", + tiles = { -- Top, base, right, left, front, back + "tubelib2_tube.png", + "tubelib2_hole.png", + "tubelib2_tube.png", + "tubelib2_tube.png", + "tubelib2_tube.png", + "tubelib2_hole.png", + }, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + Tube:after_dig_tube(pos, oldnode, oldmetadata) + end, + + paramtype2 = "facedir", -- important! + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-2/8, -4/8, -2/8, 2/8, 2/8, 2/8}, + {-2/8, -2/8, -4/8, 2/8, 2/8, -2/8}, + }, + }, + on_rotate = screwdriver.disallow, -- important! + paramtype = "light", + sunlight_propagates = true, + is_ground_content = false, + groups = {crumbly = 3, cracky = 3, snappy = 3, not_in_creative_inventory=1}, + drop = "tubelib2:tubeS", +}) + +local function push_item(pos) + local tube_dir = M(pos):get_int("tube_dir") + local dest_pos, dest_dir = Tube:get_connected_node_pos(pos, tube_dir) + local dest_node = minetest.get_node(dest_pos) + local on_push_item = minetest.registered_nodes[dest_node.name].on_push_item + --print("on_timer: dest_pos="..S(dest_pos).." dest_dir="..dest_dir) + local inv = minetest.get_inventory({type="node", pos=dest_pos}) + local stack = ItemStack("default:dirt") + + if on_push_item then + return on_push_item(dest_pos, dest_dir, stack) + elseif inv then + local leftover = inv:add_item("main", stack) + if leftover:get_count() == 0 then + return true + end + elseif dest_node.name == "air" then + minetest.add_item(dest_pos, stack) + return true + end + return false +end + +minetest.register_node("tubelib2:source", { + description = "Tubelib2 Item Source", + tiles = { + -- up, down, right, left, back, front + 'tubelib2_source.png', + 'tubelib2_source.png', + 'tubelib2_source.png', + 'tubelib2_source.png', + 'tubelib2_source.png', + 'tubelib2_conn.png', + }, + + after_place_node = function(pos, placer) + local tube_dir = ((minetest.dir_to_facedir(placer:get_look_dir()) + 2) % 4) + 1 + M(pos):set_int("tube_dir", tube_dir) + Tube:after_place_node(pos, {tube_dir}) + minetest.get_node_timer(pos):start(2) + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local tube_dir = tonumber(oldmetadata.fields.tube_dir or 0) + Tube:after_dig_node(pos, {tube_dir}) + end, + + on_timer = function(pos, elapsed) + if not push_item(pos) then + print("push_item error") + end + return true + end, + + paramtype2 = "facedir", -- important! + on_rotate = screwdriver.disallow, -- important! + paramtype = "light", + sunlight_propagates = true, + is_ground_content = false, + groups = {crumbly = 3, cracky = 3, snappy = 3}, +}) + +-- Can't be used for item transport (hyperloop test node) +minetest.register_node("tubelib2:junction", { + description = "Tubelib2 Junction", + tiles = { + 'tubelib2_conn.png', + }, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Position "..P2S(pos)) + Tube:after_place_node(pos) + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + Tube:after_dig_node(pos) + end, + + paramtype2 = "facedir", -- important! + on_rotate = screwdriver.disallow, -- important! + paramtype = "light", + sunlight_propagates = true, + is_ground_content = false, + groups = {crumbly = 3, cracky = 3, snappy = 3}, +}) + +------------------------------------------------------------------------------- +-- Teleporter +------------------------------------------------------------------------------- +local pairingList = {} + +local sFormspec = "size[7.5,3]".. + "field[0.5,1;7,1;channel;Enter channel string;]" .. + "button_exit[2,2;3,1;exit;Save]" + +local function store_connection(pos, peer_pos) + local meta = M(pos) + meta:set_string("peer_pos", P2S(peer_pos)) + meta:set_string("channel", "") + meta:set_string("formspec", "") + meta:set_string("infotext", "Connected to "..P2S(peer_pos)) +end + +local function prepare_pairing(pos) + local meta = M(pos) + meta:set_string("peer_pos", "") + meta:set_string("channel", "") + meta:set_string("formspec", sFormspec) + meta:set_string("infotext", "Pairing is missing") +end + +local function pairing(pos, channel) + if pairingList[channel] and not vector.equals(pos, pairingList[channel]) then + -- store peer position on both nodes + local peer_pos = pairingList[channel] + store_connection(pos, peer_pos) + store_connection(peer_pos, pos) + pairingList[channel] = nil + return true + else + pairingList[channel] = pos + prepare_pairing(pos) + return false + end +end + +local function stop_pairing(pos, oldmetadata) + -- unpair peer node + if oldmetadata and oldmetadata.fields then + if oldmetadata.fields.peer_pos then + local peer_pos = S2P(oldmetadata.fields.peer_pos) + prepare_pairing(peer_pos) + elseif oldmetadata.fields.channel then + pairingList[oldmetadata.fields.channel] = nil + end + end +end + +minetest.register_node("tubelib2:teleporter", { + description = "Tubelib2 Teleporter", + tiles = { + -- up, down, right, left, back, front + 'tubelib2_tele.png', + 'tubelib2_tele.png', + 'tubelib2_tele.png', + 'tubelib2_tele.png', + 'tubelib2_tele.png', + 'tubelib2_conn.png', + }, + + after_place_node = function(pos, placer) + -- the tube_dir calculation depends on the player look-dir and the hole side of the node + local tube_dir = ((minetest.dir_to_facedir(placer:get_look_dir()) + 2) % 4) + 1 + M(pos):set_int("tube_dir", tube_dir) + Tube:after_place_node(pos, {tube_dir}) + prepare_pairing(pos) + end, + + on_receive_fields = function(pos, formname, fields, player) + if fields.channel ~= nil then + pairing(pos, fields.channel) + end + end, + + on_push_item = function(pos, dir, item) + local tube_dir = M(pos):get_int("tube_dir") + if dir == tubelib2.Turn180Deg[tube_dir] then + local s = M(pos):get_string("peer_pos") + if s and s ~= "" then + push_item(S2P(s)) + return true + end + end + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + stop_pairing(pos, oldmetadata) + local tube_dir = tonumber(oldmetadata.fields.tube_dir or 0) + Tube:after_dig_node(pos, {tube_dir}) + end, + + paramtype2 = "facedir", -- important! + on_rotate = screwdriver.disallow, -- important! + paramtype = "light", + sunlight_propagates = true, + is_ground_content = false, + groups = {crumbly = 3, cracky = 3, snappy = 3}, +}) + +------------------------------------------------------------------------------- +-- Tool +------------------------------------------------------------------------------- +local function read_param2(pos, player) + local node = minetest.get_node(pos) + local dir1, dir2, num_tubes = Tube:decode_param2(pos, node.param2) + minetest.chat_send_player(player:get_player_name(), "[Tubelib2] pos="..P2S(pos)..", dir1="..dir1..", dir2="..dir2..", num_tubes="..num_tubes) +end + +local function remove_tube(itemstack, placer, pointed_thing) + if pointed_thing.type == "node" then + local pos = pointed_thing.under + if placer:get_player_control().sneak then + read_param2(pos, placer) + else + Tube:tool_remove_tube(pos, "default_break_glass") + end + else + local dir = (minetest.dir_to_facedir(placer:get_look_dir()) % 4) + 1 + minetest.chat_send_player(placer:get_player_name(), + "[Tool Help] dir="..dir.."\n".. + " left: remove node\n".. + " right: repair tube line\n") + end +end + +--local function walk(itemstack, placer, pointed_thing) +-- if pointed_thing.type == "node" then +-- local pos = pointed_thing.under +-- local dir = (minetest.dir_to_facedir(placer:get_look_dir()) % 4) + 1 +-- local t = minetest.get_us_time() +-- local pos1, outdir1, pos2, outdir2, cnt = Tube:walk(pos, dir) +-- t = minetest.get_us_time() - t +-- print("time", t) +-- if pos1 then +-- local s = "[Tubelib2] pos1="..P2S(pos1)..", outdir1="..outdir1..", pos2="..P2S(pos2)..", outdir2="..outdir2..", cnt="..cnt +-- minetest.chat_send_player(placer:get_player_name(), s) +-- end +-- else +-- local dir = (minetest.dir_to_facedir(placer:get_look_dir()) % 4) + 1 +-- minetest.chat_send_player(placer:get_player_name(), +-- "[Tool Help] dir="..dir.."\n".. +-- " left: remove node\n".. +-- " right: repair tube line\n") +-- end +--end + +local function repair_tube(itemstack, placer, pointed_thing) + if pointed_thing.type == "node" then + local pos = pointed_thing.under + local _, _, fpos1, fpos2, _, _, cnt1, cnt2 = Tube:tool_repair_tube(pos) + local length = cnt1 + cnt2 + + local s = "Tube from " .. P2S(fpos1) .. " to " .. P2S(fpos2) .. ". Lenght = " .. length + minetest.chat_send_player(placer:get_player_name(), s) + + if length > Tube.max_tube_length then + local s = string.char(0x1b) .. "(c@#ff0000)" .. "Tube length error!" + minetest.chat_send_player(placer:get_player_name(), s) + end + + minetest.sound_play("carts_cart_new", { + pos = pos, + gain = 1, + max_hear_distance = 5}) + else + local dir = (minetest.dir_to_facedir(placer:get_look_dir()) % 4) + 1 + minetest.chat_send_player(placer:get_player_name(), + "[Tool Help] dir="..dir.."\n".. + " left: remove node\n".. + " right: repair tube line\n") + end +end + +-- Tool for tube workers to crack a protected tube line +minetest.register_node("tubelib2:tool", { + description = "Tubelib2 Tool", + inventory_image = "tubelib2_tool.png", + wield_image = "tubelib2_tool.png", + use_texture_alpha = true, + groups = {cracky=1, book=1}, + on_use = remove_tube, + on_place = repair_tube, + node_placement_prediction = "", + stack_max = 1, +}) +