init.lua 15 KB

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