functions.lua 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. -- This file is designed to be reloadable.
  2. teleports = teleports or {}
  3. teleports.teleports = teleports.teleports or {}
  4. teleports.min_range = 250
  5. teleports.datafile = minetest.get_worldpath() .. "/teleports.txt"
  6. -- Localize for performance.
  7. local vector_distance = vector.distance
  8. local vector_round = vector.round
  9. local vector_add = vector.add
  10. local vector_equals = vector.equals
  11. local math_floor = math.floor
  12. local math_random = math.random
  13. dofile(teleports.modpath .. "/sickness.lua")
  14. local nyanbow = "rosestone:tail"
  15. -- Table of blocks which can be used to super-charge a teleport. Each block has a specific charge value.
  16. teleports.charge_blocks = {
  17. ["default:diamondblock"] = {charge=14 },
  18. ["default:mese"] = {charge=4 },
  19. ["default:steelblock"] = {charge=1 },
  20. ["default:copperblock"] = {charge=1.5 },
  21. ["default:bronzeblock"] = {charge=1.8 },
  22. ["default:goldblock"] = {charge=2 },
  23. ["moreores:silver_block"] = {charge=2 },
  24. ["moreores:tin_block"] = {charge=1.5 },
  25. ["moreores:mithril_block"] = {charge=24 },
  26. ["chromium:block"] = {charge=0.9 },
  27. ["zinc:block"] = {charge=1.8 },
  28. ["lead:block"] = {charge=0.4 },
  29. ["akalin:block"] = {charge=0.9 },
  30. ["alatro:block"] = {charge=0.7 },
  31. ["arol:block"] = {charge=0.5 },
  32. ["talinite:block"] = {charge=1.1 },
  33. }
  34. function teleports.nearest_beacons_to_position(pos, num, rangelim)
  35. local get_rn = rc.current_realm_at_pos
  36. local realm = get_rn(pos)
  37. -- Copy the master table's indices so we don't modify it.
  38. -- We do not need to copy the inner table data itself. Just the indices.
  39. -- Only copy over blocks in the same realm, too.
  40. local blocks = {}
  41. local sblocks = teleports.teleports
  42. for i=1, #sblocks, 1 do
  43. local v = sblocks[i]
  44. local p = v.pos
  45. if v.is_recall then
  46. if rangelim then
  47. if vector_distance(p, pos) < rangelim then
  48. if get_rn(p) == realm then
  49. blocks[#blocks+1] = v
  50. end
  51. end
  52. else
  53. if get_rn(p) == realm then
  54. blocks[#blocks+1] = v
  55. end
  56. end
  57. end
  58. end
  59. -- Sort blocks, nearest blocks first.
  60. table.sort(blocks,
  61. function(a, b)
  62. local d1 = vector_distance(a.pos, pos)
  63. local d2 = vector_distance(b.pos, pos)
  64. return d1 < d2
  65. end)
  66. -- Return N-nearest blocks (should be at the front of the sorted table).
  67. local ret = {}
  68. for i=1, num, 1 do
  69. if i <= #blocks then
  70. ret[#ret+1] = blocks[i]
  71. else
  72. break
  73. end
  74. end
  75. return ret
  76. end
  77. teleports.is_nyanbow_teleport = function(pos)
  78. local positions = {
  79. {x=pos.x-1, y=pos.y, z=pos.z},
  80. {x=pos.x+1, y=pos.y, z=pos.z},
  81. {x=pos.x, y=pos.y, z=pos.z-1},
  82. {x=pos.x, y=pos.y, z=pos.z+1},
  83. {x=pos.x-1, y=pos.y, z=pos.z-1},
  84. {x=pos.x+1, y=pos.y, z=pos.z+1},
  85. {x=pos.x-1, y=pos.y, z=pos.z+1},
  86. {x=pos.x+1, y=pos.y, z=pos.z-1},
  87. }
  88. local bows = 0
  89. for k, v in ipairs(positions) do
  90. local n = minetest.get_node(v).name
  91. if n == nyanbow then
  92. bows = bows + 1
  93. end
  94. end
  95. return (bows == 8)
  96. end
  97. teleports.save = function()
  98. local datastring = xban.serialize(teleports.teleports)
  99. if not datastring then
  100. return
  101. end
  102. minetest.safe_file_write(teleports.datafile, datastring)
  103. --[[
  104. local file, err = io.open(teleports.datafile, "w")
  105. if err then
  106. return
  107. end
  108. file:write(datastring)
  109. file:close()
  110. --]]
  111. end
  112. teleports.load = function()
  113. local file, err = io.open(teleports.datafile, "r")
  114. if err then
  115. teleports.teleports = {}
  116. return
  117. end
  118. teleports.teleports = minetest.deserialize(file:read("*all"))
  119. if type(teleports.teleports) ~= "table" then
  120. teleports.teleports = {}
  121. end
  122. file:close()
  123. end
  124. teleports.clear_area = function(blockpos, action, calls_remaining, param)
  125. if action == core.EMERGE_CANCELLED or action == core.EMERGE_ERRORED then
  126. return
  127. end
  128. for x = param.minp.x, param.maxp.x, 1 do
  129. for y = param.minp.y, param.maxp.y, 1 do
  130. for z = param.minp.z, param.maxp.z, 1 do
  131. local pos = {x=x, y=y, z=z}
  132. local node = minetest.get_node(pos)
  133. if node.name ~= "ignore" then
  134. if node.name ~= "air" and node.name ~= "bones:bones" then
  135. minetest.remove_node(pos)
  136. end
  137. end
  138. end
  139. end
  140. end
  141. end
  142. function teleports.kill_players_at_pos(teleport_pos, pname)
  143. local dead_players = minetest.get_objects_inside_radius({x=teleport_pos.x, y=teleport_pos.y+1, z=teleport_pos.z}, 2)
  144. for k, v in ipairs(dead_players) do
  145. if v and v:is_player() then
  146. if not gdac.player_is_admin(v) and v:get_player_name() ~= pname then -- Don't kill admin or self (can happen due to lag).
  147. -- Only if player isn't already dead.
  148. if v:get_hp() > 0 then
  149. -- If there's a player here already the map must be loaded, so we
  150. -- can put fire where they're standing no problem.
  151. local dp = vector_round(v:get_pos())
  152. local node = minetest.get_node(dp)
  153. if node.name == "air" then
  154. minetest.add_node(dp, {name="fire:basic_flame"})
  155. end
  156. v:set_hp(0)
  157. minetest.chat_send_all("# Server: <" .. rename.gpn(v:get_player_name()) .. "> was killed by a teleport. Noob!")
  158. end
  159. end
  160. end
  161. end
  162. end
  163. -- Calculates the probability scalar of a TP to misjump based on range vs max range.
  164. local function tpc(r1, r2)
  165. if r1 > r2 then return 0 end
  166. local d = r1 / r2
  167. d = d * -1 + 1
  168. for i=1, 10, 1 do
  169. d = d * 1.719 + 1
  170. d = math.log(d)
  171. end
  172. if d < 0 then d = 0 end
  173. if d > 1 then d = 1 end
  174. return d
  175. end
  176. teleports.teleport_player = function(player, origin_pos, teleport_pos, teleport_range)
  177. if not player or not player:is_player() then
  178. return
  179. end
  180. local pname = player:get_player_name()
  181. if sheriff.is_cheater(pname) then
  182. if sheriff.punish_probability(pname) then
  183. sheriff.punish_player(pname)
  184. return
  185. end
  186. end
  187. local player_pos = player:get_pos()
  188. if (player_pos.y < origin_pos.y) or (vector_distance(player_pos, origin_pos) > 2) then
  189. minetest.chat_send_player(pname, "# Server: You must stand on portal activation surface!")
  190. return
  191. end
  192. -- Small chance to be teleported somewhere completely random.
  193. -- The chance increases a LOT if teleports are crowded.
  194. -- You could theorize that their signals interfere with each other.
  195. local use_random = false
  196. local random_chance = 1030 -- Actually 1000, because nearby-count is always at least 1 (counting self).
  197. local count_nearby = 0
  198. -- Count number of nearby teleports (including self).
  199. for k, v in ipairs(teleports.teleports) do
  200. if vector_distance(v.pos, origin_pos) < 100 then
  201. count_nearby = count_nearby + 1
  202. end
  203. end
  204. -- Chance of misjump increases if teleports are crowded.
  205. random_chance = random_chance - (count_nearby * 30)
  206. if random_chance < 0 then random_chance = 0 end
  207. -- Chance of misjump increases as teleport is operated closer to its max range.
  208. local teleport_distance = vector_distance(origin_pos, teleport_pos)
  209. random_chance = random_chance * tpc(teleport_distance, teleport_range)
  210. -- Chance should never be worse than 1 in 50.
  211. if random_chance < 50 then
  212. random_chance = 50
  213. end
  214. random_chance = math_floor(random_chance)
  215. --minetest.chat_send_all('chance: ' .. random_chance)
  216. if math_random(1, random_chance) == 1 then
  217. if #(teleports.teleports) > 0 then
  218. local tp = teleports.teleports[math_random(1, #(teleports.teleports))]
  219. if not tp then
  220. minetest.chat_send_player(pname, "# Server: Transport error! Aborted.")
  221. return
  222. end
  223. teleport_pos = tp.pos
  224. use_random = true
  225. end
  226. end
  227. local p = vector_round(teleport_pos)
  228. local minp = {x=p.x-1, y=p.y+1, z=p.z-1}
  229. local maxp = {x=p.x+1, y=p.y+3, z=p.z+1}
  230. local target = {x=p.x-1+math_random(0, 2), y=p.y+1, z=p.z-1+math_random(0, 2)}
  231. local pos = vector_round(target)
  232. -- Perform this check only if teleport target wasn't randomized.
  233. if not use_random then
  234. local start_realm = rc.current_realm_at_pos(origin_pos)
  235. local target_realm = rc.current_realm_at_pos(pos)
  236. if target_realm == "" or start_realm == "" or start_realm ~= target_realm then
  237. minetest.chat_send_player(pname, "# Server: Target location is in a different realm! Aborting.")
  238. return
  239. end
  240. end
  241. minetest.log("[teleports] teleporting player <" .. pname .. "> to " .. minetest.pos_to_string(pos))
  242. -- Teleport player to chosen location.
  243. preload_tp.execute({
  244. player_name = pname,
  245. target_position = pos,
  246. send_blocks = true,
  247. particle_effects = true,
  248. pre_teleport_callback = function()
  249. -- Kill players standing on target teleport pad.
  250. teleports.kill_players_at_pos(teleport_pos, pname)
  251. -- Delete 3x3x3 area above teleport.
  252. for x=minp.x, maxp.x do
  253. for y=minp.y, maxp.y do
  254. for z=minp.z, maxp.z do
  255. local pos = {x=x, y=y, z=z}
  256. local node = minetest.get_node(pos)
  257. if node.name ~= "ignore" then
  258. -- Do not destroy players' bones.
  259. if node.name ~= "air" and node.name ~= "bones:bones" then
  260. minetest.add_node(pos, {name="fire:basic_flame"})
  261. end
  262. end
  263. end
  264. end
  265. end
  266. end,
  267. post_teleport_callback = function()
  268. portal_sickness.on_use_portal(pname)
  269. if use_random then
  270. minetest.after(10, function()
  271. local RED = core.get_color_escape_sequence("#ff0000")
  272. minetest.chat_send_player(pname, RED .. "# Server: Coordinate translation error. Unknown destination!")
  273. chat_core.alert_player_sound(pname)
  274. end)
  275. end
  276. end,
  277. })
  278. teleports.ping_all_teleports(player)
  279. end
  280. teleports.find_nearby = function(pos, count, network, yespublic)
  281. local nearby = {}
  282. local trange, isnyan = teleports.calculate_range(pos)
  283. local start_realm = rc.current_realm_at_pos(pos)
  284. for i = #teleports.teleports, 1, -1 do
  285. local tp = teleports.teleports[i]
  286. if not vector_equals(tp.pos, pos) and vector_distance(tp.pos, pos) <= trange then
  287. local target_realm = rc.current_realm_at_pos(tp.pos)
  288. -- Only find teleports in the same dimension.
  289. if start_realm ~= "" and start_realm == target_realm then
  290. local othernet = tp.channel or ""
  291. if othernet == network or (othernet == "" and yespublic == 'true') then
  292. table.insert(nearby, tp)
  293. if #nearby >= count then
  294. break
  295. end
  296. end
  297. end
  298. end
  299. end
  300. return nearby
  301. end
  302. teleports.find_specific = function(pos)
  303. for i = 1, #teleports.teleports, 1 do
  304. local tp = teleports.teleports[i]
  305. if vector_equals(tp.pos, pos) then
  306. return i -- Return index of teleport.
  307. end
  308. end
  309. end
  310. teleports.calculate_charge = function(pos)
  311. local positions = {
  312. {x=pos.x-1, y=pos.y, z=pos.z},
  313. {x=pos.x+1, y=pos.y, z=pos.z},
  314. {x=pos.x, y=pos.y, z=pos.z-1},
  315. {x=pos.x, y=pos.y, z=pos.z+1},
  316. {x=pos.x-1, y=pos.y, z=pos.z-1},
  317. {x=pos.x+1, y=pos.y, z=pos.z+1},
  318. {x=pos.x-1, y=pos.y, z=pos.z+1},
  319. {x=pos.x+1, y=pos.y, z=pos.z-1},
  320. }
  321. local bows = 0
  322. local charge = 1 -- Ambient charge is at least 1 (the teleport block provides 1 KJ).
  323. for k, v in ipairs(positions) do
  324. local n = minetest.get_node(v).name
  325. local c = 0
  326. if teleports.charge_blocks[n] ~= nil then
  327. c = teleports.charge_blocks[n].charge
  328. end
  329. charge = charge + c
  330. if n == nyanbow then
  331. bows = bows + 1
  332. end
  333. end
  334. local is_nyanporter = false
  335. if bows == 8 then
  336. is_nyanporter = true
  337. end
  338. charge = math_floor(charge + 0.5)
  339. -- Combined teleports interfere with each other and reduce their range.
  340. local minp = vector.add(pos, {x=-2, y=0, z=-2})
  341. local maxp = vector.add(pos, {x=2, y=0, z=2})
  342. local others = minetest.find_nodes_in_area(minp, maxp, "teleports:teleport")
  343. local other_count = 1
  344. if others and #others > 0 then
  345. charge = charge / #others
  346. other_count = #others
  347. end
  348. return charge, other_count, is_nyanporter
  349. end
  350. local function cds(y)
  351. local scalar = 1
  352. if y < 0 then
  353. -- You can probably tell that I'm really, really bad at math.
  354. local depth = math.abs(y)
  355. scalar = depth / 30912
  356. -- Clamp.
  357. if scalar > 1 then scalar = 1 end
  358. if scalar < 0 then scalar = 0 end
  359. -- Invert.
  360. scalar = (scalar * -1) + 1
  361. -- Magic!
  362. -- The number of iterations determines the steepness of the curve.
  363. for i=1, 5, 1 do
  364. scalar = scalar * 1.719
  365. scalar = scalar + 1
  366. -- Input to log should be [1, 2.719].
  367. -- Log should return something in range [0, 1].
  368. scalar = math.log(scalar)
  369. end
  370. -- Clamp.
  371. if scalar > 1 then scalar = 1 end
  372. if scalar < 0 then scalar = 0 end
  373. end
  374. return scalar
  375. end
  376. teleports.calculate_range = function(pos)
  377. -- Compute charge.
  378. local chg, other_cnt, nyan = teleports.calculate_charge(pos)
  379. if nyan then
  380. local owner = minetest.get_meta(pos):get_string("owner")
  381. -- There is an admin teleport pair between the Surface Colony and the City of Fire.
  382. -- This special exception code makes it work.
  383. if owner == gdac.name_of_admin then
  384. return 31000, nyan
  385. else
  386. -- Range of nyan teleports is reduced if they're crowded.
  387. return (7770 / other_cnt), nyan
  388. end
  389. end
  390. -- How much distance each unit of charge is good for.
  391. local inc = 25
  392. -- Compute extra range.
  393. local rng = math_floor(inc * chg)
  394. -- Calculate how much to scale extra range by depth.
  395. local scalar = cds(pos.y)
  396. -- Scale extra range by depth.
  397. rng = rng * scalar
  398. -- Add extra range to base (minimum) range.
  399. rng = rng + 250
  400. -- Teleport range shall not go below 250 meters.
  401. return math_floor(rng), nyan
  402. end
  403. function teleports.write_infotext(pos)
  404. local meta = minetest.get_meta(pos)
  405. local inv = meta:get_inventory()
  406. local name = meta:get_string("name")
  407. local network = meta:get_string("network")
  408. local owner = meta:get_string("owner")
  409. local dname = rename.gpn(owner)
  410. local public = meta:get_int("public") or 1
  411. if public == 1 then public = 'true' else public = 'false' end
  412. local id = "<" .. name .. ">"
  413. local net = "<" .. network .. ">"
  414. local own = "<" .. dname .. ">"
  415. if name == "" then id = "NONE" end
  416. if network == "" then net = "PUBLIC" end
  417. if public == 'false' then net = "SUPPRESSED" end
  418. local beacon = ""
  419. local item = {name="rosestone:head", count=1, wear=0, metadata=""}
  420. if inv:contains_item("price", item) then
  421. beacon = "\nRecall signal emission normal"
  422. end
  423. meta:set_string("infotext", "Teleporter. Punch to update controls.\nOwner: " .. own .. "\nBeacon ID: " .. id .. "\nBeacon Channel: " .. net .. beacon)
  424. end
  425. teleports.update = function(pos)
  426. local meta = minetest.get_meta(pos)
  427. local network = meta:get_string("network") or ""
  428. local owner = meta:get_string("owner") or ""
  429. local name = meta:get_string("name") or ""
  430. local yespublic = meta:get_string("yespublic") or 'true'
  431. local buttons = "";
  432. local nearby = teleports.find_nearby(pos, 10, network, yespublic)
  433. local button_x = 8
  434. local button_y = 1
  435. for i, v in ipairs(nearby) do
  436. local tp = v.pos
  437. local data = tp.x .. "," .. tp.y .. "," .. tp.z
  438. local real_label = rc.pos_to_string(tp)
  439. meta:set_string("loc" .. (i), data)
  440. if v.name ~= nil then
  441. if v.name ~= "" then
  442. real_label = v.name
  443. end
  444. end
  445. buttons = buttons ..
  446. "button_exit[" .. button_x .. "," .. button_y ..
  447. ";3,0.5;tp" .. i .. ";" .. minetest.formspec_escape(real_label) .. "]";
  448. button_y = button_y + 1
  449. if button_y >= 6 then
  450. button_y = 1
  451. button_x = 5
  452. end
  453. end
  454. local public = meta:get_int("public") or 1
  455. if public == 1 then public = 'true' else public = 'false' end
  456. teleports.write_infotext(pos)
  457. local net = "<" .. network .. ">"
  458. local nm = "<" .. name .. ">"
  459. if name == "" then nm = "NONE" end
  460. if network == "" then net = "PUBLIC" end
  461. if public == 'false' then net = "SUPPRESSED" end
  462. local charge, count, isnyan = teleports.calculate_charge(pos)
  463. local range = teleports.calculate_range(pos)
  464. if isnyan then
  465. charge = "ROSE"
  466. end
  467. local formspec = "size[11,7;]" ..
  468. default.gui_bg ..
  469. default.gui_bg_img ..
  470. default.gui_slots ..
  471. "label[0,0;" .. 'Transport to nearby beacons! Need mossy cobble for energy.' .. "]" ..
  472. "label[1,0.70;Beacon ID: " .. minetest.formspec_escape(nm) .. "]" ..
  473. "label[1,1.2;Beacon Channel: " .. minetest.formspec_escape(net) .. "]" ..
  474. "field[0.3,2.7;2,0.5;id;Change Beacon ID;]" .. "button_exit[2,2.4;2,0.5;change_id;Confirm]" ..
  475. "field[0.3,3.9;2,0.5;network;Change Channel;]" .. "button_exit[2,3.6;2,0.5;change_network;Confirm]" ..
  476. buttons ..
  477. "button_exit[0,5.2;2,0.5;cancel;Close]" ..
  478. "checkbox[0.02,4.1;showhide;Show Channel;" .. public .. "]" ..
  479. "checkbox[2,4.1;yespublic;Connect Public;" .. yespublic .. "]" ..
  480. "label[2,5;Ambient Charge: " .. charge .. " KJ]" ..
  481. "label[2,5.35;Transport Range: " .. range .. " M]" ..
  482. "list[context;price;0,0.75;1,1;]" ..
  483. "list[current_player;main;0,6;11,1;]" ..
  484. "listring[]"
  485. meta:set_string("formspec", formspec)
  486. end
  487. teleports.on_receive_fields = function(pos, formname, fields, player)
  488. if not player then return end
  489. if not player:is_player() then return end
  490. if player:get_hp() <= 0 then return end -- Ignore dead players.
  491. local playername = player:get_player_name()
  492. local meta = minetest.get_meta(pos)
  493. local isnyan = teleports.is_nyanbow_teleport(pos)
  494. local owner = meta:get_string("owner") or ""
  495. local infinite_fuel = false
  496. if owner == "MustTest" then
  497. infinite_fuel = true
  498. else
  499. local inv = meta:get_inventory()
  500. local item = {name="rosestone:head", count=1, wear=0, metadata=""}
  501. if inv:contains_item("price", item) then
  502. infinite_fuel = true
  503. end
  504. end
  505. local admin = minetest.check_player_privs(playername, {server=true})
  506. local needsave = false
  507. -- Make sure this teleport, at this postion, has an entry.
  508. local tp_idx = teleports.find_specific(pos)
  509. if not tp_idx then
  510. minetest.chat_send_player(playername, "# Server: Transporter data error: 0xDEADBEEF.")
  511. easyvend.sound_error(playername)
  512. return
  513. end
  514. if not teleports.teleports[tp_idx] then
  515. minetest.chat_send_player(playername, "# Server: Transporter data error: 0xDEADBEEF.")
  516. easyvend.sound_error(playername)
  517. return
  518. end
  519. if fields.showhide then
  520. if owner == playername or admin then
  521. if fields.showhide == "true" then
  522. meta:set_int("public", 1)
  523. else
  524. meta:set_int("public", 0)
  525. end
  526. else
  527. minetest.chat_send_player(playername, "# Server: Only the owner can change the configuration.")
  528. easyvend.sound_error(playername)
  529. end
  530. end
  531. if fields.yespublic then
  532. if owner == playername or admin then
  533. if fields.yespublic == "true" then
  534. meta:set_string("yespublic", 'true')
  535. else
  536. meta:set_string("yespublic", 'false')
  537. end
  538. else
  539. minetest.chat_send_player(playername, "# Server: Only the owner can change the configuration.")
  540. easyvend.sound_error(playername)
  541. end
  542. end
  543. if fields.change_id and fields.id then
  544. if owner == playername or admin then
  545. meta:set_string("name", fields.id)
  546. teleports.teleports[tp_idx].name = fields.id
  547. needsave = true
  548. else
  549. minetest.chat_send_player(playername, "# Server: Only the owner can change the configuration.")
  550. easyvend.sound_error(playername)
  551. end
  552. end
  553. if fields.change_network and fields.network then
  554. if owner == playername or admin then
  555. meta:set_string("network", fields.network)
  556. teleports.teleports[tp_idx].channel = fields.network
  557. needsave = true
  558. else
  559. minetest.chat_send_player(playername, "# Server: Only the owner can change the configuration.")
  560. easyvend.sound_error(playername)
  561. end
  562. end
  563. if needsave == true then
  564. teleports.save()
  565. end
  566. local pressed_tp_button = false
  567. local pressed_tp_location
  568. for i = 1, 10, 1 do
  569. -- According to button names/data set in the machine update function.
  570. local btnname = "tp" .. i
  571. local posname = "loc" .. i
  572. if fields[btnname] then
  573. pressed_tp_button = true
  574. pressed_tp_location = meta:get_string(posname)
  575. break
  576. end
  577. end
  578. if pressed_tp_button then
  579. local have_biofuel = false
  580. local tpname = pressed_tp_location
  581. local have_target = false
  582. local target_pos = {x=0, y=0, z=0}
  583. local teleport_range = nil
  584. if tpname and type(tpname) == "string" then
  585. local tppos = minetest.string_to_pos(tpname)
  586. if tppos then
  587. teleport_range = teleports.calculate_range(pos)
  588. if vector_distance(tppos, pos) <= teleport_range then
  589. -- Do not permit teleporting from one realm to another.
  590. -- Doing so requires a different kind of teleport device.
  591. local start_realm = rc.current_realm_at_pos(pos)
  592. local target_realm = rc.current_realm_at_pos(tppos)
  593. if start_realm ~= "" and start_realm == target_realm then
  594. local exists = false
  595. for i = 1, #teleports.teleports, 1 do
  596. local tp = teleports.teleports[i]
  597. if vector_equals(tp.pos, tppos) then
  598. exists = true
  599. break
  600. end
  601. end
  602. if exists then
  603. have_target = true
  604. target_pos = tppos
  605. else
  606. minetest.chat_send_player(playername, "# Server: Transport control error: target no longer exists.")
  607. easyvend.sound_error(playername)
  608. end
  609. else
  610. minetest.chat_send_player(playername, "# Server: Cannot teleport between realm boundaries!")
  611. easyvend.sound_error(playername)
  612. end
  613. else
  614. minetest.chat_send_player(playername, "# Server: Transport control error: target out of range!")
  615. easyvend.sound_error(playername)
  616. end
  617. else
  618. minetest.chat_send_player(playername, "# Server: Transport control error: 0xDEADBEEF.")
  619. easyvend.sound_error(playername)
  620. end
  621. else
  622. minetest.chat_send_player(playername, "# Server: Transport control error: formspec.")
  623. easyvend.sound_error(playername)
  624. end
  625. if have_target == true then -- Don't use fuel unless a valid target is found.
  626. local inv = meta:get_inventory();
  627. if not admin and not infinite_fuel then -- Don't do fuel calculation if admin is using teleport.
  628. -- Cost is 1 item of fuel per 300 meters.
  629. -- This means players save on fuel when using long range teleports,
  630. -- instead of using a chain of short-range teleports.
  631. -- However, long range teleports cost more to make.
  632. local rcost = math_floor(vector_distance(pos, target_pos) / 300)
  633. if isnyan then
  634. -- Nyan teleports have much greater fuel efficiency.
  635. rcost = math_floor(vector_distance(pos, target_pos) / 600)
  636. end
  637. if rcost < 1 then rcost = 1 end
  638. -- If using lilies as fuel, fewer are required.
  639. -- Lilies are bit harder to get.
  640. local lcost = math_floor(rcost * 0.5)
  641. if lcost < 1 then lcost = 1 end
  642. local price1 = {name="default:mossycobble", count=rcost, wear=0, metadata=""}
  643. local price2 = {name="flowers:waterlily", count=lcost, wear=0, metadata=""}
  644. if not inv:is_empty("price") then
  645. if inv:contains_item("price", price1) then
  646. inv:remove_item("price", price1)
  647. have_biofuel = true
  648. elseif inv:contains_item("price", price2) then
  649. inv:remove_item("price", price2)
  650. have_biofuel = true
  651. else
  652. minetest.chat_send_player(playername, "# Server: Insufficient stored energy for transport. Add more biofuel.")
  653. easyvend.sound_error(playername)
  654. end
  655. else
  656. minetest.chat_send_player(playername, "# Server: Transporter is on maintenance energy only. Add biofuel to use.")
  657. easyvend.sound_error(playername)
  658. end
  659. end
  660. if have_biofuel or admin or infinite_fuel then
  661. local teleport_pos = {x=target_pos.x, y=target_pos.y, z=target_pos.z}
  662. teleports.teleport_player(player, pos, teleport_pos, teleport_range)
  663. end
  664. end
  665. end
  666. -- Always update the teleport formspec.
  667. teleports.update(pos)
  668. end
  669. teleports.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  670. local pname = player:get_player_name()
  671. -- Protection interferes with building public networks.
  672. --if minetest.test_protection(pos, pname) then return 0 end
  673. if listname == "price" and stack:get_name() == "default:mossycobble" then
  674. return stack:get_count()
  675. elseif listname == "price" and stack:get_name() == "flowers:waterlily" then
  676. return stack:get_count()
  677. elseif listname == "price" and stack:get_name() == "rosestone:head" then
  678. if minetest.test_protection(pos, pname) then return 0 end
  679. return stack:get_count()
  680. end
  681. return 0
  682. end
  683. teleports.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  684. local pname = player:get_player_name()
  685. -- Protection interferes with building public networks.
  686. --if minetest.test_protection(pos, pname) then return 0 end
  687. if stack:get_name() == "rosestone:head" then
  688. if minetest.test_protection(pos, pname) then return 0 end
  689. return stack:get_count()
  690. end
  691. return stack:get_count()
  692. end
  693. teleports.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  694. return 0
  695. end
  696. function teleports.update_beacon_data(pos)
  697. local meta = minetest.get_meta(pos)
  698. local inv = meta:get_inventory()
  699. local item = {name="rosestone:head", count=1, wear=0, metadata=""}
  700. if inv:contains_item("price", item) then
  701. for k, v in ipairs(teleports.teleports) do
  702. if vector_equals(v.pos, pos) then
  703. if not v.is_recall then
  704. v.is_recall = true
  705. teleports.save()
  706. end
  707. end
  708. end
  709. else
  710. for k, v in ipairs(teleports.teleports) do
  711. if vector_equals(v.pos, pos) then
  712. if v.is_recall then
  713. v.is_recall = nil
  714. teleports.save()
  715. end
  716. end
  717. end
  718. end
  719. end
  720. function teleports.on_metadata_inventory_put(pos, listname, index, stack, player)
  721. teleports.update_beacon_data(pos)
  722. end
  723. function teleports.on_metadata_inventory_take(pos, listname, index, stack, player)
  724. teleports.update_beacon_data(pos)
  725. end
  726. teleports.can_dig = function(pos)
  727. local meta = minetest.get_meta(pos)
  728. local inv = meta:get_inventory()
  729. return inv:is_empty("price")
  730. end
  731. teleports.after_place_node = function(pos, placer)
  732. if placer and placer:is_player() then
  733. local meta = minetest.get_meta(pos)
  734. local pname = placer:get_player_name()
  735. local dname = rename.gpn(pname)
  736. meta:set_string("owner", pname)
  737. meta:set_string("rename", dname)
  738. meta:set_string("name", "")
  739. meta:set_string("network", "")
  740. meta:set_int("public", 1)
  741. local inv = meta:get_inventory()
  742. inv:set_size("price", 1)
  743. local initialcharge = {name="default:mossycobble", count=30, wear=0, metadata=""}
  744. inv:add_item("price", initialcharge)
  745. teleports.update(pos)
  746. table.insert(teleports.teleports, {pos=vector_round(pos)})
  747. teleports.save()
  748. end
  749. end
  750. teleports.on_destruct = function(pos)
  751. --minetest.chat_send_all("# Server: Destructing teleport!")
  752. for i, EachTeleport in ipairs(teleports.teleports) do
  753. if vector_equals(EachTeleport.pos, pos) then
  754. table.remove(teleports.teleports, i)
  755. teleports.save()
  756. end
  757. end
  758. end
  759. function teleports.ping_all_teleports(initiating_player)
  760. local players = minetest.get_connected_players()
  761. local ping = function(pos)
  762. local xd = 1
  763. local zd = 1
  764. minetest.add_particlespawner({
  765. amount = 80,
  766. time = 5,
  767. minpos = {x=pos.x-xd, y=pos.y+1, z=pos.z-zd},
  768. maxpos = {x=pos.x+xd, y=pos.y+3, z=pos.z+zd},
  769. minvel = {x=-1, y=-1, z=-1},
  770. maxvel = {x=1, y=1, z=1},
  771. minacc = {x=-1, y=-1, z=-1},
  772. maxacc = {x=1, y=1, z=1},
  773. minexptime = 0.5,
  774. maxexptime = 1.5,
  775. minsize = 1,
  776. maxsize = 1.5,
  777. collisiondetection = false,
  778. texture = "nether_particle_anim4.png",
  779. animation = {
  780. type = "vertical_frames",
  781. aspect_w = 7,
  782. aspect_h = 7,
  783. -- Disabled for now due to causing older clients to hang.
  784. --length = -1,
  785. length = 0.3,
  786. },
  787. glow = 14,
  788. })
  789. end
  790. -- Spawn particles over every teleport that's near a player.
  791. local ports = teleports.teleports
  792. local tlen = #ports
  793. local plen = #players
  794. for k = 1, tlen, 1 do
  795. local porthub = ports[k]
  796. local portpos = porthub.pos
  797. for i = 1, plen, 1 do
  798. local pref = players[i]
  799. local playerpos = pref:get_pos()
  800. local dist = vector_distance(portpos, playerpos)
  801. -- Don't add particles for the initiating player above the teleport they
  802. -- are actually using (but spawn particles for them over any nearby).
  803. if dist < 32 and (pref ~= initiating_player or dist > 2) then
  804. ping(portpos)
  805. if math_random(1, 500) == 1 then
  806. minetest.after(math_random(1, 5), function()
  807. pm.spawn_random_wisp(vector_add(portpos, {x=0, y=1, z=0}))
  808. end)
  809. end
  810. end
  811. end
  812. end
  813. end
  814. teleports.on_punch = function(pos, node, puncher, pointed_thing)
  815. teleports.update(pos)
  816. -- Maybe this is a bit too spammy and generally unnecessary?
  817. if puncher and puncher:is_player() then
  818. minetest.chat_send_player(puncher:get_player_name(), "# Server: This machine has been updated.")
  819. end
  820. end
  821. teleports.on_diamond_place = function(itemstack, placer, pointed_thing)
  822. local stack = ItemStack("default:diamondblock")
  823. local pos = pointed_thing.above
  824. local name = "default:diamondblock"
  825. if minetest.get_node({x=pos.x+1,y=pos.y,z=pos.z}).name == name and
  826. minetest.get_node({x=pos.x+1,y=pos.y,z=pos.z+1}).name == name and
  827. minetest.get_node({x=pos.x+1,y=pos.y,z=pos.z-1}).name == name and
  828. minetest.get_node({x=pos.x-1,y=pos.y,z=pos.z}).name == name and
  829. minetest.get_node({x=pos.x-1,y=pos.y,z=pos.z+1}).name == name and
  830. minetest.get_node({x=pos.x-1,y=pos.y,z=pos.z-1}).name == name and
  831. minetest.get_node({x=pos.x,y=pos.y,z=pos.z+1}).name == name and
  832. minetest.get_node({x=pos.x,y=pos.y,z=pos.z-1}).name == name
  833. then
  834. stack = ItemStack("teleports:teleport")
  835. end
  836. local ret = minetest.item_place(stack, placer, pointed_thing)
  837. if ret == nil then
  838. return itemstack
  839. else
  840. return ItemStack("default:diamondblock " .. itemstack:get_count() - (1 - ret:get_count()))
  841. end
  842. end
  843. -- Admin API function, refills ALL teleports with fuel (if fuel is empty).
  844. function teleports.refill_all()
  845. local tps = teleports.teleports
  846. for k, v in ipairs(tps) do
  847. local meta = minetest.get_meta(v.pos)
  848. if meta then
  849. local inv = meta:get_inventory()
  850. if inv then
  851. if inv:is_empty("price") then
  852. inv:set_stack("price", 1, ItemStack("flowers:waterlily 64"))
  853. else
  854. local stack = inv:get_stack("price", 1)
  855. if stack:get_name() == "default:mossycobble" then
  856. stack:set_count(64)
  857. inv:set_stack("price", 1, stack)
  858. elseif stack:get_name() == "flowers:waterlily" then
  859. stack:set_count(64)
  860. inv:set_stack("price", 1, stack)
  861. end
  862. end
  863. end
  864. end
  865. end
  866. end