init.lua 25 KB

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