functions.lua 29 KB

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