forked from mtcontrib/pipeworks
		
	Compare commits
	
		
			33 Commits
		
	
	
		
			nalc-1.2.0
			...
			ee03959a65
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ee03959a65 | |||
|  | 8322f256c5 | ||
|  | ba7eb19317 | ||
| 4dd30df37a | |||
|  | 065c953eba | ||
| 946da11206 | |||
|  | 9a63d17e4a | ||
|  | d814357ddf | ||
|  | c2fe5fe956 | ||
|  | 9dbaa5f4f6 | ||
| 77c8026400 | |||
|  | d93396600f | ||
|  | c966a8a57d | ||
| 70b521c721 | |||
|  | 61b061f669 | ||
|  | fe91d5eb46 | ||
|  | eb1064ca6d | ||
|  | f54e25ec52 | ||
| 6d795b7d34 | |||
|  | dedb0dd54e | ||
|  | 477a024034 | ||
| 92249b7941 | |||
|  | 34cb0e7682 | ||
|  | 505fc7cc49 | ||
| e04fb691ad | |||
|  | cb58a646cf | ||
|  | e3135c53f0 | ||
| ac80224371 | |||
|  | 366d57f4da | ||
|  | c79e68a80c | ||
|  | 9338c109a6 | ||
|  | 63bee98948 | ||
|  | ee6c9991b9 | 
| @@ -392,6 +392,27 @@ minetest.register_node("pipeworks:autocrafter", { | |||||||
| 						end | 						end | ||||||
| 					end | 					end | ||||||
| 					after_recipe_change(pos,inv) | 					after_recipe_change(pos,inv) | ||||||
|  | 				elseif msg == "get_recipe" then | ||||||
|  | 					local meta = minetest.get_meta(pos) | ||||||
|  | 					local inv = meta:get_inventory() | ||||||
|  | 					local recipe = {} | ||||||
|  | 					for y=0,2,1 do | ||||||
|  | 						local row = {} | ||||||
|  | 						for x=1,3,1 do | ||||||
|  | 							local slot = y*3+x | ||||||
|  | 							table.insert(row, inv:get_stack("recipe",slot):get_name()) | ||||||
|  | 						end | ||||||
|  | 						table.insert(recipe, row) | ||||||
|  | 					end | ||||||
|  | 					local setchan = meta:get_string("channel") | ||||||
|  | 					local output = inv:get_stack("output", 1) | ||||||
|  | 					digiline:receptor_send(pos, digiline.rules.default, setchan, { | ||||||
|  | 						recipe = recipe, | ||||||
|  | 						result = { | ||||||
|  | 							name = output:get_name(), | ||||||
|  | 							count = output:get_count(), | ||||||
|  | 						} | ||||||
|  | 					  }) | ||||||
| 				elseif msg == "off" then | 				elseif msg == "off" then | ||||||
| 					update_meta(meta, false) | 					update_meta(meta, false) | ||||||
| 					minetest.get_node_timer(pos):stop() | 					minetest.get_node_timer(pos):stop() | ||||||
|   | |||||||
| @@ -53,27 +53,28 @@ local function tube_autoroute(pos) | |||||||
| 	} | 	} | ||||||
| 	-- xm = 1, xp = 2, ym = 3, yp = 4, zm = 5, zp = 6 | 	-- xm = 1, xp = 2, ym = 3, yp = 4, zm = 5, zp = 6 | ||||||
|  |  | ||||||
| 	local positions = {} | 	local adjlist = {} -- this will be used in item_transport | ||||||
| 	local nodes = {} |  | ||||||
| 	for i, adj in ipairs(adjustments) do | 	for i, adj in ipairs(adjustments) do | ||||||
| 		positions[i] = vector.add(pos, adj) | 		local position = vector.add(pos, adj) | ||||||
| 		nodes[i] = minetest.get_node(positions[i]) | 		local node = minetest.get_node(position) | ||||||
| 	end |  | ||||||
|  |  | ||||||
| 	for i, node in ipairs(nodes) do |  | ||||||
| 		local idef = minetest.registered_nodes[node.name] | 		local idef = minetest.registered_nodes[node.name] | ||||||
| 		-- handle the tubes themselves | 		-- handle the tubes themselves | ||||||
| 		if is_tube(node.name) then | 		if is_tube(node.name) then | ||||||
| 			active[i] = 1 | 			active[i] = 1 | ||||||
|  | 			table.insert(adjlist, adj) | ||||||
| 		-- handle new style connectors | 		-- handle new style connectors | ||||||
| 		elseif idef and idef.tube and idef.tube.connect_sides then | 		elseif idef and idef.tube and idef.tube.connect_sides then | ||||||
| 			local dir = adjustments[i] | 			if idef.tube.connect_sides[nodeside(node, vector.multiply(adj, -1))] then | ||||||
| 			if idef.tube.connect_sides[nodeside(node, vector.multiply(dir, -1))] then |  | ||||||
| 				active[i] = 1 | 				active[i] = 1 | ||||||
|  | 				table.insert(adjlist, adj) | ||||||
| 			end | 			end | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
|  | 	minetest.get_meta(pos):set_string("adjlist", minetest.serialize(adjlist)) | ||||||
|  |  | ||||||
| 	-- all sides checked, now figure which tube to use. | 	-- all sides checked, now figure which tube to use. | ||||||
|  |  | ||||||
| 	local nodedef = minetest.registered_nodes[nctr.name] | 	local nodedef = minetest.registered_nodes[nctr.name] | ||||||
|   | |||||||
| @@ -86,10 +86,9 @@ function pipeworks.table_contains(tbl, element) | |||||||
| end | end | ||||||
|  |  | ||||||
| function pipeworks.table_extend(tbl, tbl2) | function pipeworks.table_extend(tbl, tbl2) | ||||||
| 	local index = #tbl + 1 | 	local oldlength = #tbl | ||||||
| 	for _, elt in ipairs(tbl2) do | 	for i = 1,#tbl2 do | ||||||
| 		tbl[index] = elt | 		tbl[oldlength + i] = tbl2[i] | ||||||
| 		index = index + 1 |  | ||||||
| 	end | 	end | ||||||
| end | end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -77,10 +77,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) | |||||||
| 					pipeworks.after_place(pos) | 					pipeworks.after_place(pos) | ||||||
| 				end) | 				end) | ||||||
| 				minetest.sound_play(sound, {gain = 0.3, pos = pos, max_hear_distance = 10}) | 				minetest.sound_play(sound, {gain = 0.3, pos = pos, max_hear_distance = 10}) | ||||||
| 			end | 			elseif pipeworks.may_configure(pos, player) then | ||||||
|  | 				-- Pipeworks Switch | ||||||
| 			-- Pipeworks Switch |  | ||||||
| 			if pipeworks.may_configure(pos, player) and not fields.quit then |  | ||||||
| 				fs_helpers.on_receive_fields(pos, fields) | 				fs_helpers.on_receive_fields(pos, fields) | ||||||
| 				minetest.show_formspec(player:get_player_name(), "pipeworks:chest_formspec", get_chest_formspec(pos)) | 				minetest.show_formspec(player:get_player_name(), "pipeworks:chest_formspec", get_chest_formspec(pos)) | ||||||
| 			end | 			end | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								init.lua
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								init.lua
									
									
									
									
									
								
							| @@ -71,8 +71,8 @@ function pipeworks.may_configure(pos, player) | |||||||
