gate.lua 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. -- Localize for performance.
  2. local vector_distance = vector.distance
  3. local vector_round = vector.round
  4. local math_random = math.random
  5. -- Gateway schematic.
  6. local o = {"default:obsidian", "griefer:grieferstone", "cavestuff:dark_obsidian", "cavestuff:glow_obsidian"}
  7. local a = function(name)
  8. -- Bones check needed here to prevent players from blocking a gate by dying in
  9. -- it. E.g., bones never decay while a hacker is online.
  10. if name == "air" or name == "bones:bones" then
  11. return true
  12. end
  13. if name == "nether:portal_liquid" or name == "nether:portal_hidden" then
  14. return true
  15. end
  16. end
  17. local gate_northsouth = {
  18. data = {
  19. {p={x=0, y=0, z=0}, n=o},
  20. {p={x=1, y=0, z=0}, n=o},
  21. {p={x=2, y=0, z=0}, n=o},
  22. {p={x=3, y=0, z=0}, n=o},
  23. {p={x=0, y=1, z=0}, n=o},
  24. {p={x=0, y=2, z=0}, n=o},
  25. {p={x=0, y=3, z=0}, n=o},
  26. {p={x=0, y=4, z=0}, n=o},
  27. {p={x=1, y=4, z=0}, n=o},
  28. {p={x=2, y=4, z=0}, n=o},
  29. {p={x=3, y=4, z=0}, n=o},
  30. {p={x=3, y=1, z=0}, n=o},
  31. {p={x=3, y=2, z=0}, n=o},
  32. {p={x=3, y=3, z=0}, n=o},
  33. {p={x=1, y=1, z=0}, n=a},
  34. {p={x=2, y=1, z=0}, n=a},
  35. {p={x=1, y=2, z=0}, n=a},
  36. {p={x=2, y=2, z=0}, n=a},
  37. {p={x=1, y=3, z=0}, n=a},
  38. {p={x=2, y=3, z=0}, n=a},
  39. },
  40. minp = {x=0, y=0, z=0},
  41. maxp = {x=3, y=4, z=0},
  42. }
  43. local gate_eastwest = {
  44. data = {
  45. {p={x=0, y=0, z=0}, n=o},
  46. {p={x=0, y=0, z=1}, n=o},
  47. {p={x=0, y=0, z=2}, n=o},
  48. {p={x=0, y=0, z=3}, n=o},
  49. {p={x=0, y=1, z=0}, n=o},
  50. {p={x=0, y=2, z=0}, n=o},
  51. {p={x=0, y=3, z=0}, n=o},
  52. {p={x=0, y=4, z=0}, n=o},
  53. {p={x=0, y=4, z=1}, n=o},
  54. {p={x=0, y=4, z=2}, n=o},
  55. {p={x=0, y=4, z=3}, n=o},
  56. {p={x=0, y=1, z=3}, n=o},
  57. {p={x=0, y=2, z=3}, n=o},
  58. {p={x=0, y=3, z=3}, n=o},
  59. {p={x=0, y=1, z=1}, n=a},
  60. {p={x=0, y=1, z=2}, n=a},
  61. {p={x=0, y=2, z=1}, n=a},
  62. {p={x=0, y=2, z=2}, n=a},
  63. {p={x=0, y=3, z=1}, n=a},
  64. {p={x=0, y=3, z=2}, n=a},
  65. },
  66. minp = {x=0, y=0, z=0},
  67. maxp = {x=0, y=4, z=3},
  68. }
  69. obsidian_gateway.gate_ns_data = gate_northsouth
  70. obsidian_gateway.gate_ew_data = gate_eastwest
  71. -- Quickly check for protection in an area.
  72. local function check_protection(pos, radius)
  73. -- How much beyond the radius to check for protections.
  74. local e = 3
  75. local minp = vector.new(pos.x-(radius+e), pos.y-(radius+e), pos.z-(radius+e))
  76. local maxp = vector.new(pos.x+(radius+e), pos.y+(radius+e), pos.z+(radius+e))
  77. -- Step size, to avoid checking every single node.
  78. -- This assumes protections cannot be smaller than this size.
  79. local ss = 3
  80. local check = minetest.test_protection
  81. for x=minp.x, maxp.x, ss do
  82. for y=minp.y, maxp.y, ss do
  83. for z=minp.z, maxp.z, ss do
  84. if check({x=x, y=y, z=z}, "") then
  85. -- Protections are present.
  86. return true
  87. end
  88. end
  89. end
  90. end
  91. -- Nothing in the area is protected.
  92. return false
  93. end
  94. -- Get a list of node positions inside the gate's frame.
  95. function obsidian_gateway.door_positions(origin, northsouth)
  96. local airpoints
  97. if northsouth then
  98. local o = origin
  99. airpoints = {
  100. {x=o.x+1, y=o.y+1, z=o.z+0},
  101. {x=o.x+2, y=o.y+1, z=o.z+0},
  102. {x=o.x+1, y=o.y+2, z=o.z+0},
  103. {x=o.x+2, y=o.y+2, z=o.z+0},
  104. {x=o.x+1, y=o.y+3, z=o.z+0},
  105. {x=o.x+2, y=o.y+3, z=o.z+0},
  106. }
  107. else
  108. local o = origin
  109. airpoints = {
  110. {x=o.x+0, y=o.y+1, z=o.z+1},
  111. {x=o.x+0, y=o.y+1, z=o.z+2},
  112. {x=o.x+0, y=o.y+2, z=o.z+1},
  113. {x=o.x+0, y=o.y+2, z=o.z+2},
  114. {x=o.x+0, y=o.y+3, z=o.z+1},
  115. {x=o.x+0, y=o.y+3, z=o.z+2},
  116. }
  117. end
  118. return airpoints
  119. end
  120. function obsidian_gateway.spawn_liquid(origin, northsouth, returngate, force)
  121. local color = 6
  122. local rotation = 1
  123. local str_origin = minetest.pos_to_string(origin)
  124. if northsouth then
  125. rotation = 0
  126. end
  127. if returngate then
  128. color = 5
  129. end
  130. -- Node's drawtype is "colorfacedir".
  131. local node = {
  132. name = "nether:portal_liquid",
  133. param2 = (color * 32 + rotation),
  134. }
  135. local vadd = vector.add
  136. local airpoints = obsidian_gateway.door_positions(origin, northsouth)
  137. local spawned = false
  138. local count = #airpoints
  139. for k = 1, count, 1 do
  140. local p = airpoints[k]
  141. local oldnode = minetest.get_node(p)
  142. -- Make sure node to be replaced is air. If we were to overwrite any
  143. -- existing portal liquid, that would cause callbacks to run, which would
  144. -- interfere with what we're doing here.
  145. if oldnode.name == "air" then
  146. -- Run 'on_construct' callbacks, etc.
  147. minetest.set_node(p, node)
  148. -- This tells the particle code (inside node's on_timer) what color to use.
  149. local meta = minetest.get_meta(p)
  150. if returngate then
  151. meta:set_string("color", "red")
  152. else
  153. meta:set_string("color", "gold")
  154. end
  155. meta:set_string("gate_origin", str_origin)
  156. meta:set_string("gate_northsouth", tostring(northsouth))
  157. meta:mark_as_private({"color", "gate_origin", "gate_northsouth"})
  158. spawned = true
  159. elseif force and oldnode.name == "nether:portal_hidden" then
  160. minetest.swap_node(p, node)
  161. -- Manually execute callback.
  162. local ndef = minetest.registered_nodes[node.name]
  163. ndef.on_construct(p)
  164. -- Do not need to set "color" meta here, the hidden node already has it.
  165. -- Same with 'gate_origin'.
  166. spawned = true
  167. end
  168. end
  169. if spawned then
  170. ambiance.sound_play("nether_portal_ignite", origin, 1.0, 64)
  171. end
  172. end
  173. -- Determine whether the gateway has active portal liquid.
  174. function obsidian_gateway.have_liquid(origin, northsouth)
  175. local airpoints = obsidian_gateway.door_positions(origin, northsouth)
  176. local total = 0
  177. local count = #airpoints
  178. for k = 1, count, 1 do
  179. local p = airpoints[k]
  180. local node = minetest.get_node(p)
  181. if node.name == "nether:portal_liquid" then
  182. total = total + 1
  183. end
  184. end
  185. return (total == 6)
  186. end
  187. -- Get gate's origin and northsouth/eastwest orientation.
  188. function obsidian_gateway.get_origin_and_dir(pos)
  189. local meta = minetest.get_meta(pos)
  190. local str_origin = meta:get_string("gate_origin")
  191. local str_northsouth = meta:get_string("gate_northsouth")
  192. meta:mark_as_private({"gate_origin", "gate_northsouth"})
  193. local origin = minetest.string_to_pos(str_origin)
  194. if origin then
  195. -- Returns origin, true/false.
  196. return origin, (str_northsouth == "true")
  197. end
  198. end
  199. function obsidian_gateway.remove_liquid(pos, points)
  200. local node = {name="air"}
  201. local removed = false
  202. local count = #points
  203. for k = 1, count, 1 do
  204. local v = points[k]
  205. local n = minetest.get_node(v)
  206. if n.name == "nether:portal_liquid" then
  207. -- Must use 'swap_node' to avoid triggering further callbacks on the
  208. -- portal liquid node (and nearby portal liquid nodes).
  209. minetest.swap_node(v, node)
  210. removed = true
  211. elseif n.name == "nether:portal_hidden" then
  212. minetest.swap_node(v, node)
  213. -- Node is hidden, so do not set 'removed' flag (removal makes no sound).
  214. end
  215. end
  216. if removed then
  217. ambiance.sound_play("nether_portal_extinguish", pos, 1.0, 64)
  218. end
  219. end
  220. function obsidian_gateway.regenerate_liquid(target, northsouth)
  221. local success, so, ap, ns, key, po =
  222. obsidian_gateway.find_gate(target, northsouth)
  223. -- Spawn portal liquid only if there is a gate here with the expected
  224. -- orientation. Force liquid placement over hidden portal nodes.
  225. if success then
  226. local meta = minetest.get_meta(so)
  227. local isreturngate = (meta:get_int("obsidian_gateway_return_gate_" .. key) == 1)
  228. obsidian_gateway.spawn_liquid(so, ns, isreturngate, true)
  229. end
  230. end
  231. function obsidian_gateway.find_gate(pos, require_ns)
  232. local result
  233. local points
  234. local counts
  235. local origin
  236. local northsouth
  237. local ns_key
  238. local playerorigin
  239. -- Find the gateway (threshold under player)!
  240. result, points, counts, origin =
  241. schematic_find.detect_schematic(pos, gate_northsouth)
  242. northsouth = true
  243. ns_key = "ns"
  244. if result then
  245. playerorigin = vector.add(origin, {x=1, y=1, z=0})
  246. end
  247. if not result then
  248. -- Couldn't find northsouth gateway, so try to find eastwest.
  249. result, points, counts, origin =
  250. schematic_find.detect_schematic(pos, gate_eastwest)
  251. northsouth = false
  252. ns_key = "ew"
  253. if result then
  254. playerorigin = vector.add(origin, {x=0, y=1, z=1})
  255. end
  256. end
  257. -- Early exit.
  258. if not result then
  259. return
  260. end
  261. -- If a specific orientation is required, then check that.
  262. if require_ns ~= nil then
  263. if northsouth ~= require_ns then
  264. return
  265. end
  266. end
  267. -- Store locations of air/portal nodes inside the gateway.
  268. local airpoints = {}
  269. if result then
  270. for k, v in ipairs(points) do
  271. local nn = minetest.get_node(v).name
  272. if nn == "air" or nn == "nether:portal_liquid" or
  273. nn == "nether:portal_hidden" then
  274. airpoints[#airpoints+1] = v
  275. end
  276. end
  277. end
  278. -- Did we find a working gateway?
  279. local yes = false
  280. if result then
  281. local o = counts["default:obsidian"] or 0
  282. local d = counts["cavestuff:dark_obsidian"] or 0
  283. local c = counts["cavestuff:glow_obsidian"] or 0
  284. local g = counts["griefer:grieferstone"] or 0
  285. local a = (#airpoints == 6)
  286. if (o + d + c) == 12 and g == 2 and a == true then
  287. yes = true
  288. end
  289. end
  290. if yes then
  291. return true, origin, airpoints, northsouth, ns_key, playerorigin
  292. end
  293. end
  294. function obsidian_gateway.get_gate_player_spawn_pos(pos, dir)
  295. if dir == "ns" then
  296. return vector.add(pos, {x=1.5, y=1.5, z=0})
  297. elseif dir == "ew" then
  298. return vector.add(pos, {x=0, y=1.5, z=1.5})
  299. else
  300. return pos
  301. end
  302. end
  303. function obsidian_gateway.attempt_activation(pos, player, itemstring)
  304. local pname = player:get_player_name()
  305. local ppos = vector_round(player:get_pos())
  306. local under = utility.node_under_pos(player:get_pos())
  307. local inside = vector.add(under, {x=0, y=1, z=0})
  308. local nodeunder = minetest.get_node(under).name
  309. -- Player must be standing on one of these.
  310. if nodeunder ~= "default:obsidian" and
  311. nodeunder ~= "griefer:grieferstone" and
  312. nodeunder ~= "cavestuff:dark_obsidian" and
  313. nodeunder ~= "cavestuff:glow_obsidian" then
  314. -- This triggers when other types of portals are used, so is incorrect to display this chat.
  315. --minetest.chat_send_player(pname, "# Server: You need to be standing in the gateway for it to work!")
  316. return
  317. end
  318. local success
  319. local origin
  320. local northsouth
  321. local ns_key
  322. local playerorigin
  323. local airpoints
  324. success, origin, airpoints, northsouth, ns_key, playerorigin =
  325. obsidian_gateway.find_gate(pos)
  326. if not success then
  327. return
  328. end
  329. -- Add/update sound beacon.
  330. ambiance.spawn_sound_beacon("soundbeacon:gate", origin, 20, 1)
  331. ambiance.replay_nearby_sound_beacons(origin, 6)
  332. if sheriff.is_cheater(pname) then
  333. if sheriff.punish_probability(pname) then
  334. sheriff.punish_player(pname)
  335. return
  336. end
  337. end
  338. local target
  339. local meta = minetest.get_meta(origin)
  340. -- By spliting the key names by ns/ew, I ensure connected portals don't
  341. -- stomp on each other's data.
  342. target = minetest.string_to_pos(meta:get_string("obsidian_gateway_destination_" .. ns_key))
  343. meta:mark_as_private("obsidian_gateway_destination_" .. ns_key)
  344. --if not target then
  345. -- minetest.chat_send_player(pname, "# Server: Gateway has no destination! Aborting.")
  346. -- return
  347. --end
  348. -- If activating the gate in the OUTBACK, and player previously died in
  349. -- MIDFELD, send them back to MIDFELD, do NOT send them to the overworld.
  350. if rc.current_realm_at_pos(origin) == "abyss" then
  351. if player:get_meta():get_int("abyss_return_midfeld") == 1 then
  352. target = obsidian_gateway.get_midfeld_spawn()
  353. end
  354. end
  355. -- Gates CANNOT be initialized in the Abyss!
  356. -- (Only the outgoing realm-gate is useable.)
  357. -- This prevents players from building their own gates in the Abyss.
  358. if not target and rc.current_realm_at_pos(origin) == "abyss" then
  359. minetest.after(0, function()
  360. -- Detonate some TNT!
  361. tnt.boom(vector.add(ppos, {x=math_random(-3, 3), y=0, z=math_random(-3, 3)}), {
  362. radius = 3,
  363. ignore_protection = false,
  364. ignore_on_blast = false,
  365. damage_radius = 5,
  366. disable_drops = true,
  367. })
  368. end)
  369. return
  370. end
  371. local isreturngate = (meta:get_int("obsidian_gateway_return_gate_" .. ns_key) == 1)
  372. local actual_owner = meta:get_string("obsidian_gateway_owner_" .. ns_key)
  373. local isowner = (actual_owner == pname)
  374. local first_time_init = false
  375. minetest.log("action", pname .. " activated gateway @ " .. minetest.pos_to_string(pos))
  376. -- Initialize gateway for the first time.
  377. if itemstring == "pearl" then
  378. if not target or (meta:get_string("obsidian_gateway_success_" .. ns_key) ~= "yes" and not isreturngate) then
  379. -- Target is valid then this could be an OLD gate with old metadata.
  380. -- This can ALSO happen if player initializes a new gate twice or more times before
  381. -- the first initialization completes.
  382. if target and not isreturngate and meta:get_string("obsidian_gateway_success_" .. ns_key) == "" then
  383. minetest.chat_send_player(pname, "# Server: It looks like this could possibly be an OLD gate! Aborting for safety reasons.")
  384. minetest.chat_send_player(pname, "# Server: If this Gateway was previously functioning normally, please mail the admin with the coordinates.")
  385. minetest.chat_send_player(pname, "# Server: If this is a Gate that you have just constructed, you can safely ignore this message.")
  386. minetest.chat_send_player(pname, "# Server: The Gateway's EXIT location is @ " .. rc.pos_to_namestr(target) .. ".")
  387. minetest.after(1.5, function() easyvend.sound_error(pname) end)
  388. return
  389. end
  390. -- Algorithm for locating the destination.
  391. -- Get a potential gate location.
  392. target = rc.get_random_realm_gate_position(pname, origin)
  393. -- Is target outside bounds?
  394. local bad = function(target, origin)
  395. -- Handle nil.
  396. if not target then
  397. return true
  398. end
  399. -- Don't allow exit points near the colonies.
  400. if vector_distance(target, {x=0, y=0, z=0}) < 1000 or
  401. vector_distance(target, {x=0, y=-30790, z=0}) < 1000 then
  402. return true
  403. end
  404. -- Exit must not be too close to start.
  405. if vector_distance(target, origin) < 100 then
  406. return true
  407. end
  408. -- Or too far.
  409. -- This causes too many failures.
  410. -- Note: this is now handled by the 'rc' mod.
  411. --if vector_distance(target, origin) > 7000 then
  412. -- return true
  413. --end
  414. if not rc.is_valid_gateway_region(target) then
  415. return true
  416. end
  417. end
  418. -- Keep trying until the target is within bounds.
  419. local num_tries = 0
  420. while bad(target, origin) do
  421. target = rc.get_random_realm_gate_position(pname, origin)
  422. num_tries = num_tries + 1
  423. -- Max 3 tries.
  424. if num_tries >= 2 then
  425. ---[[
  426. minetest.after(0, function()
  427. -- Detonate some TNT!
  428. tnt.boom(vector.add(ppos, {x=math_random(-3, 3), y=0, z=math_random(-3, 3)}), {
  429. radius = 3,
  430. ignore_protection = false,
  431. ignore_on_blast = false,
  432. damage_radius = 5,
  433. disable_drops = true,
  434. })
  435. end)
  436. --]]
  437. return
  438. end
  439. end
  440. meta:set_string("obsidian_gateway_destination_" .. ns_key, minetest.pos_to_string(target))
  441. meta:set_string("obsidian_gateway_owner_" .. ns_key, pname)
  442. meta:mark_as_private({
  443. "obsidian_gateway_destination_" .. ns_key,
  444. "obsidian_gateway_owner_" .. ns_key
  445. })
  446. first_time_init = true
  447. isowner = true
  448. else
  449. -- Used a pearl but gate already activated.
  450. return
  451. end
  452. end -- Itemstring is "pearl".
  453. -- Happens if gate is not initialized and we didn't use a pearl to activate it.
  454. if not target then
  455. return
  456. end
  457. -- Event horizon color depends on whether we are a return gate.
  458. obsidian_gateway.spawn_liquid(origin, northsouth, isreturngate)
  459. if gdac.player_is_admin(pname) then
  460. isowner = true
  461. end
  462. -- Let everyone use gates owned by the admin.
  463. if minetest.get_player_privs(actual_owner).server then
  464. isowner = true
  465. end
  466. -- Slightly randomize player's exit coordinates.
  467. -- Without changing the coordinates of the gateway.
  468. local pdest
  469. if northsouth then
  470. pdest = vector.add(target, {x=math_random(0, 1), y=0, z=0})
  471. else
  472. pdest = vector.add(target, {x=0, y=0, z=math_random(0, 1)})
  473. end
  474. pdest = vector_round(pdest)
  475. -- Make sure target is within some realm.
  476. -- This generally should not happen.
  477. if not rc.is_valid_realm_pos(pdest) then
  478. return
  479. end
  480. -- Collect any friends to bring along.
  481. local friendstobring = {}
  482. local allplayers = minetest.get_connected_players()
  483. for k, v in ipairs(allplayers) do
  484. if v:get_player_name() ~= pname then
  485. if vector_distance(v:get_pos(), player:get_pos()) < 3 then
  486. friendstobring[#friendstobring+1] = v:get_player_name()
  487. end
  488. end
  489. end
  490. portal_cb.call_before_use({
  491. gate_origin = origin,
  492. gate_orientation = ns_key, -- "ns" or "ew"
  493. player_name = pname,
  494. teleport_destination = table.copy(pdest),
  495. })
  496. -- Create a gateway at the player's destination.
  497. -- This gateway links back to the first.
  498. -- If it is destroyed, the player is stuck!
  499. preload_tp.execute({
  500. player_name = pname,
  501. target_position = pdest,
  502. emerge_radius = 32,
  503. particle_effects = true,
  504. -- Force teleport on first init.
  505. -- This should reduce problems due to the player moving around and canceling
  506. -- the teleport on a new gate.
  507. force_teleport = first_time_init,
  508. pre_teleport_callback = function()
  509. -- Cancel teleport if origin gate does not have portal liquid.
  510. if not obsidian_gateway.have_liquid(origin, northsouth) then
  511. minetest.chat_send_player(pname, "# Server: Portal disrupted.")
  512. -- Cancel transport.
  513. return true
  514. end
  515. -- Don't build return portal on top of someone's protected stuff.
  516. if first_time_init then
  517. if check_protection(vector.add(target, {x=0, y=3, z=0}), 5) then
  518. minetest.chat_send_player(pname, "# Server: Return-gate construction FAILED due to protection near " .. rc.pos_to_namestr(target) .. ".")
  519. -- Clear data for the initial gate. This will permit the player to retry without tearing everything down and building it again.
  520. local meta = minetest.get_meta(origin)
  521. meta:set_string("obsidian_gateway_success_" .. ns_key, "")
  522. meta:set_string("obsidian_gateway_destination_" .. ns_key, "")
  523. meta:set_string("obsidian_gateway_owner_" .. ns_key, "")
  524. meta:mark_as_private({
  525. "obsidian_gateway_success_" .. ns_key,
  526. "obsidian_gateway_destination_" .. ns_key,
  527. "obsidian_gateway_owner_" .. ns_key
  528. })
  529. -- Cancel transport.
  530. return true
  531. end
  532. end
  533. -- Build return portal (only if not already using a return portal).
  534. -- Also, only build return portal on first use of the initial portal.
  535. if not isreturngate and first_time_init then
  536. if northsouth then
  537. -- Place northsouth gateway.
  538. local path = obsidian_gateway.modpath .. "/obsidian_gateway_northsouth.mts"
  539. local gpos = vector.add(target, {x=-1, y=-1, z=0})
  540. minetest.place_schematic(gpos, path, "0", nil, true)
  541. local meta = minetest.get_meta(gpos)
  542. meta:set_string("obsidian_gateway_destination_" .. ns_key, minetest.pos_to_string(playerorigin))
  543. meta:set_string("obsidian_gateway_owner_" .. ns_key, pname)
  544. meta:set_int("obsidian_gateway_return_gate_" .. ns_key, 1)
  545. meta:mark_as_private({
  546. "obsidian_gateway_destination_" .. ns_key,
  547. "obsidian_gateway_owner_" .. ns_key,
  548. "obsidian_gateway_return_gate_" .. ns_key
  549. })
  550. else
  551. -- Place eastwest gateway.
  552. local path = obsidian_gateway.modpath .. "/obsidian_gateway_eastwest.mts"
  553. local gpos = vector.add(target, {x=0, y=-1, z=-1})
  554. minetest.place_schematic(gpos, path, "0", nil, true)
  555. local meta = minetest.get_meta(gpos)
  556. meta:set_string("obsidian_gateway_destination_" .. ns_key, minetest.pos_to_string(playerorigin))
  557. meta:set_string("obsidian_gateway_owner_" .. ns_key, pname)
  558. meta:set_int("obsidian_gateway_return_gate_" .. ns_key, 1)
  559. meta:mark_as_private({
  560. "obsidian_gateway_destination_" .. ns_key,
  561. "obsidian_gateway_owner_" .. ns_key,
  562. "obsidian_gateway_return_gate_" .. ns_key
  563. })
  564. end
  565. end
  566. -- Mark the initial gate as success.
  567. -- If this is not done, then gate will assume it is not initialized
  568. -- the next time it is used. This fixes a bug where the return gate is
  569. -- not properly constructed if the player moves during transport
  570. -- (because this callback function doesn't get called).
  571. if not isreturngate and first_time_init then
  572. local meta = minetest.get_meta(origin)
  573. meta:set_string("obsidian_gateway_success_" .. ns_key, "yes")
  574. meta:mark_as_private("obsidian_gateway_success_" .. ns_key)
  575. end
  576. -- If the destination is the Abyss, then kill player first.
  577. -- This helps to prevent player from bringing any foreign items into this realm.
  578. -- Note: this relies on the teleport code already checking all other preconditions
  579. -- first. I.e., if this callback returns 'false', then the player absolutely
  580. -- will be teleported.
  581. if rc.current_realm_at_pos(pdest) == "abyss" then
  582. -- Dump player bones, as if they died.
  583. -- This should behave exactly as if the player died, with the exception of
  584. -- setting the player's health to 0.
  585. bones.dump_bones(pname, true)
  586. local pref = minetest.get_player_by_name(pname)
  587. pref:set_hp(pova.get_active_modifier(pref, "properties").hp_max)
  588. pref:get_meta():set_string("last_death_pos", "") -- Fake death.
  589. give_initial_stuff.give(pref)
  590. end
  591. -- Always regenerate portal liquid in the destination portal.
  592. -- (It will often be missing since no one was near it.)
  593. -- This function will check if there actually is a gate, here.
  594. obsidian_gateway.regenerate_liquid(target, northsouth)
  595. -- If the player is someone other than the owner, using this Gate has consequences.
  596. if not isowner then
  597. -- This function is already called normally, when a Gate is used.
  598. -- Calling it again here, effectively doubles the chance that the user
  599. -- starts feeling rather ill.
  600. portal_sickness.on_use_portal(pname)
  601. end
  602. end,
  603. post_teleport_callback = function()
  604. portal_cb.call_after_use({
  605. gate_origin = origin,
  606. gate_orientation = ns_key, -- "ns" or "ew"
  607. player_name = pname,
  608. teleport_destination = table.copy(pdest),
  609. })
  610. -- Any others in area get brought along, too.
  611. for k, v in ipairs(friendstobring) do
  612. local friend = minetest.get_player_by_name(v)
  613. if friend then
  614. local fname = friend:get_player_name()
  615. preload_tp.execute({
  616. player_name = fname,
  617. target_position = pdest,
  618. particle_effects = true,
  619. pre_teleport_callback = function()
  620. -- If the destination is the Abyss, then kill player first.
  621. -- Note: this relies on the teleport code already checking all other preconditions
  622. -- first. I.e., if this callback returns 'false', then the player absolutely
  623. -- will be teleported.
  624. if rc.current_realm_at_pos(pdest) == "abyss" then
  625. -- Dump player bones, as if they died.
  626. -- This should behave exactly as if the player died, with the exception of
  627. -- setting the player's health to 0.
  628. bones.dump_bones(fname, true)
  629. local pref = minetest.get_player_by_name(fname)
  630. pref:set_hp(pova.get_active_modifier(pref, "properties").hp_max)
  631. pref:get_meta():set_string("last_death_pos", "") -- Fake death.
  632. give_initial_stuff.give(pref)
  633. end
  634. end,
  635. force_teleport = true,
  636. send_blocks = true,
  637. })
  638. portal_sickness.on_use_portal(fname)
  639. end
  640. end
  641. -- Update liquids around on first init.
  642. if first_time_init then
  643. minetest.after(2, function()
  644. mapfix.execute(target, 10)
  645. end)
  646. end
  647. ambiance.spawn_sound_beacon("soundbeacon:gate", target, 20, 1)
  648. ambiance.replay_nearby_sound_beacons(target, 6)
  649. portal_sickness.on_use_portal(pname)
  650. -- Clear player's "died in MIDFELD" flag, once transport to MIDFELD succeeded.
  651. if rc.current_realm_at_pos(target) == "midfeld" then
  652. local pref = minetest.get_player_by_name(pname)
  653. if pref then
  654. pref:get_meta():set_int("abyss_return_midfeld", 0)
  655. end
  656. end
  657. end,
  658. teleport_sound = "nether_portal_usual",
  659. send_blocks = true,
  660. })
  661. return true
  662. end
  663. -- To be called inside node's 'on_destruct' callback.
  664. -- Note: 'transient' is ONLY true when node to be destructed is portal liquid!
  665. function obsidian_gateway.on_damage_gate(pos, transient)
  666. -- First, perform some cheap checks to see if there could possibly be a gate
  667. -- at this location. We only perform the expensive checks if the cheap checks
  668. -- pass!
  669. local minp = vector.add(pos, {x=-4, y=-4, z=-4})
  670. local maxp = vector.add(pos, {x=4, y=4, z=4})
  671. local names = {
  672. "default:obsidian",
  673. "cavestuff:dark_obsidian",
  674. "cavestuff:glow_obsidian",
  675. "griefer:grieferstone",
  676. "nether:portal_liquid",
  677. "nether:portal_hidden",
  678. }
  679. local points, counts = minetest.find_nodes_in_area(minp, maxp, names)
  680. if #points == 0 then
  681. return
  682. end
  683. local doorpoints = points
  684. -- Remove all portal-liquid nodes. (Will play sound if any removed.)
  685. -- First, try to get gate origin from meta. If this fails, then we use the
  686. -- 'points' array as a fallback (old behavior).
  687. local origin, northsouth = obsidian_gateway.get_origin_and_dir(pos)
  688. if origin then
  689. doorpoints = obsidian_gateway.door_positions(origin, northsouth)
  690. end
  691. minetest.after(0, obsidian_gateway.remove_liquid, pos, doorpoints)
  692. -- If transient "pop" of portal liquid nodes, then do not continue further to
  693. -- actually damage the gate.
  694. if transient then
  695. return
  696. end
  697. -- A valid gate requires exactly 2 oerkki stone.
  698. -- (There may be additional oerkki stone not part of the gate.)
  699. if counts["griefer:grieferstone"] < 2 then
  700. return
  701. end
  702. do
  703. local o = counts["default:obsidian"] or 0
  704. local d = counts["cavestuff:dark_obsidian"] or 0
  705. local c = counts["cavestuff:glow_obsidian"] or 0
  706. -- Including the node that will be removed (we should have been called
  707. -- inside of 'on_destruct' for a given node), there should be 12 obsidian
  708. -- remaining, otherwise cannot be a valid gate. (There may be additional
  709. -- obsidian nearby not part of the gate.)
  710. if (o + d + c) < 12 then
  711. return
  712. end
  713. end
  714. -- Cheap checks completed, now do the expensive check.
  715. if not obsidian_gateway.find_gate(pos) then
  716. return
  717. end
  718. -- If we reach here, we know we have a valid gate attached to this position.
  719. -- We don't care if it is a NS or EW-facing gate.
  720. -- This has a chance to destroy one of the oerkki stones, which costs some
  721. -- resources to craft again. But don't spawn lava in overworld. The point of
  722. -- this is to make it a bit more costly to constantly reset the gate if you
  723. -- don't like where it goes. Note: using 'swap_node' first in order to prevent
  724. -- calling additional callbacks.
  725. local idx = math.random(1, #points)
  726. local tar = points[idx]
  727. minetest.swap_node(tar, {name="air"})
  728. minetest.set_node(tar, {name="fire:basic_flame"})
  729. ambiance.sound_play("nether_rack_destroy", pos, 1.0, 64)
  730. end