init.lua 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. if not minetest.global_exists("randspawn") then randspawn = {} end
  2. randspawn.modpath = minetest.get_modpath("randspawn")
  3. -- Localize for performance.
  4. local math_random = math.random
  5. -- After the Outback gateway exit coordinates are changed, this is the min and
  6. -- max number of days until it changes again.
  7. randspawn.min_days = 10
  8. randspawn.max_days = 90
  9. function randspawn.check_spawn_reset()
  10. local meta = randspawn.modstorage
  11. local stime = meta:get_string("spawn_reset_timer")
  12. -- If timestamp is missing, then initialize it.
  13. -- Outback reset will be scheduled after the timeout.
  14. if not stime or stime == "" then
  15. local time = os.time()
  16. local days = 60*60*24*math_random(randspawn.min_days, randspawn.max_days)
  17. time = time + days
  18. stime = tostring(time)
  19. meta:set_string("spawn_reset_timer", stime)
  20. -- Find a new spawn point.
  21. local t = serveressentials.get_realm_names()
  22. for k, realm in ipairs(t) do
  23. randspawn.find_new_spawn(false, realm)
  24. end
  25. return
  26. end
  27. local now = os.time()
  28. local later = tonumber(stime) -- Time of future reset (or initialization).
  29. if now >= later then
  30. later = later + 60*60*24*math_random(randspawn.min_days, randspawn.max_days)
  31. stime = tostring(later)
  32. meta:set_string("spawn_reset_timer", stime)
  33. -- Find a new spawn point.
  34. local t = serveressentials.get_realm_names()
  35. for k, realm in ipairs(t) do
  36. randspawn.find_new_spawn(false, realm)
  37. end
  38. end
  39. end
  40. minetest.after(0, function() randspawn.check_spawn_reset() end)
  41. -- Used by the calendar item.
  42. function randspawn.get_spawn_reset_timeout()
  43. local meta = randspawn.modstorage
  44. local stime = meta:get_string("spawn_reset_timer")
  45. local later = tonumber(stime)
  46. local now = os.time()
  47. local diff = later - now
  48. if diff < 0 then diff = 0 end
  49. return diff
  50. end
  51. -- List of nodes Outback gates should never place players over.
  52. local FORBIDDEN_SPAWN_SURFACE_NODES = {
  53. "default:water_source",
  54. "cw:water_source",
  55. "default:lava_source",
  56. "lbrim:lava_source",
  57. }
  58. local function forbidden_surface(nodename)
  59. for k, v in ipairs(FORBIDDEN_SPAWN_SURFACE_NODES) do
  60. if nodename == v then
  61. return true
  62. end
  63. end
  64. return false
  65. end
  66. local function callback(blockpos, action, calls_remaining, param)
  67. -- We don't do anything until the last callback.
  68. if calls_remaining ~= 0 then
  69. return
  70. end
  71. -- Check if there was an error on the LAST call.
  72. -- Note: this will usually fail if the area to emerge intersects the map edge.
  73. -- But usually we don't try to do that, here.
  74. if action == core.EMERGE_CANCELLED or action == core.EMERGE_ERRORED then
  75. return
  76. end
  77. local pos = param.pos
  78. local realm = param.realm
  79. local get_node = minetest.get_node
  80. local miny = pos.y - 15
  81. local maxy = pos.y + 195
  82. -- Start at bottom of emerged area and scan upward to find ground.
  83. for y = miny, maxy, 1 do
  84. local thispos = {x=pos.x, y=y, z=pos.z}
  85. local nu = get_node(thispos)
  86. local na = get_node(vector.add(thispos, {x=0, y=1, z=0}))
  87. local nb = get_node(vector.add(thispos, {x=0, y=2, z=0}))
  88. local nd = get_node(vector.add(thispos, {x=0, y=3, z=0}))
  89. -- Exit if map not loaded.
  90. if nu.name == "ignore" or na.name == "ignore" then
  91. --minetest.log("hit ignore at y=" .. y .. ", aborting")
  92. break
  93. end
  94. -- Require 3 nodes of air to ensure player doesn't spawn with head buried.
  95. -- We're not communists.
  96. if na.name == "air" and nb.name == "air" and nd.name == "air" and nu.name ~= "air" then
  97. if not forbidden_surface(nu.name) then
  98. thispos.y = thispos.y + 1
  99. -- We have a new spawnpoint.
  100. --minetest.log("found new spawn location!")
  101. serveressentials.update_exit_location(thispos, realm)
  102. return
  103. end
  104. end
  105. end
  106. -- We didn't find a suitable spawn location. Try again shortly.
  107. minetest.log("could not find spawn location, trying again.")
  108. local ls = param.local_shift
  109. minetest.after(10, function() randspawn.find_new_spawn(ls, realm) end)
  110. end
  111. function randspawn.find_new_spawn(local_shift, realm)
  112. local realmspawny = 0
  113. local realmspawn = rc.get_realm_data(realm).spawnlevel
  114. local randx = math_random(-6000, 6000)
  115. local randz = math_random(-6000, 6000)
  116. if type(realmspawn) == "number" then
  117. realmspawny = realmspawn
  118. elseif type(realmspawn) == "function" then
  119. realmspawny = realmspawn({x=randx, y=0, z=randz})
  120. end
  121. local pos = {x=randx, y=realmspawny, z=randz}
  122. -- If we're only performing a local shift, adjust the coordinates randomly
  123. -- around the current existing coordinates (if existing coords exist!).
  124. if local_shift then
  125. local rad = 75
  126. local soldpos = serveressentials.get_current_exit_location(realm)
  127. local oldpos = minetest.string_to_pos(soldpos)
  128. if oldpos then
  129. pos.x = math.random(oldpos.x - rad, oldpos.x + rad)
  130. pos.y = oldpos.y
  131. pos.z = math.random(oldpos.z - rad, oldpos.z + rad)
  132. end
  133. end
  134. local minp = vector.add(pos, {x=-7, y=-20, z=-7})
  135. local maxp = vector.add(pos, {x=7, y=200, z=7})
  136. minetest.emerge_area(minp, maxp, callback,
  137. {pos=table.copy(pos), local_shift=local_shift, realm=realm})
  138. end
  139. -- This function shall ALWAYS return the Outback's static_spawn!
  140. local function get_respawn_position(invoke_pos, pname)
  141. -- Regardless of where player dies, if they have no bed,
  142. -- then they respawn in the outback. Note that a player may lose their bed if
  143. -- killed by another player outside of the city.
  144. return rc.static_spawn("abyss")
  145. end
  146. randspawn.get_respawn_pos = get_respawn_position
  147. -- Note: this is also called from the /spawn chatcommand,
  148. -- but only after validation passes (distance, etc.).
  149. -- This API shall place player at the Outback's static_spawn, ALWAYS.
  150. randspawn.reposition_player = function(pname, death_pos)
  151. local player = minetest.get_player_by_name(pname)
  152. if player then
  153. -- Ensure teleport is forced, to prevent a cheat.
  154. local pos = get_respawn_position(death_pos, pname)
  155. pos = vector.add(pos, {x=math_random(-1, 1), y=0, z=math_random(-1, 1)})
  156. preload_tp.execute({
  157. player_name = pname,
  158. target_position = pos,
  159. emerge_radius = 32,
  160. post_teleport_callback = function()
  161. ambiance.sound_play("respawn", pos, 0.5, 10)
  162. end,
  163. force_teleport = true,
  164. send_blocks = false,
  165. particle_effects = true,
  166. })
  167. end
  168. end
  169. -- The calendar item calls this to report the location of the current spawnpoint.
  170. function randspawn.get_spawn_name(realm)
  171. local s = serveressentials.get_current_exit_location(realm)
  172. local p = minetest.string_to_pos(s)
  173. -- The outback spawn exit isn't fixed, so you can never really know exactly
  174. -- where someone will come out at ... the calendar thus can only give you the
  175. -- aproximate location.
  176. if p then
  177. local rd = rc.get_realm_data(rc.current_realm_at_pos(p))
  178. local ro = {
  179. x = rd.realm_origin.x % 100,
  180. y = rd.realm_origin.y % 10,
  181. z = rd.realm_origin.z % 100,
  182. }
  183. p.x = math.floor(p.x / 100) * 100 + ro.x
  184. p.y = math.floor(p.y / 10) * 10 + ro.y
  185. p.z = math.floor(p.z / 100) * 100 + ro.z
  186. return rc.pos_to_namestr(p)
  187. end
  188. return "Unknown Location"
  189. end
  190. function randspawn.on_outback_gate_use(params)
  191. if rc.current_realm_at_pos(params.gate_origin) == "abyss" then
  192. -- Shift the Outback exit 30 minutes after every use.
  193. minetest.after(60*30, function()
  194. local realmname = rc.current_realm_at_pos(params.teleport_destination)
  195. randspawn.find_new_spawn(true, realmname)
  196. end)
  197. -- Make sure there's a return gate entity, in lieu of an actual gate.
  198. obsidian_gateway.create_portal_entity(vector.offset(params.teleport_destination, 0, 7, 0), {
  199. target = obsidian_gateway.get_gate_player_spawn_pos(
  200. params.gate_origin, params.gate_orientation),
  201. })
  202. end
  203. end
  204. if not randspawn.run_once then
  205. -- Reloadable.
  206. local file = randspawn.modpath .. "/init.lua"
  207. local name = "randspawn:core"
  208. reload.register_file(name, file, false)
  209. portal_cb.register_after_use(function(params)
  210. randspawn.on_outback_gate_use(params)
  211. end)
  212. randspawn.modstorage = minetest.get_mod_storage()
  213. randspawn.run_once = true
  214. end