| 	local meta = minetest.get_meta(pos) | 	local meta = minetest.get_meta(pos) | ||||||
| 	local owner = meta:get_string("owner") | 	local owner = meta:get_string("owner") | ||||||
|  |  | ||||||
| 	if owner ~= "" then -- wielders and filters | 	if owner ~= "" and owner == name then -- wielders and filters | ||||||
| 		return owner == name | 		return true | ||||||
| 	end | 	end | ||||||
| 	return not minetest.is_protected(pos, name) | 	return not minetest.is_protected(pos, name) | ||||||
| end | end | ||||||
| @@ -150,4 +150,4 @@ end | |||||||
|  |  | ||||||
| minetest.register_alias("pipeworks:pipe", "pipeworks:pipe_110000_empty") | minetest.register_alias("pipeworks:pipe", "pipeworks:pipe_110000_empty") | ||||||
|  |  | ||||||
| print("Pipeworks loaded!") | minetest.log("info", "Pipeworks loaded!") | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ end | |||||||
| -- both optional w/ sensible defaults and fallback to normal allow_* function | -- both optional w/ sensible defaults and fallback to normal allow_* function | ||||||
| -- XXX: possibly change insert_object to insert_item | -- XXX: possibly change insert_object to insert_item | ||||||
|  |  | ||||||
| local adjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}} | local default_adjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}} | ||||||
|  |  | ||||||
| function pipeworks.notvel(tbl, vel) | function pipeworks.notvel(tbl, vel) | ||||||
| 	local tbl2={} | 	local tbl2={} | ||||||
| @@ -80,6 +80,9 @@ local function go_next_compat(pos, cnode, cmeta, cycledir, vel, stack, owner) | |||||||
| 	if minetest.registered_nodes[cnode.name] and minetest.registered_nodes[cnode.name].tube and minetest.registered_nodes[cnode.name].tube.can_go then | 	if minetest.registered_nodes[cnode.name] and minetest.registered_nodes[cnode.name].tube and minetest.registered_nodes[cnode.name].tube.can_go then | ||||||
| 		can_go = minetest.registered_nodes[cnode.name].tube.can_go(pos, cnode, vel, stack) | 		can_go = minetest.registered_nodes[cnode.name].tube.can_go(pos, cnode, vel, stack) | ||||||
| 	else | 	else | ||||||
|  | 		local adjlist_string = minetest.get_meta(pos):get_string("adjlist") | ||||||
|  | 		local adjlist = minetest.deserialize(adjlist_string) or default_adjlist -- backward compat: if not found, use old behavior: all directions | ||||||
|  |  | ||||||
| 		can_go = pipeworks.notvel(adjlist, vel) | 		can_go = pipeworks.notvel(adjlist, vel) | ||||||
| 	end | 	end | ||||||
| 	-- can_go() is expected to return an array-like table of candidate offsets. | 	-- can_go() is expected to return an array-like table of candidate offsets. | ||||||
|   | |||||||
							
								
								
									
										110
									
								
								locale/pipeworks.zh_CN.tr
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								locale/pipeworks.zh_CN.tr
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | # textdomain: pipeworks | ||||||
|  | # License: CC-by-SA 4.0 | ||||||
|  | # Author: pevernow <3450354617@qq.com> | ||||||
|  |  | ||||||
|  | ## digilines interfacing | ||||||
|  | Channel=频道 | ||||||
|  |  | ||||||
|  | ## init | ||||||
|  | Allow splitting incoming stacks from tubes=允许从管道中拆分传入堆栈 | ||||||
|  |  | ||||||
|  | ## autocrafter | ||||||
|  | Unknown item=通道 | ||||||
|  | unconfigured Autocrafter: unknown recipe=未配置的自动工作台: 未知配方 | ||||||
|  | unconfigured Autocrafter=未配置的自动工作台 | ||||||
|  | '@1' Autocrafter (@2)=自动工作台 '@1' (@2) | ||||||
|  | Save=保存 | ||||||
|  | paused '@1' Autocrafter=暂停的自动工作台 | ||||||
|  | Autocrafter=自动工作台 | ||||||
|  |  | ||||||
|  | ## compat-furnaces | ||||||
|  | Allow splitting incoming material (not fuel) stacks from tubes=允许从管子中分离进来的材料(不是燃料)堆 | ||||||
|  |  | ||||||
|  | ## decorative tubes | ||||||
|  | Airtight steelblock embedded tube=密封嵌入式铁块管道 | ||||||
|  | Airtight panel embedded tube=密封嵌入式片状管道 | ||||||
|  |  | ||||||
|  | ## devices | ||||||
|  | Pump/Intake Module=泵/进气模块 | ||||||
|  | Valve=阀门 | ||||||
|  | Decorative grating=Decorative grating | ||||||
|  | Spigot outlet=龙头 | ||||||
|  | Airtight Pipe entry/exit=密闭管进/出 | ||||||
|  | Flow Sensor=流量传感器 | ||||||
|  | Flow sensor (on)=流量传感器(上) | ||||||
|  | empty=空的 | ||||||
|  | @1% full=满的@1 % | ||||||
|  | Expansion Tank (@1)=扩展水箱 (@1) | ||||||
|  | Fluid Storage Tank (@1)=储液罐 (@1) | ||||||
|  | Fountainhead=源泉 | ||||||
|  | Straight-only Pipe=直管 | ||||||
|  | ## filter-injector | ||||||
|  | (slot #@1 next)=(下一个插槽 : #@1) | ||||||
|  | @1 Filter-Injector=@1取物器 | ||||||
|  | Sequence slots by Priority=优先顺序排列 | ||||||
|  | Sequence slots Randomly=随机排列时隙 | ||||||
|  | Sequence slots by Rotation=旋转顺序槽 | ||||||
|  | Exact match - off=完全匹配-关闭 | ||||||
|  | Exact match - on=完全匹配-开启 | ||||||
|  | Prefer item types:=偏好物品类型 : | ||||||
|  | Itemwise=逐项 | ||||||
|  | Stackwise=堆叠方式 | ||||||
|  | Digiline=Digiline | ||||||
|  |  | ||||||
|  | ## legacy | ||||||
|  | Auto-Tap=自动轴阀 | ||||||
|  |  | ||||||
|  | ## pipes | ||||||
|  | Pipe Segment=管道 | ||||||
|  | Pipe Segment (legacy)=管道(旧版) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## routing tubes | ||||||
|  | Pneumatic tube segment=普通管道 | ||||||
|  | Broken Tube=断管 | ||||||
|  | High Priority Tube Segment=高优先级管段 | ||||||
|  | Accelerating Pneumatic Tube Segment=加速管道 | ||||||
|  | Crossing Pneumatic Tube Segment=定向管道 | ||||||
|  | One way tube=单向管 | ||||||
|  |  | ||||||
|  | ## signal tubes | ||||||
|  | Detecting Pneumatic Tube Segment on=检测管道(运行中) | ||||||
|  | Detecting Pneumatic Tube Segment=检测管道 | ||||||
|  | Digiline Detecting Pneumatic Tube Segment=Digiline检测管道 | ||||||
|  | Digiline Detecting Tube=Digiline检测管 | ||||||
|  | Conducting Pneumatic Tube Segment=传导管道 | ||||||
|  | Conducting Pneumatic Tube Segment on=传导管道(运行中) | ||||||
|  | Digiline Conducting Pneumatic Tube Segment=Digiline传导式气动管道 | ||||||
|  | Mesecon and Digiline Conducting Pneumatic Tube Segment=Mesecon和Digiline传导管道 | ||||||
|  | Mesecon and Digiline Conducting Pneumatic Tube Segment on=Mesecon和Digiline传导管道(运行中) | ||||||
|  |  | ||||||
|  | ## sorting tubes | ||||||
|  | Sorting Pneumatic Tube Segment=分类管道 | ||||||
|  | Sorting pneumatic tube=分类管道 | ||||||
|  |  | ||||||
|  | ## teleport tube | ||||||
|  | Receive=接收 | ||||||
|  | channels are public by default=频道默认为公开 | ||||||
|  | use <player>:<channel> for fully private channels=将<player>:<channel>用于完全私人的频道 | ||||||
|  | use <player>;<channel> for private receivers=使用<player>;<channel>作为私人接收器 | ||||||
|  | Teleporting Pneumatic Tube Segment=传送管道 | ||||||
|  | unconfigured Teleportation Tube=未配置的传送管道 | ||||||
|  | Sorry, channel '@1' is reserved for exclusive use by @2=抱歉,频道‘@1’保留供‘@2’专用 | ||||||
|  | Sorry, receiving from channel '@1' is reserved for @2=抱歉,从频道'@1'接收的内容已保留给'@2' | ||||||
|  | Teleportation Tube @1 on '@2'=传送管'@1'在'@2'上 | ||||||
|  |  | ||||||
|  | ## trashcan | ||||||
|  | Trash Can=垃圾箱 | ||||||
|  |  | ||||||
|  | ## tube registration | ||||||
|  | Pneumatic tube segment (legacy)=普通管道(旧式) | ||||||
|  |  | ||||||
|  | ## vacuum tubes | ||||||
|  | Vacuuming Pneumatic Tube Segment=拾取管道 | ||||||
|  | Adjustable Vacuuming Pneumatic Tube Segment=高级拾取管道 | ||||||
|  | Adjustable Vacuuming Pneumatic Tube Segment (@1m)=高级拾取管道(@1m) | ||||||
|  |  | ||||||
|  | ## wielder | ||||||
|  | Node Breaker=方块破坏器 | ||||||
|  | Deployer=放置器 | ||||||
|  | Dispenser=投掷器 | ||||||
							
								
								
									
										348
									
								
								lua_tube.lua
									
									
									
									
									
								
							
							
						
						
									
										348
									
								
								lua_tube.lua
									
									
									
									
									
								
							| @@ -13,8 +13,10 @@ | |||||||
| -- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port | -- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port | ||||||
| -- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states | -- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states | ||||||
| -- set_port_states(pos, ports): Applies new port states to a Luacontroller at pos | -- set_port_states(pos, ports): Applies new port states to a Luacontroller at pos | ||||||
| -- run(pos): runs the code in the controller at pos | -- run_inner(pos, code, event): runs code on the controller at pos and event | ||||||
| -- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error messages | -- reset_formspec(pos, code, errmsg): installs new code and prints error messages, without resetting LCID | ||||||
|  | -- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error message | ||||||
|  | -- run(pos, event): a wrapper for run_inner which gets code & handles errors via reset_meta | ||||||
| -- resetn(pos): performs a hardware reset, turns off all ports | -- resetn(pos): performs a hardware reset, turns off all ports | ||||||
| -- | -- | ||||||
| -- The Sandbox | -- The Sandbox | ||||||
| @@ -77,7 +79,7 @@ local function update_real_port_states(pos, rule_name, new_state) | |||||||
| 	if rule_name.x == nil then | 	if rule_name.x == nil then | ||||||
| 		for _, rname in ipairs(rule_name) do | 		for _, rname in ipairs(rule_name) do | ||||||
| 			local port = pos_to_side[rname.x + (2 * rname.y) + (3 * rname.z) + 4] | 			local port = pos_to_side[rname.x + (2 * rname.y) + (3 * rname.z) + 4] | ||||||
| 			L[port] = (newstate == "on") and 1 or 0 | 			L[port] = (new_state == "on") and 1 or 0 | ||||||
| 		end | 		end | ||||||
| 	else | 	else | ||||||
| 		local port = pos_to_side[rule_name.x + (2 * rule_name.y) + (3 * rule_name.z) + 4] | 		local port = pos_to_side[rule_name.x + (2 * rule_name.y) + (3 * rule_name.z) + 4] | ||||||
| @@ -168,7 +170,7 @@ local function set_port_states(pos, ports) | |||||||
| 		-- Solution / Workaround: | 		-- Solution / Workaround: | ||||||
| 		-- Remember which output was turned off and ignore next "off" event. | 		-- Remember which output was turned off and ignore next "off" event. | ||||||
| 		local meta = minetest.get_meta(pos) | 		local meta = minetest.get_meta(pos) | ||||||
| 		local ign = minetest.deserialize(meta:get_string("ignore_offevents")) or {} | 		local ign = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {} | ||||||
| 		if ports.red    and not vports.red    and not mesecon.is_powered(pos, rules.red)    then ign.red    = true end | 		if ports.red    and not vports.red    and not mesecon.is_powered(pos, rules.red)    then ign.red    = true end | ||||||
| 		if ports.blue   and not vports.blue   and not mesecon.is_powered(pos, rules.blue)   then ign.blue   = true end | 		if ports.blue   and not vports.blue   and not mesecon.is_powered(pos, rules.blue)   then ign.blue   = true end | ||||||
| 		if ports.yellow and not vports.yellow and not mesecon.is_powered(pos, rules.yellow) then ign.yellow = true end | 		if ports.yellow and not vports.yellow and not mesecon.is_powered(pos, rules.yellow) then ign.yellow = true end | ||||||
| @@ -214,7 +216,7 @@ end | |||||||
|  |  | ||||||
| local function ignore_event(event, meta) | local function ignore_event(event, meta) | ||||||
| 	if event.type ~= "off" then return false end | 	if event.type ~= "off" then return false end | ||||||
| 	local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents")) or {} | 	local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {} | ||||||
| 	if ignore_offevents[event.pin.name] then | 	if ignore_offevents[event.pin.name] then | ||||||
| 		ignore_offevents[event.pin.name] = nil | 		ignore_offevents[event.pin.name] = nil | ||||||
| 		meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents)) | 		meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents)) | ||||||
| @@ -227,7 +229,11 @@ end | |||||||
| ------------------------- | ------------------------- | ||||||
|  |  | ||||||
| local function safe_print(param) | local function safe_print(param) | ||||||
|  | 	local string_meta = getmetatable("") | ||||||
|  | 	local sandbox = string_meta.__index | ||||||
|  | 	string_meta.__index = string -- Leave string sandbox temporarily | ||||||
| 	print(dump(param)) | 	print(dump(param)) | ||||||
|  | 	string_meta.__index = sandbox -- Restore string sandbox | ||||||
| end | end | ||||||
|  |  | ||||||
| local function safe_date() | local function safe_date() | ||||||
| @@ -267,6 +273,7 @@ local function remove_functions(x) | |||||||
| 	local seen = {} | 	local seen = {} | ||||||
|  |  | ||||||
| 	local function rfuncs(x) | 	local function rfuncs(x) | ||||||
|  | 		if x == nil then return end | ||||||
| 		if seen[x] then return end | 		if seen[x] then return end | ||||||
| 		seen[x] = true | 		seen[x] = true | ||||||
| 		if type(x) ~= "table" then return end | 		if type(x) ~= "table" then return end | ||||||
| @@ -290,49 +297,174 @@ local function remove_functions(x) | |||||||
| 	return x | 	return x | ||||||
| end | end | ||||||
|  |  | ||||||
| local function get_interrupt(pos) | -- The setting affects API so is not intended to be changeable at runtime | ||||||
| 	-- iid = interrupt id | local get_interrupt | ||||||
| 	local function interrupt(time, iid) | if mesecon.setting("luacontroller_lightweight_interrupts", false) then | ||||||
| 		if type(time) ~= "number" then return end | 	-- use node timer | ||||||
| 		local luac_id = minetest.get_meta(pos):get_int("luac_id") | 	get_interrupt = function(pos, itbl, send_warning) | ||||||
| 		mesecon.queue:add_action(pos, "pipeworks:lc_tube_interrupt", {luac_id, iid}, time, iid, 1) | 		return (function(time, iid) | ||||||
|  | 			if type(time) ~= "number" then error("Delay must be a number") end | ||||||
|  | 			if iid ~= nil then send_warning("Interrupt IDs are disabled on this server") end | ||||||
|  | 			table.insert(itbl, function() minetest.get_node_timer(pos):start(time) end) | ||||||
|  | 		end) | ||||||
|  | 	end | ||||||
|  | else | ||||||
|  | 	-- use global action queue | ||||||
|  | 	-- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards | ||||||
|  | 	get_interrupt = function(pos, itbl, send_warning) | ||||||
|  | 		-- iid = interrupt id | ||||||
|  | 		local function interrupt(time, iid) | ||||||
|  | 			-- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y | ||||||
|  | 			-- Hence the values get moved out. Should take less time than original, so totally compatible | ||||||
|  | 			if type(time) ~= "number" then error("Delay must be a number") end | ||||||
|  | 			table.insert(itbl, function () | ||||||
|  | 				-- Outside string metatable sandbox, can safely run this now | ||||||
|  | 				local luac_id = minetest.get_meta(pos):get_int("luac_id") | ||||||
|  | 				-- Check if IID is dodgy, so you can't use interrupts to store an infinite amount of data. | ||||||
|  | 				-- Note that this is safe from alter-after-free because this code gets run after the sandbox has ended. | ||||||
|  | 				-- This runs outside of the timer and *shouldn't* harm perf. unless dodgy data is being sent in the first place | ||||||
|  | 				iid = remove_functions(iid) | ||||||
|  | 				local msg_ser = minetest.serialize(iid) | ||||||
|  | 				if #msg_ser <= mesecon.setting("luacontroller_interruptid_maxlen", 256) then | ||||||
|  | 					mesecon.queue:add_action(pos, "pipeworks:lc_tube_interrupt", {luac_id, iid}, time, iid, 1) | ||||||
|  | 				else | ||||||
|  | 					send_warning("An interrupt ID was too large!") | ||||||
|  | 				end | ||||||
|  | 			end) | ||||||
|  | 		end | ||||||
|  | 		return interrupt | ||||||
|  | 	end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | -- Given a message object passed to digiline_send, clean it up into a form | ||||||
|  | -- which is safe to transmit over the network and compute its "cost" (a very | ||||||
|  | -- rough estimate of its memory usage). | ||||||
|  | -- | ||||||
|  | -- The cleaning comprises the following: | ||||||
|  | -- 1. Functions (and userdata, though user scripts ought not to get hold of | ||||||
|  | --    those in the first place) are removed, because they break the model of | ||||||
|  | --    Digilines as a network that carries basic data, and they could exfiltrate | ||||||
|  | --    references to mutable objects from one Luacontroller to another, allowing | ||||||
|  | --    inappropriate high-bandwidth, no-wires communication. | ||||||
|  | -- 2. Tables are duplicated because, being mutable, they could otherwise be | ||||||
|  | --    modified after the send is complete in order to change what data arrives | ||||||
|  | --    at the recipient, perhaps in violation of the previous cleaning rule or | ||||||
|  | --    in violation of the message size limit. | ||||||
|  | -- | ||||||
|  | -- The cost indication is only approximate; it’s not a perfect measurement of | ||||||
|  | -- the number of bytes of memory used by the message object. | ||||||
|  | -- | ||||||
|  | -- Parameters: | ||||||
|  | -- msg -- the message to clean | ||||||
|  | -- back_references -- for internal use only; do not provide | ||||||
|  | -- | ||||||
|  | -- Returns: | ||||||
|  | -- 1. The cleaned object. | ||||||
|  | -- 2. The approximate cost of the object. | ||||||
|  | local function clean_and_weigh_digiline_message(msg, back_references) | ||||||
|  | 	local t = type(msg) | ||||||
|  | 	if t == "string" then | ||||||
|  | 		-- Strings are immutable so can be passed by reference, and cost their | ||||||
|  | 		-- length plus the size of the Lua object header (24 bytes on a 64-bit | ||||||
|  | 		-- platform) plus one byte for the NUL terminator. | ||||||
|  | 		return msg, #msg + 25 | ||||||
|  | 	elseif t == "number" then | ||||||
|  | 		-- Numbers are passed by value so need not be touched, and cost 8 bytes | ||||||
|  | 		-- as all numbers in Lua are doubles. | ||||||
|  | 		return msg, 8 | ||||||
|  | 	elseif t == "boolean" then | ||||||
|  | 		-- Booleans are passed by value so need not be touched, and cost 1 | ||||||
|  | 		-- byte. | ||||||
|  | 		return msg, 1 | ||||||
|  | 	elseif t == "table" then | ||||||
|  | 		-- Tables are duplicated. Check if this table has been seen before | ||||||
|  | 		-- (self-referential or shared table); if so, reuse the cleaned value | ||||||
|  | 		-- of the previous occurrence, maintaining table topology and avoiding | ||||||
|  | 		-- infinite recursion, and charge zero bytes for this as the object has | ||||||
|  | 		-- already been counted. | ||||||
|  | 		back_references = back_references or {} | ||||||
|  | 		local bref = back_references[msg] | ||||||
|  | 		if bref then | ||||||
|  | 			return bref, 0 | ||||||
|  | 		end | ||||||
|  | 		-- Construct a new table by cleaning all the keys and values and adding | ||||||
|  | 		-- up their costs, plus 8 bytes as a rough estimate of table overhead. | ||||||
|  | 		local cost = 8 | ||||||
|  | 		local ret = {} | ||||||
|  | 		back_references[msg] = ret | ||||||
|  | 		for k, v in pairs(msg) do | ||||||
|  | 			local k_cost, v_cost | ||||||
|  | 			k, k_cost = clean_and_weigh_digiline_message(k, back_references) | ||||||
|  | 			v, v_cost = clean_and_weigh_digiline_message(v, back_references) | ||||||
|  | 			if k ~= nil and v ~= nil then | ||||||
|  | 				-- Only include an element if its key and value are of legal | ||||||
|  | 				-- types. | ||||||
|  | 				ret[k] = v | ||||||
|  | 			end | ||||||
|  | 			-- If we only counted the cost of a table element when we actually | ||||||
|  | 			-- used it, we would be vulnerable to the following attack: | ||||||
|  | 			-- 1. Construct a huge table (too large to pass the cost limit). | ||||||
|  | 			-- 2. Insert it somewhere in a table, with a function as a key. | ||||||
|  | 			-- 3. Insert it somewhere in another table, with a number as a key. | ||||||
|  | 			-- 4. The first occurrence doesn’t pay the cost because functions | ||||||
|  | 			--    are stripped and therefore the element is dropped. | ||||||
|  | 			-- 5. The second occurrence doesn’t pay the cost because it’s in | ||||||
|  | 			--    back_references. | ||||||
|  | 			-- By counting the costs regardless of whether the objects will be | ||||||
|  | 			-- included, we avoid this attack; it may overestimate the cost of | ||||||
|  | 			-- some messages, but only those that won’t be delivered intact | ||||||
|  | 			-- anyway because they contain illegal object types. | ||||||
|  | 			cost = cost + k_cost + v_cost | ||||||
|  | 		end | ||||||
|  | 		return ret, cost | ||||||
|  | 	else | ||||||
|  | 		return nil, 0 | ||||||
| 	end | 	end | ||||||
| 	return interrupt |  | ||||||
| end | end | ||||||
|  |  | ||||||
|  |  | ||||||
| local function get_digiline_send(pos) | -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards | ||||||
| 	if not digiline then return end | local function get_digiline_send(pos, itbl, send_warning) | ||||||
|  | 	if not minetest.global_exists("digilines") then return end | ||||||
|  | 	local chan_maxlen = mesecon.setting("luacontroller_digiline_channel_maxlen", 256) | ||||||
|  | 	local maxlen = mesecon.setting("luacontroller_digiline_maxlen", 50000) | ||||||
| 	return function(channel, msg) | 	return function(channel, msg) | ||||||
|  | 		-- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y | ||||||
|  | 		--        or via anything that could. | ||||||
| 		-- Make sure channel is string, number or boolean | 		-- Make sure channel is string, number or boolean | ||||||
| 		if (type(channel) ~= "string" and type(channel) ~= "number" and type(channel) ~= "boolean") then | 		if type(channel) == "string" then | ||||||
|  | 			if #channel > chan_maxlen then | ||||||
|  | 				send_warning("Channel string too long.") | ||||||
|  | 				return false | ||||||
|  | 			end | ||||||
|  | 		elseif (type(channel) ~= "string" and type(channel) ~= "number" and type(channel) ~= "boolean") then | ||||||
|  | 			send_warning("Channel must be string, number or boolean.") | ||||||
| 			return false | 			return false | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		-- It is technically possible to send functions over the wire since | 		local msg_cost | ||||||
| 		-- the high performance impact of stripping those from the data has | 		msg, msg_cost = clean_and_weigh_digiline_message(msg) | ||||||
| 		-- been decided to not be worth the added realism. | 		if msg == nil or msg_cost > maxlen then | ||||||
| 		-- Make sure serialized version of the data is not insanely long to | 			send_warning("Message was too complex, or contained invalid data.") | ||||||
| 		-- prevent DoS-like attacks |  | ||||||
| 		local msg_ser = minetest.serialize(msg) |  | ||||||
| 		if #msg_ser > mesecon.setting("luacontroller_digiline_maxlen", 50000) then |  | ||||||
| 			return false | 			return false | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		minetest.after(0, function() | 		table.insert(itbl, function () | ||||||
| 			digilines.receptor_send(pos, digiline_rules_luatube, channel, msg) | 			-- Runs outside of string metatable sandbox | ||||||
|  | 			local luac_id = minetest.get_meta(pos):get_int("luac_id") | ||||||
|  | 			mesecon.queue:add_action(pos, "pipeworks:lt_digiline_relay", {channel, luac_id, msg}) | ||||||
| 		end) | 		end) | ||||||
| 		return true | 		return true | ||||||
| 	end | 	end | ||||||
| end | end | ||||||
|  |  | ||||||
|  |  | ||||||
| local safe_globals = { | local safe_globals = { | ||||||
|  | 	-- Don't add pcall/xpcall unless willing to deal with the consequences (unless very careful, incredibly likely to allow killing server indirectly) | ||||||
| 	"assert", "error", "ipairs", "next", "pairs", "select", | 	"assert", "error", "ipairs", "next", "pairs", "select", | ||||||
| 	"tonumber", "tostring", "type", "unpack", "_VERSION" | 	"tonumber", "tostring", "type", "unpack", "_VERSION" | ||||||
| } | } | ||||||
|  |  | ||||||
| local function create_environment(pos, mem, event) | local function create_environment(pos, mem, event, itbl, send_warning) | ||||||
| 	-- Make sure the tube hasn't broken. | 	-- Make sure the tube hasn't broken. | ||||||
| 	local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates | 	local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates | ||||||
| 	if not vports then return {} end | 	if not vports then return {} end | ||||||
| @@ -352,8 +484,8 @@ local function create_environment(pos, mem, event) | |||||||
| 		heat = mesecon.get_heat(pos), | 		heat = mesecon.get_heat(pos), | ||||||
| 		heat_max = mesecon.setting("overheat_max", 20), | 		heat_max = mesecon.setting("overheat_max", 20), | ||||||
| 		print = safe_print, | 		print = safe_print, | ||||||
| 		interrupt = get_interrupt(pos), | 		interrupt = get_interrupt(pos, itbl, send_warning), | ||||||
| 		digiline_send = get_digiline_send(pos), | 		digiline_send = get_digiline_send(pos, itbl, send_warning), | ||||||
| 		string = { | 		string = { | ||||||
| 			byte = string.byte, | 			byte = string.byte, | ||||||
| 			char = string.char, | 			char = string.char, | ||||||
| @@ -441,10 +573,11 @@ local function create_sandbox(code, env) | |||||||
| 		jit.off(f, true) | 		jit.off(f, true) | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
|  | 	local maxevents = mesecon.setting("luacontroller_maxevents", 10000) | ||||||
| 	return function(...) | 	return function(...) | ||||||
|  | 		-- NOTE: This runs within string metatable sandbox, so the setting's been moved out for safety | ||||||
| 		-- Use instruction counter to stop execution | 		-- Use instruction counter to stop execution | ||||||
| 		-- after luacontroller_maxevents | 		-- after luacontroller_maxevents | ||||||
| 		local maxevents = mesecon.setting("luacontroller_maxevents", 10000) |  | ||||||
| 		debug.sethook(timeout, "", maxevents) | 		debug.sethook(timeout, "", maxevents) | ||||||
| 		local ok, ret = pcall(f, ...) | 		local ok, ret = pcall(f, ...) | ||||||
| 		debug.sethook()  -- Clear hook | 		debug.sethook()  -- Clear hook | ||||||
| @@ -455,7 +588,7 @@ end | |||||||
|  |  | ||||||
|  |  | ||||||
| local function load_memory(meta) | local function load_memory(meta) | ||||||
| 	return minetest.deserialize(meta:get_string("lc_memory")) or {} | 	return minetest.deserialize(meta:get_string("lc_memory"), true) or {} | ||||||
| end | end | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -465,6 +598,7 @@ local function save_memory(pos, meta, mem) | |||||||
|  |  | ||||||
| 	if (#memstring <= memsize_max) then | 	if (#memstring <= memsize_max) then | ||||||
| 		meta:set_string("lc_memory", memstring) | 		meta:set_string("lc_memory", memstring) | ||||||
|  | 		meta:mark_as_private("lc_memory") | ||||||
| 	else | 	else | ||||||
| 		print("Error: lua_tube memory overflow. "..memsize_max.." bytes available, " | 		print("Error: lua_tube memory overflow. "..memsize_max.." bytes available, " | ||||||
| 				..#memstring.." required. Controller overheats.") | 				..#memstring.." required. Controller overheats.") | ||||||
| @@ -472,26 +606,42 @@ local function save_memory(pos, meta, mem) | |||||||
| 	end | 	end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | -- Returns success (boolean), errmsg (string), retval(any, return value of the user supplied code) | ||||||
| local function run(pos, event) | -- run (as opposed to run_inner) is responsible for setting up meta according to this output | ||||||
|  | local function run_inner(pos, code, event) | ||||||
| 	local meta = minetest.get_meta(pos) | 	local meta = minetest.get_meta(pos) | ||||||
| 	if overheat(pos) then return end | 	-- Note: These return success, presumably to avoid changing LC ID. | ||||||
| 	if ignore_event(event, meta) then return end | 	if overheat(pos) then return true, "", nil end | ||||||
|  | 	if ignore_event(event, meta) then return true, "", nil end | ||||||
|  |  | ||||||
| 	-- Load code & mem from meta | 	-- Load code & mem from meta | ||||||
| 	local mem  = load_memory(meta) | 	local mem  = load_memory(meta) | ||||||
| 	local code = meta:get_string("code") | 	local code = meta:get_string("code") | ||||||
|  |  | ||||||
|  | 	-- 'Last warning' label. | ||||||
|  | 	local warning = "" | ||||||
|  | 	local function send_warning(str) | ||||||
|  | 		warning = "Warning: " .. str | ||||||
|  | 	end | ||||||
|  |  | ||||||
| 	-- Create environment | 	-- Create environment | ||||||
| 	local env = create_environment(pos, mem, event) | 	local itbl = {} | ||||||
|  | 	local env = create_environment(pos, mem, event, itbl, send_warning) | ||||||
|  |  | ||||||
| 	-- Create the sandbox and execute code | 	-- Create the sandbox and execute code | ||||||
| 	local f, msg = create_sandbox(code, env) | 	local f, msg = create_sandbox(code, env) | ||||||
| 	if not f then return false, msg end | 	if not f then return false, msg, nil end | ||||||
| 	local succ, msg = pcall(f) | 	-- Start string true sandboxing | ||||||
| 	if not succ then return false, msg end | 	local onetruestring = getmetatable("") | ||||||
|  | 	-- If a string sandbox is already up yet inconsistent, something is very wrong | ||||||
|  | 	assert(onetruestring.__index == string) | ||||||
|  | 	onetruestring.__index = env.string | ||||||
|  | 	local success, msg = pcall(f) | ||||||
|  | 	onetruestring.__index = string | ||||||
|  | 	-- End string true sandboxing | ||||||
|  | 	if not success then return false, msg, nil end | ||||||
| 	if type(env.port) ~= "table" then | 	if type(env.port) ~= "table" then | ||||||
| 		return false, "Ports set are invalid." | 		return false, "Ports set are invalid.", nil | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	-- Actually set the ports | 	-- Actually set the ports | ||||||
| @@ -500,35 +650,81 @@ local function run(pos, event) | |||||||
| 	-- Save memory. This may burn the luacontroller if a memory overflow occurs. | 	-- Save memory. This may burn the luacontroller if a memory overflow occurs. | ||||||
| 	save_memory(pos, meta, env.mem) | 	save_memory(pos, meta, env.mem) | ||||||
|  |  | ||||||
| 	return succ, msg | 	-- Execute deferred tasks | ||||||
|  | 	for _, v in ipairs(itbl) do | ||||||
|  | 		local failure = v() | ||||||
|  | 		if failure then | ||||||
|  | 			return false, failure, nil | ||||||
|  | 		end | ||||||
|  | 	end | ||||||
|  | 	return true, warning, msg | ||||||
| end | end | ||||||
|  |  | ||||||
| mesecon.queue:add_function("pipeworks:lc_tube_interrupt", function (pos, luac_id, iid) | local function reset_formspec(meta, code, errmsg) | ||||||
| 	-- There is no lua_tube anymore / it has been reprogrammed / replaced / burnt | 	meta:set_string("code", code) | ||||||
| 	if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end | 	meta:mark_as_private("code") | ||||||
| 	if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end | 	code = minetest.formspec_escape(code or "") | ||||||
| 	run(pos, {type = "interrupt", iid = iid}) | 	errmsg = minetest.formspec_escape(tostring(errmsg or "")) | ||||||
| end) | 	meta:set_string("formspec", "size[12,10]" | ||||||
|  | 		.."background[-0.2,-0.25;12.4,10.75;jeija_luac_background.png]" | ||||||
|  | 		.."label[0.1,8.3;"..errmsg.."]" | ||||||
|  | 		.."textarea[0.2,0.2;12.2,9.5;code;;"..code.."]" | ||||||
|  | 		.."image_button[4.75,8.75;2.5,1;jeija_luac_runbutton.png;program;]" | ||||||
|  | 		.."image_button_exit[11.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]" | ||||||
|  | 		) | ||||||
|  | end | ||||||
|  |  | ||||||
| local function reset_meta(pos, code, errmsg) | local function reset_meta(pos, code, errmsg) | ||||||
| 	local meta = minetest.get_meta(pos) | 	local meta = minetest.get_meta(pos) | ||||||
| 	meta:set_string("code", code) | 	reset_formspec(meta, code, errmsg) | ||||||
| 	code = minetest.formspec_escape(code or "") |  | ||||||
| 	errmsg = minetest.formspec_escape(tostring(errmsg or "")) |  | ||||||
| 	meta:set_string("formspec", "size[12,10]".. |  | ||||||
| 		"background[-0.2,-0.25;12.4,10.75;jeija_luac_background.png]".. |  | ||||||
| 		"textarea[0.2,0.2;12.2,9.5;code;;"..code.."]".. |  | ||||||
| 		"image_button[4.75,8.75;2.5,1;jeija_luac_runbutton.png;program;]".. |  | ||||||
| 		"image_button_exit[11.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]".. |  | ||||||
| 		"label[0.1,9;"..errmsg.."]") |  | ||||||
| 	meta:set_int("luac_id", math.random(1, 65535)) | 	meta:set_int("luac_id", math.random(1, 65535)) | ||||||
| end | end | ||||||
|  |  | ||||||
|  | -- Wraps run_inner with LC-reset-on-error | ||||||
|  | local function run(pos, event) | ||||||
|  | 	local meta = minetest.get_meta(pos) | ||||||
|  | 	local code = meta:get_string("code") | ||||||
|  | 	local ok, errmsg, retval = run_inner(pos, code, event) | ||||||
|  | 	if not ok then | ||||||
|  | 		reset_meta(pos, code, errmsg) | ||||||
|  | 	else | ||||||
|  | 		reset_formspec(meta, code, errmsg) | ||||||
|  | 	end | ||||||
|  | 	return ok, errmsg, retval | ||||||
|  | end | ||||||
|  |  | ||||||
| local function reset(pos) | local function reset(pos) | ||||||
| 	set_port_states(pos, {red = false, blue = false, yellow = false, | 	set_port_states(pos, {red = false, blue = false, yellow = false, | ||||||
| 		green = false, black = false, white = false}) | 		green = false, black = false, white = false}) | ||||||
| end | end | ||||||
|  |  | ||||||
|  | local function node_timer(pos) | ||||||
|  | 	if minetest.registered_nodes[minetest.get_node(pos).name].is_burnt then | ||||||
|  | 		return false | ||||||
|  | 	end | ||||||
|  | 	run(pos, {type="interrupt"}) | ||||||
|  | 	return false | ||||||
|  | end | ||||||
|  |  | ||||||
|  | ----------------------- | ||||||
|  | -- A.Queue callbacks -- | ||||||
|  | ----------------------- | ||||||
|  |  | ||||||
|  | mesecon.queue:add_function("pipeworks:lc_tube_interrupt", function (pos, luac_id, iid) | ||||||
|  | 	-- There is no lua_tube anymore / it has been reprogrammed / replaced / burnt | ||||||
|  | 	if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end | ||||||
|  | 	if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end | ||||||
|  | 	run(pos, {type="interrupt", iid = iid}) | ||||||
|  | end) | ||||||
|  |  | ||||||
|  | mesecon.queue:add_function("pipeworks:lt_digiline_relay", function (pos, channel, luac_id, msg) | ||||||
|  | 	if not digiline then return end | ||||||
|  | 	-- This check is only really necessary because in case of server crash, old actions can be thrown into the future | ||||||
|  | 	if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end | ||||||
|  | 	if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end | ||||||
|  | 	-- The actual work | ||||||
|  | 	digiline:receptor_send(pos, digiline_rules_luatube, channel, msg) | ||||||
|  | end) | ||||||
|  |  | ||||||
| ----------------------- | ----------------------- | ||||||
| -- Node Registration -- | -- Node Registration -- | ||||||
| @@ -558,6 +754,7 @@ local digiline = { | |||||||
| 	receptor = {}, | 	receptor = {}, | ||||||
| 	effector = { | 	effector = { | ||||||
| 		action = function(pos, node, channel, msg) | 		action = function(pos, node, channel, msg) | ||||||
|  | 			msg = clean_and_weigh_digiline_message(msg) | ||||||
| 			run(pos, {type = "digiline", channel = channel, msg = msg}) | 			run(pos, {type = "digiline", channel = channel, msg = msg}) | ||||||
| 		end | 		end | ||||||
| 	}, | 	}, | ||||||
| @@ -565,6 +762,18 @@ local digiline = { | |||||||
| 		rules = pipeworks.digilines_rules | 		rules = pipeworks.digilines_rules | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | local function get_program(pos) | ||||||
|  | 	local meta = minetest.get_meta(pos) | ||||||
|  | 	return meta:get_string("code") | ||||||
|  | end | ||||||
|  |  | ||||||
|  | local function set_program(pos, code) | ||||||
|  | 	reset(pos) | ||||||
|  | 	reset_meta(pos, code) | ||||||
|  | 	return run(pos, {type="program"}) | ||||||
|  | end | ||||||
|  |  | ||||||
| local function on_receive_fields(pos, form_name, fields, sender) | local function on_receive_fields(pos, form_name, fields, sender) | ||||||
| 	if not fields.program then | 	if not fields.program then | ||||||
| 		return | 		return | ||||||
| @@ -574,12 +783,10 @@ local function on_receive_fields(pos, form_name, fields, sender) | |||||||
| 		minetest.record_protection_violation(pos, name) | 		minetest.record_protection_violation(pos, name) | ||||||
| 		return | 		return | ||||||
| 	end | 	end | ||||||
| 	reset(pos) | 	local ok, err = set_program(pos, fields.code) | ||||||
| 	reset_meta(pos, fields.code) | 	if not ok then | ||||||
| 	local succ, err = run(pos, {type="program"}) | 		-- it's not an error from the server perspective | ||||||
| 	if not succ then | 		minetest.log("action", "Lua controller programming error: " .. tostring(err)) | ||||||
| 		print(err) |  | ||||||
| 		reset_meta(pos, fields.code, err) |  | ||||||
| 	end | 	end | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -715,7 +922,11 @@ for white  = 0, 1 do | |||||||
| 		receptor = { | 		receptor = { | ||||||
| 			state = mesecon.state.on, | 			state = mesecon.state.on, | ||||||
| 			rules = output_rules[cid] | 			rules = output_rules[cid] | ||||||
| 		} | 		}, | ||||||
|  | 		luacontroller = { | ||||||
|  | 			get_program = get_program, | ||||||
|  | 			set_program = set_program, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	minetest.register_node(node_name, { | 	minetest.register_node(node_name, { | ||||||
| @@ -723,6 +934,7 @@ for white  = 0, 1 do | |||||||
| 		drawtype = "nodebox", | 		drawtype = "nodebox", | ||||||
| 		tiles = tiles, | 		tiles = tiles, | ||||||
| 		paramtype = "light", | 		paramtype = "light", | ||||||
|  | 		is_ground_content = false, | ||||||
| 		groups = groups, | 		groups = groups, | ||||||
| 		drop = BASENAME.."000000", | 		drop = BASENAME.."000000", | ||||||
| 		sunlight_propagates = true, | 		sunlight_propagates = true, | ||||||
| @@ -749,6 +961,7 @@ for white  = 0, 1 do | |||||||
| 			pipeworks.after_dig(pos, node) | 			pipeworks.after_dig(pos, node) | ||||||
| 		end, | 		end, | ||||||
| 		is_luacontroller = true, | 		is_luacontroller = true, | ||||||
|  | 		on_timer = node_timer, | ||||||
| 		tubelike = 1, | 		tubelike = 1, | ||||||
| 		tube = { | 		tube = { | ||||||
| 			connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1}, | 			connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1}, | ||||||
| @@ -762,7 +975,7 @@ for white  = 0, 1 do | |||||||
| 						break | 						break | ||||||
| 					end | 					end | ||||||
| 				end | 				end | ||||||
| 				local succ, msg = run(pos, { | 				local succ, _, msg = run(pos, { | ||||||
| 					type = "item", | 					type = "item", | ||||||
| 					pin = src, | 					pin = src, | ||||||
| 					itemstring = stack:to_string(), | 					itemstring = stack:to_string(), | ||||||
| @@ -785,14 +998,6 @@ for white  = 0, 1 do | |||||||
| 			minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"}) | 			minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"}) | ||||||
| 			pipeworks.scan_for_tube_objects(pos) | 			pipeworks.scan_for_tube_objects(pos) | ||||||
| 		end, | 		end, | ||||||
| 		on_blast = function(pos, intensity) |  | ||||||
| 			if not intensity or intensity > 1 + 3^0.5 then |  | ||||||
| 				minetest.remove_node(pos) |  | ||||||
| 				return {string.format("%s_%s", name, dropname)} |  | ||||||
| 			end |  | ||||||
| 			minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"}) |  | ||||||
| 			pipeworks.scan_for_tube_objects(pos) |  | ||||||
| 		end, |  | ||||||
| 	}) | 	}) | ||||||
| end | end | ||||||
| end | end | ||||||
| @@ -836,6 +1041,7 @@ minetest.register_node(BASENAME .. "_burnt", { | |||||||
| 	tiles = tiles_burnt, | 	tiles = tiles_burnt, | ||||||
| 	is_burnt = true, | 	is_burnt = true, | ||||||
| 	paramtype = "light", | 	paramtype = "light", | ||||||
|  | 	is_ground_content = false, | ||||||
| 	groups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory=1}, | 	groups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory=1}, | ||||||
| 	drop = BASENAME.."000000", | 	drop = BASENAME.."000000", | ||||||
| 	sunlight_propagates = true, | 	sunlight_propagates = true, | ||||||
|   | |||||||
| @@ -340,6 +340,8 @@ local move_entities_globalstep_part2 = function(dtime) | |||||||
| 			entity._velocity = master_entity:get_velocity() | 			entity._velocity = master_entity:get_velocity() | ||||||
| 			entity._acceleration = master_entity:get_acceleration() | 			entity._acceleration = master_entity:get_acceleration() | ||||||
| 		else | 		else | ||||||
|  | 			entity._velocity = entity._velocity or vector.new(0,0,0) | ||||||
|  | 			entity._acceleration = entity._acceleration or vector.new(0,0,0) | ||||||
| 			entity._pos = vector.add(vector.add( | 			entity._pos = vector.add(vector.add( | ||||||
| 				entity._pos, | 				entity._pos, | ||||||
| 				vector.multiply(entity._velocity, dtime)), | 				vector.multiply(entity._velocity, dtime)), | ||||||
|   | |||||||
| @@ -261,3 +261,11 @@ if minetest.get_modpath("mesecons_mvps") ~= nil then | |||||||
| 		end | 		end | ||||||
| 	end) | 	end) | ||||||
| end | end | ||||||
|  |  | ||||||
|  | -- Expose teleport tube database API for other mods | ||||||
|  | pipeworks.tptube = { | ||||||
|  | 	hash = hash, | ||||||
|  | 	save_tube_db = save_tube_db, | ||||||
|  | 	get_db = function() return tp_tube_db or read_tube_db() end, | ||||||
|  | 	tp_tube_db_version = tp_tube_db_version | ||||||
|  | } | ||||||
|   | |||||||
| @@ -378,7 +378,7 @@ if pipeworks.enable_node_breaker then | |||||||
| 	minetest.register_craft({ | 	minetest.register_craft({ | ||||||
| 		output = "pipeworks:nodebreaker_off", | 		output = "pipeworks:nodebreaker_off", | ||||||
| 		recipe = { | 		recipe = { | ||||||
| 			{ "pipeworks:gear", "pipeworks:gear",   "pipeworks:gear"    }, | 			{ "basic_materials:gear_steel", "basic_materials:gear_steel",   "basic_materials:gear_steel"    }, | ||||||
| 			{ "default:stone", "mesecons:piston",   "default:stone" }, | 			{ "default:stone", "mesecons:piston",   "default:stone" }, | ||||||
| 			{ "group:wood",    "mesecons:mesecon",  "group:wood" }, | 			{ "group:wood",    "mesecons:mesecon",  "group:wood" }, | ||||||
| 		} | 		} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user