functions.lua 34 KB

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