init.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. obsidian_gateway = obsidian_gateway or {}
  2. obsidian_gateway.modpath = minetest.get_modpath("obsidian_gateway")
  3. -- Gateway schematic.
  4. local o = {"default:obsidian", "griefer:grieferstone", "cavestuff:dark_obsidian", "cavestuff:glow_obsidian"}
  5. local a = function(name)
  6. if name == "air" then
  7. return true
  8. end
  9. end
  10. local gate_northsouth = {
  11. data = {
  12. {p={x=0, y=0, z=0}, n=o},
  13. {p={x=1, y=0, z=0}, n=o},
  14. {p={x=2, y=0, z=0}, n=o},
  15. {p={x=3, y=0, z=0}, n=o},
  16. {p={x=0, y=1, z=0}, n=o},
  17. {p={x=0, y=2, z=0}, n=o},
  18. {p={x=0, y=3, z=0}, n=o},
  19. {p={x=0, y=4, z=0}, n=o},
  20. {p={x=1, y=4, z=0}, n=o},
  21. {p={x=2, y=4, z=0}, n=o},
  22. {p={x=3, y=4, z=0}, n=o},
  23. {p={x=3, y=1, z=0}, n=o},
  24. {p={x=3, y=2, z=0}, n=o},
  25. {p={x=3, y=3, z=0}, n=o},
  26. {p={x=1, y=1, z=0}, n=a},
  27. {p={x=2, y=1, z=0}, n=a},
  28. {p={x=1, y=2, z=0}, n=a},
  29. {p={x=2, y=2, z=0}, n=a},
  30. {p={x=1, y=3, z=0}, n=a},
  31. {p={x=2, y=3, z=0}, n=a},
  32. },
  33. minp = {x=0, y=0, z=0},
  34. maxp = {x=3, y=4, z=0},
  35. }
  36. local gate_eastwest = {
  37. data = {
  38. {p={x=0, y=0, z=0}, n=o},
  39. {p={x=0, y=0, z=1}, n=o},
  40. {p={x=0, y=0, z=2}, n=o},
  41. {p={x=0, y=0, z=3}, n=o},
  42. {p={x=0, y=1, z=0}, n=o},
  43. {p={x=0, y=2, z=0}, n=o},
  44. {p={x=0, y=3, z=0}, n=o},
  45. {p={x=0, y=4, z=0}, n=o},
  46. {p={x=0, y=4, z=1}, n=o},
  47. {p={x=0, y=4, z=2}, n=o},
  48. {p={x=0, y=4, z=3}, n=o},
  49. {p={x=0, y=1, z=3}, n=o},
  50. {p={x=0, y=2, z=3}, n=o},
  51. {p={x=0, y=3, z=3}, n=o},
  52. {p={x=0, y=1, z=1}, n=a},
  53. {p={x=0, y=1, z=2}, n=a},
  54. {p={x=0, y=2, z=1}, n=a},
  55. {p={x=0, y=2, z=2}, n=a},
  56. {p={x=0, y=3, z=1}, n=a},
  57. {p={x=0, y=3, z=2}, n=a},
  58. },
  59. minp = {x=0, y=0, z=0},
  60. maxp = {x=0, y=4, z=3},
  61. }
  62. obsidian_gateway.gate_ns_data = gate_northsouth
  63. obsidian_gateway.gate_ew_data = gate_eastwest
  64. -- Quickly check for protection in an area.
  65. local function check_protection(pos, radius)
  66. -- How much beyond the radius to check for protections.
  67. local e = 3
  68. local minp = vector.new(pos.x-(radius+e), pos.y-(radius+e), pos.z-(radius+e))
  69. local maxp = vector.new(pos.x+(radius+e), pos.y+(radius+e), pos.z+(radius+e))
  70. -- Step size, to avoid checking every single node.
  71. -- This assumes protections cannot be smaller than this size.
  72. local ss = 3
  73. local check = minetest.test_protection
  74. for x=minp.x, maxp.x, ss do
  75. for y=minp.y, maxp.y, ss do
  76. for z=minp.z, maxp.z, ss do
  77. if check({x=x, y=y, z=z}, "") then
  78. -- Protections are present.
  79. return true
  80. end
  81. end
  82. end
  83. end
  84. -- Nothing in the area is protected.
  85. return false
  86. end
  87. function obsidian_gateway.find_gate(pos)
  88. local result
  89. local points
  90. local counts
  91. local origin
  92. local northsouth
  93. local ns_key
  94. local playerorigin
  95. -- Find the gateway (threshold under player)!
  96. result, points, counts, origin =
  97. schematic_find.detect_schematic(pos, gate_northsouth)
  98. northsouth = true
  99. ns_key = "ns"
  100. if result then
  101. playerorigin = vector.add(origin, {x=1, y=1, z=0})
  102. end
  103. if not result then
  104. -- Couldn't find northsouth gateway, so try to find eastwest.
  105. result, points, counts, origin =
  106. schematic_find.detect_schematic(pos, gate_eastwest)
  107. northsouth = false
  108. ns_key = "ew"
  109. if result then
  110. playerorigin = vector.add(origin, {x=0, y=1, z=1})
  111. end
  112. end
  113. -- Debugging.
  114. if not result then
  115. --minetest.chat_send_player(pname, "# Server: Bad gateway.")
  116. return
  117. end
  118. -- Store locations of air inside the portal gateway.
  119. local airpoints = {}
  120. if result then
  121. for k, v in ipairs(points) do
  122. if minetest.get_node(v).name == "air" then
  123. airpoints[#airpoints+1] = v
  124. end
  125. end
  126. end
  127. -- Did we find a working gateway?
  128. local yes = false
  129. if result then
  130. local o = counts["default:obsidian"] or 0
  131. local d = counts["cavestuff:dark_obsidian"] or 0
  132. local c = counts["cavestuff:glow_obsidian"] or 0
  133. local g = counts["griefer:grieferstone"] or 0
  134. if (o + d + c) == 12 and g == 2 then
  135. yes = true
  136. end
  137. end
  138. if yes then
  139. return true, origin, airpoints, northsouth, ns_key, playerorigin
  140. end
  141. end
  142. function obsidian_gateway.attempt_activation(pos, player)
  143. local pname = player:get_player_name()
  144. local ppos = vector.round(player:get_pos())
  145. local under = utility.node_under_pos(player:get_pos())
  146. local inside = vector.add(under, {x=0, y=1, z=0})
  147. local nodeunder = minetest.get_node(under).name
  148. -- Player must be standing on one of these.
  149. if nodeunder ~= "default:obsidian" and
  150. nodeunder ~= "griefer:grieferstone" and
  151. nodeunder ~= "cavestuff:dark_obsidian" then
  152. -- This triggers when other types of portals are used, so is incorrect to display this chat.
  153. --minetest.chat_send_player(pname, "# Server: You need to be standing in the gateway for it to work!")
  154. return
  155. end
  156. local success
  157. local origin
  158. local northsouth
  159. local ns_key
  160. local playerorigin
  161. local airpoints
  162. success, origin, airpoints, northsouth, ns_key, playerorigin =
  163. obsidian_gateway.find_gate(pos)
  164. if not success then
  165. return
  166. end
  167. -- Add/update sound beacon.
  168. ambiance.spawn_sound_beacon("soundbeacon:gate", origin, 20, 1)
  169. ambiance.replay_nearby_sound_beacons(origin, 6)
  170. if sheriff.is_cheater(pname) then
  171. if sheriff.punish_probability(pname) then
  172. sheriff.punish_player(pname)
  173. return
  174. end
  175. end
  176. minetest.log("action", pname .. " activated gateway @ " .. minetest.pos_to_string(pos))
  177. local target
  178. local meta = minetest.get_meta(origin)
  179. -- By spliting the key names by ns/ew, I ensure connected portals don't
  180. -- stomp on each other's data.
  181. target = minetest.string_to_pos(meta:get_string("obsidian_gateway_destination_" .. ns_key))
  182. --if not target then
  183. -- minetest.chat_send_player(pname, "# Server: Gateway has no destination! Aborting.")
  184. -- return
  185. --end
  186. -- Enable this if any serious problems occur.
  187. --if pname ~= "MustTest" then
  188. -- minetest.chat_send_player(pname, "# Server: Safety abort! Gateways are locked until further notice due to an error in the code.")
  189. -- return
  190. --end
  191. local isreturngate = (meta:get_int("obsidian_gateway_return_gate_" .. ns_key) == 1)
  192. local actual_owner = meta:get_string("obsidian_gateway_owner_" .. ns_key)
  193. local isowner = (actual_owner == pname)
  194. local first_time_init = false
  195. -- Initialize gateway for the first time.
  196. if not target or (meta:get_string("obsidian_gateway_success_" .. ns_key) ~= "yes" and not isreturngate) then
  197. -- Target is valid then this could be an OLD gate with old metadata.
  198. if target and not isreturngate and meta:get_string("obsidian_gateway_success_" .. ns_key) == "" then
  199. minetest.chat_send_player(pname, "# Server: It looks like this could possibly be an OLD gate! Aborting for safety reasons.")
  200. minetest.chat_send_player(pname, "# Server: If this Gateway was previously functioning normally, please mail the admin with the coordinates.")
  201. minetest.chat_send_player(pname, "# Server: If this is a Gate that you have just constructed, you can safely ignore this message.")
  202. minetest.chat_send_player(pname, "# Server: The Gateway's EXIT location is @ " .. rc.pos_to_namestr(target) .. ".")
  203. minetest.after(1.5, function() easyvend.sound_error(pname) end)
  204. return
  205. end
  206. -- Algorithm for locating the destination.
  207. -- Get a potential gate location.
  208. target = rc.get_random_realm_gate_position(pname, origin)
  209. -- Is target outside bounds?
  210. local bad = function(target, origin)
  211. -- Handle nil.
  212. if not target then
  213. return true
  214. end
  215. -- Don't allow exit points near the colonies.
  216. if vector.distance(target, {x=0, y=0, z=0}) < 2000 or
  217. vector.distance(target, {x=0, y=-30790, z=0}) < 2000 then
  218. return true
  219. end
  220. -- Exit must not be too close to start.
  221. if vector.distance(target, origin) < 500 then
  222. return true
  223. end
  224. -- Or too far.
  225. -- This causes too many failures.
  226. -- Note: this is now handled by the 'rc' mod.
  227. --if vector.distance(target, origin) > 7000 then
  228. -- return true
  229. --end
  230. if not rc.is_valid_gateway_region(target) then
  231. return true
  232. end
  233. end
  234. -- Keep trying until the target is within bounds.
  235. local num_tries = 0
  236. while bad(target, origin) do
  237. target = rc.get_random_realm_gate_position(pname, origin)
  238. num_tries = num_tries + 1
  239. -- Max 3 tries.
  240. if num_tries >= 2 then
  241. ---[[
  242. minetest.after(0, function()
  243. -- Detonate some TNT!
  244. tnt.boom(vector.add(ppos, {x=math.random(-3, 3), y=0, z=math.random(-3, 3)}), {
  245. radius = 3,
  246. ignore_protection = false,
  247. ignore_on_blast = false,
  248. damage_radius = 5,
  249. disable_drops = true,
  250. })
  251. end)
  252. --]]
  253. return
  254. end
  255. end
  256. meta:set_string("obsidian_gateway_destination_" .. ns_key, minetest.pos_to_string(target))
  257. meta:set_string("obsidian_gateway_owner_" .. ns_key, pname)
  258. first_time_init = true
  259. isowner = true
  260. end
  261. --minetest.chat_send_player(pname, "# Server: Safety ABORT #2.")
  262. --do return end
  263. if gdac.player_is_admin(pname) then
  264. isowner = true
  265. end
  266. -- Let everyone use gates owned by the admin.
  267. if actual_owner == "MustTest" then
  268. isowner = true
  269. end
  270. -- Slightly randomize player's exit coordinates.
  271. -- Without changing the coordinates of the gateway.
  272. local pdest
  273. if northsouth then
  274. pdest = vector.add(target, {x=math.random(0, 1), y=0, z=0})
  275. else
  276. pdest = vector.add(target, {x=0, y=0, z=math.random(0, 1)})
  277. end
  278. -- Collect any friends to bring along.
  279. local friendstobring = {}
  280. local allplayers = minetest.get_connected_players()
  281. for k, v in ipairs(allplayers) do
  282. if v:get_player_name() ~= pname then
  283. if vector.distance(v:get_pos(), player:get_pos()) < 3 then
  284. friendstobring[#friendstobring+1] = v:get_player_name()
  285. end
  286. end
  287. end
  288. -- Create a gateway at the player's destination.
  289. -- This gateway links back to the first.
  290. -- If it is destroyed, the player is stuck!
  291. preload_tp.preload_and_teleport(pname, pdest, 32,
  292. function()
  293. if not isowner then
  294. -- Grief portal if used by someone other than owner.
  295. local plava = airpoints[math.random(1, #airpoints)]
  296. --minetest.chat_send_all("# Server: Attempting to grief gateway @ " .. minetest.pos_to_string(plava) .. "!")
  297. if minetest.get_node(plava).name == "air" then
  298. if plava.y < -10 then
  299. minetest.add_node(plava, {name="default:lava_source"})
  300. else
  301. minetest.add_node(plava, {name="fire:basic_flame"})
  302. end
  303. end
  304. end
  305. -- Don't build return portal on top of someone's protected stuff.
  306. if first_time_init then
  307. if check_protection(vector.add(target, {x=0, y=3, z=0}), 5) then
  308. minetest.chat_send_player(pname, "# Server: Return-gate construction FAILED due to protection near " .. rc.pos_to_namestr(target) .. ".")
  309. -- Clear data for the initial gate. This will permit the player to retry without tearing everything down and building it again.
  310. local meta = minetest.get_meta(origin)
  311. meta:set_string("obsidian_gateway_success_" .. ns_key, "")
  312. meta:set_string("obsidian_gateway_destination_" .. ns_key, "")
  313. meta:set_string("obsidian_gateway_owner_" .. ns_key, "")
  314. -- Cancel transport.
  315. return true
  316. end
  317. end
  318. -- Build return portal (only if not already using a return portal).
  319. -- Also, only build return portal on first use of the initial portal.
  320. if not isreturngate and first_time_init then
  321. if northsouth then
  322. -- Place northsouth gateway.
  323. local path = obsidian_gateway.modpath .. "/obsidian_gateway_northsouth.mts"
  324. local gpos = vector.add(target, {x=-1, y=-1, z=0})
  325. minetest.place_schematic(gpos, path, "0", nil, true)
  326. local meta = minetest.get_meta(gpos)
  327. meta:set_string("obsidian_gateway_destination_" .. ns_key, minetest.pos_to_string(playerorigin))
  328. meta:set_string("obsidian_gateway_owner_" .. ns_key, pname)
  329. meta:set_int("obsidian_gateway_return_gate_" .. ns_key, 1)
  330. else
  331. -- Place eastwest gateway.
  332. local path = obsidian_gateway.modpath .. "/obsidian_gateway_eastwest.mts"
  333. local gpos = vector.add(target, {x=0, y=-1, z=-1})
  334. minetest.place_schematic(gpos, path, "0", nil, true)
  335. local meta = minetest.get_meta(gpos)
  336. meta:set_string("obsidian_gateway_destination_" .. ns_key, minetest.pos_to_string(playerorigin))
  337. meta:set_string("obsidian_gateway_owner_" .. ns_key, pname)
  338. meta:set_int("obsidian_gateway_return_gate_" .. ns_key, 1)
  339. end
  340. end
  341. -- Mark the initial gate as success.
  342. -- If this is not done, then gate will assume it is not initialized
  343. -- the next time it is used. This fixes a bug where the return gate is
  344. -- not properly constructed if the player moves during transport
  345. -- (because this callback function doesn't get called).
  346. if not isreturngate and first_time_init then
  347. local meta = minetest.get_meta(origin)
  348. meta:set_string("obsidian_gateway_success_" .. ns_key, "yes")
  349. meta:mark_as_private("obsidian_gateway_success_" .. ns_key)
  350. end
  351. -- If the destination is the Abyss, then kill player first.
  352. -- This helps to prevent player from bringing any foreign items into this realm.
  353. -- Note: this relies on the teleport code already checking all other preconditions
  354. -- first. I.e., if this callback returns 'false', then the player absolutely
  355. -- will be teleported.
  356. if rc.current_realm_at_pos(pdest) == "abyss" then
  357. -- Dump player bones, as if they died.
  358. -- This should behave exactly as if the player died, with the exception of
  359. -- setting the player's health to 0.
  360. bones.dump_bones(pname)
  361. bones.last_known_death_locations[pname] = nil -- Fake death.
  362. local pref = minetest.get_player_by_name(pname)
  363. pref:set_hp(pref:get_properties().hp_max)
  364. give_initial_stuff.give(pref)
  365. end
  366. end,
  367. function()
  368. for k, v in ipairs(friendstobring) do
  369. local friend = minetest.get_player_by_name(v)
  370. if friend then
  371. local fname = friend:get_player_name()
  372. preload_tp.preload_and_teleport(fname, pdest, 16,
  373. function()
  374. -- If the destination is the Abyss, then kill player first.
  375. -- This helps to prevent player from bringing any foreign items into this realm.
  376. -- Note: this relies on the teleport code already checking all other preconditions
  377. -- first. I.e., if this callback returns 'false', then the player absolutely
  378. -- will be teleported.
  379. if rc.current_realm_at_pos(pdest) == "abyss" then
  380. -- Dump player bones, as if they died.
  381. -- This should behave exactly as if the player died, with the exception of
  382. -- setting the player's health to 0.
  383. bones.dump_bones(fname)
  384. bones.last_known_death_locations[fname] = nil -- Fake death.
  385. local pref = minetest.get_player_by_name(fname)
  386. pref:set_hp(pref:get_properties().hp_max)
  387. give_initial_stuff.give(pref)
  388. end
  389. end,
  390. nil, nil, true)
  391. portal_sickness.on_use_portal(fname)
  392. end
  393. end
  394. -- Update liquids around on first init.
  395. if first_time_init then
  396. minetest.after(2, function()
  397. mapfix.execute(target, 10)
  398. end)
  399. end
  400. ambiance.spawn_sound_beacon("soundbeacon:gate", target, 20, 1)
  401. ambiance.replay_nearby_sound_beacons(target, 6)
  402. portal_sickness.on_use_portal(pname)
  403. end, nil, false, "nether_portal_usual")
  404. end
  405. if not obsidian_gateway.run_once then
  406. local c = "obsidian_gateway:core"
  407. local f = obsidian_gateway.modpath .. "/init.lua"
  408. reload.register_file(c, f, false)
  409. obsidian_gateway.run_once = true
  410. end