handler.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. --[[
  2. Advanced Portals (advanced_portals), A minetest add-on that allows certain users to create and place portals on their minetest world.
  3. Copyright (c) 2020 Genshin <emperor_genshin@hotmail.com>
  4. License: GPLv3
  5. --]]
  6. --Delay to when handlers update
  7. local update_interval = 25
  8. local particle_emitter_interval = 4
  9. --Delay to when ambiance will loop
  10. local ambiance_interval = 200
  11. local ambiance_list = {}
  12. local debug = minetest.settings:get_bool("enable_portals_debug") or false
  13. local function get_count_from_table(table)
  14. count = 0
  15. for k,v in pairs(table) do
  16. count = count + 1
  17. end
  18. return count
  19. end
  20. local function do_particle_effect(pos, delay, amount, texture, min_size, max_size, radius, gravity, glow)
  21. radius = radius or 2
  22. min_size = min_size or 0.5
  23. max_size = max_size or 1
  24. gravity = gravity or -10
  25. glow = glow or 0
  26. delay = delay or 0.25
  27. minetest.add_particlespawner({
  28. amount = amount,
  29. time = delay,
  30. minpos = pos,
  31. maxpos = pos,
  32. minvel = {x = -radius, y = -radius, z = -radius},
  33. maxvel = {x = radius, y = radius, z = radius},
  34. minacc = {x = 0, y = gravity, z = 0},
  35. maxacc = {x = 0, y = gravity, z = 0},
  36. minexptime = 0.1,
  37. maxexptime = 1,
  38. minsize = min_size,
  39. maxsize = max_size,
  40. texture = texture,
  41. glow = glow,
  42. })
  43. end
  44. local function update_ambiance_to_players_within_radius(self, distance)
  45. local pos = self.object:get_pos()
  46. local node = self.portal_nodename
  47. local ambiance_sound = get_portal_handler_sounds(node, "portal_ambiance")
  48. local ambiance_distance = get_portal_handler_sounds(node, "sound_distance")
  49. local entities = minetest.get_objects_inside_radius(pos, ambiance_distance)
  50. local players = minetest.get_connected_players()
  51. local within_radius = {}
  52. --Get the following players within the radius and stack them in a table
  53. for k,v in pairs(entities) do
  54. local entity = v
  55. if not entity then
  56. within_radius = {}
  57. break
  58. end
  59. if entity:is_player() then
  60. local name = entity:get_player_name()
  61. within_radius[name] = entity
  62. end
  63. end
  64. --Check which players are ouside of radius
  65. for _,player in pairs(players) do
  66. local name = player:get_player_name()
  67. if within_radius[name] then
  68. if not ambiance_list[name] then
  69. local ambiance = minetest.sound_play(ambiance_sound, {
  70. object = player,
  71. loop = true,
  72. })
  73. ambiance_list[name] = ambiance
  74. end
  75. else
  76. if ambiance_list[name] then
  77. minetest.sound_stop(ambiance_list[name])
  78. ambiance_list[name] = nil
  79. end
  80. end
  81. end
  82. end
  83. local function force_stop_ambiance_to_players()
  84. local players = minetest.get_connected_players()
  85. --Check which players are ouside of radius
  86. for _,player in pairs(players) do
  87. local name = player:get_player_name()
  88. if name then
  89. if ambiance_list[name] then
  90. minetest.sound_stop(ambiance_list[name])
  91. ambiance_list[name] = nil
  92. end
  93. end
  94. end
  95. end
  96. --[Local Function] Teleport Player or Mob to a location or pparticle_emitter_intervalortal
  97. local function teleport_entity(userdata, self)
  98. local portal_data = get_portal_data(self.portal)
  99. local linked = portal_data.linked
  100. local linked_portal_data = nil
  101. local pos = nil
  102. local height = nil
  103. local node = self.portal_nodename
  104. local warp_sound = get_portal_handler_sounds(node, "portal_warp") or ""
  105. local sound_distance = get_portal_handler_sounds(node, "sound_distance")
  106. local entity_pos = userdata:get_pos()
  107. if linked == true then
  108. linked_portal_data = get_portal_data(portal_data.linked_portal)
  109. if not linked_portal_data then
  110. return
  111. end
  112. pos = linked_portal_data.location
  113. --height = get_portal_handler_origin_height(self.portal_nodename, "warp_handler")
  114. if pos == nil then
  115. minetest.log("error", "Failed to find location of linked portal. Refusing to teleport entity to a unknown location")
  116. return
  117. end
  118. if debug == true then
  119. minetest.chat_send_all("Teleported Something! - Linked")
  120. end
  121. do_particle_effect(entity_pos, 0.25, 1, "warp_particle.png", 10, 20, radius, 20, 30)
  122. userdata:set_pos({x = pos.x, y = pos.y + 3, z = pos.z})
  123. elseif linked == false then
  124. pos = portal_data.coordinates
  125. if pos == nil then
  126. minetest.log("error", "Failed to find specified destination. Refusing to teleport entity to a unknown location")
  127. return
  128. end
  129. if debug == true then
  130. minetest.chat_send_all("Teleported Something! - Not Linked")
  131. end
  132. do_particle_effect(entity_pos, 5, 25, "warp_particle.png", math.random(5,10), math.random(10,20), radius, 20, 10)
  133. userdata:set_pos(pos)
  134. end
  135. if userdata:is_player() then
  136. local name = userdata:get_player_name()
  137. if ambiance_list[name] then
  138. minetest.sound_stop(ambiance_list[name])
  139. ambiance_list[name] = nil
  140. end
  141. end
  142. minetest.sound_play(warp_sound, {
  143. pos = self.object:get_pos(),
  144. max_hear_distance = sound_distance,
  145. gain = 10.0,
  146. })
  147. minetest.sound_play(warp_sound, {
  148. pos = pos,
  149. max_hear_distance = sound_distance,
  150. gain = 10.0,
  151. })
  152. return
  153. end
  154. --[Local Function] Get node from vertical position
  155. local function get_node_from_vertical_pos(userdata, direction, vertical_height)
  156. local pos = userdata:get_pos()
  157. if direction == nil then else
  158. if direction == "up" then
  159. pos = {x = pos.x, y = pos.y + vertical_height, z = pos.z}
  160. elseif direction == "down" then
  161. pos = {x = pos.x, y = pos.y - vertical_height, z = pos.z}
  162. end
  163. end
  164. local result = tostring(minetest.get_node(pos)["name"]) or "Error"
  165. return result
  166. end
  167. --[Local Function] Portal Warp Handler Behavior
  168. local function handler_step(self)
  169. self.update_timer = self.update_timer + 1
  170. if self.emit_particles == "true" then
  171. self.particle_emitter_timer = self.particle_emitter_timer + 1
  172. if self.particle_emitter_timer >= particle_emitter_interval then
  173. local texture = get_portal_particle_values(self.portal_nodename, "texture")
  174. local delay = get_portal_particle_values(self.portal_nodename, "delay")
  175. local spread = get_portal_particle_values(self.portal_nodename, "spread")
  176. local pos = self.object:get_pos()
  177. do_particle_effect(pos, delay, 1, texture, math.random(5,8), math.random(8,12), spread, 1, 30)
  178. self.particle_emitter_timer = 0
  179. end
  180. end
  181. if self.update_timer >= update_interval then
  182. if self.origin_height == nil then
  183. force_stop_ambiance_to_players()
  184. self.object:remove()
  185. return
  186. end
  187. local portal_node = tostring(get_node_from_vertical_pos(self.object, "down", self.origin_height))
  188. --minetest.chat_send_all(tostring(portal_node)..", "..tostring(self.portal_node)..", "..tostring(self.origin_height)..", "..tostring(self.portal))
  189. if self.portal == nil or self.portal_node == nil then --delete yourself if it's not associated with a portal (failsafe)
  190. force_stop_ambiance_to_players()
  191. --minetest.chat_send_all("Uh Oh 2 - Handler")
  192. self.object:remove()
  193. return
  194. elseif portal_node ~= self.portal_node then --No Portal below you?, if so then kill yourself (failsafe)
  195. force_stop_ambiance_to_players()
  196. --minetest.chat_send_all("Uh Oh 3 - Handler")
  197. self.object:remove()
  198. return
  199. end
  200. update_ambiance_to_players_within_radius(self)
  201. local portal_data = get_portal_data(self.portal)
  202. --If data hasn't loaded successfully, skip the rest of the stuff (failsafe)
  203. if portal_data == nil then
  204. return
  205. end
  206. self.emit_particles = get_portal_particle_values(self.portal_nodename, "flag") or self.emit_particles
  207. local pos = self.object:get_pos()
  208. local detected_entities = minetest.get_objects_inside_radius(pos, self.scan_radius)
  209. local private = portal_data.private
  210. if private == true then
  211. local whitelist = portal_data.whitelist
  212. for _,entity in pairs(detected_entities) do
  213. if entity:is_player() then
  214. if debug == true then
  215. minetest.chat_send_all("Attempting to teleport someone! - Private")
  216. end
  217. local player = entity
  218. local player_name = player:get_player_name()
  219. for k,v in pairs(whitelist) do
  220. local allowed = v
  221. local pname = player:get_player_name()
  222. if pname == allowed or player_name == self.portal_owner then
  223. teleport_entity(player, self)
  224. end
  225. end
  226. else
  227. local npc = entity:get_luaentity()
  228. if npc.type and npc ~= self then
  229. if debug == true then
  230. minetest.chat_send_all("Attempting to teleport something! - Private")
  231. end
  232. teleport_entity(npc.object, self)
  233. end
  234. end
  235. end
  236. elseif private == false then
  237. for _,entity in pairs(detected_entities) do
  238. if entity:is_player() then
  239. if debug == true then
  240. minetest.chat_send_all("Attempting to teleport someone! - Not Private")
  241. end
  242. teleport_entity(entity, self)
  243. else
  244. local npc = entity:get_luaentity()
  245. if npc.type and npc ~= self then
  246. if debug == true then
  247. minetest.chat_send_all("Attempting to teleport something! - Not Private")
  248. end
  249. teleport_entity(npc.object, self)
  250. end
  251. end
  252. end
  253. end
  254. ::portal_handler_loop_end::
  255. self.update_timer = 0
  256. end
  257. end
  258. local function update_portal_nametag(self)
  259. local portal_data = get_portal_data(self.portal)
  260. --If data hasn't loaded successfully, skip the rest of the stuff (failsafe)
  261. if portal_data == nil then
  262. return
  263. end
  264. self.object:set_properties({
  265. nametag = minetest.colorize("#0066ff", portal_data.nametag).."\n"..minetest.colorize("#959595", "["..portal_data.portal_name.."]")
  266. })
  267. end
  268. --[Local Function] Portal Nametag Behavior
  269. local function nametag_step(self)
  270. self.update_timer = self.update_timer + 1
  271. if self.update_timer >= update_interval then
  272. if self.origin_height == nil then
  273. --minetest.chat_send_all("Uh Oh 1 - Nametag")
  274. self.object:remove()
  275. return
  276. end
  277. local portal_node = tostring(get_node_from_vertical_pos(self.object, "down", self.origin_height))
  278. --minetest.chat_send_all(tostring(portal_node)..", "..tostring(self.portal_node)..", "..tostring(self.origin_height)..", "..tostring(self.portal))
  279. if self.portal == nil or self.portal_node == nil then --delete yourself if it's not associated with a portal (failsafe)
  280. --minetest.chat_send_all("Uh Oh 2 - Nametag")
  281. self.object:remove()
  282. return
  283. elseif portal_node ~= self.portal_node then --No Portal below you?, if so then kill yourself (failsafe)
  284. --minetest.chat_send_all("Uh Oh 3 - Nametag")
  285. self.object:remove()
  286. return
  287. end
  288. local portal_data = get_portal_data(self.portal)
  289. --If data hasn't loaded successfully, skip the rest of the stuff (failsafe)
  290. if portal_data == nil then
  291. return
  292. end
  293. update_portal_nametag(self)
  294. self.update_timer = 0
  295. end
  296. end
  297. --[Entity] Portal Teleportation Handler
  298. minetest.register_entity("advanced_portals:portal_handler", {
  299. physical = false,
  300. collisionbox = {0, 0, 0, 0, 0, 0},
  301. visual = "sprite",
  302. glow = 30,
  303. textures = {"portal_deactivated.png"},
  304. visual_size = {x = 0, y = 0},
  305. origin_height = nil,
  306. portal = nil,
  307. portal_node = nil,
  308. portal_nodename = nil,
  309. scan_radius = 2,
  310. update_timer = 0,
  311. particle_emitter_timer = 0,
  312. emit_particles = "false",
  313. particle_texture = "",
  314. particle_delay = 0,
  315. particle_spread = 0,
  316. on_activate = function(self, staticdata, dtime_s)
  317. -- load entity variables
  318. local tmp = minetest.deserialize(staticdata)
  319. if tmp then
  320. for _,stat in pairs(tmp) do
  321. self[_] = stat
  322. end
  323. end
  324. self.set = self.set
  325. self.radius = self.radius
  326. self.object:set_armor_groups({immortal = 100}) --Can't die by anything
  327. self.object:set_properties(self)
  328. if debug == true then
  329. self.object:set_properties({
  330. nametag = "<[DEBUG]: Portal Handler Position>"
  331. })
  332. end
  333. if self.portal == nil then else
  334. local portal_data = get_portal_data(self.portal)
  335. local current_origin_height = get_portal_handler_origin_height(self.portal_nodename, "warp_handler")
  336. --print("[DEBUG] warp handler current_origin_height = "..current_origin_height)
  337. if current_origin_height ~= self.origin_height then
  338. local pos = portal_data.location
  339. self.origin_height = current_origin_height
  340. self.object:set_pos({x = pos.x, y = pos.y + current_origin_height, z = pos.z})
  341. end
  342. end
  343. end,
  344. get_staticdata = function(self)
  345. local tmp = {}
  346. for _,stat in pairs(self) do
  347. local t = type(stat)
  348. if t ~= 'function'
  349. and t ~= 'nil'
  350. and t ~= 'userdata' then
  351. tmp[_] = self[_]
  352. end
  353. end
  354. return minetest.serialize(tmp)
  355. end,
  356. on_punch = function(self, hitter) --Can't get punched by anything
  357. return
  358. end,
  359. on_step = function(self)
  360. handler_step(self)
  361. end
  362. })
  363. --[Entity] Portal Nametag
  364. minetest.register_entity("advanced_portals:portal_nametag", {
  365. physical = false,
  366. collisionbox = {0, 0, 0, 0, 0, 0},
  367. visual = "sprite",
  368. tag = "",
  369. glow = 30,
  370. textures = {"portal_deactivated.png"},
  371. visual_size = {x = 0, y = 0},
  372. origin_height = nil,
  373. portal = nil,
  374. set = false,
  375. portal_node = nil,
  376. portal_nodename = nil,
  377. scan_radius = 2,
  378. update_timer = 0,
  379. on_activate = function(self, staticdata, dtime_s)
  380. -- load entity variables
  381. local tmp = minetest.deserialize(staticdata)
  382. if tmp then
  383. for _,stat in pairs(tmp) do
  384. self[_] = stat
  385. end
  386. end
  387. self.radius = self.radius
  388. self.object:set_armor_groups({immortal = 100}) --Can't die by anything
  389. self.object:set_properties(self)
  390. update_portal_nametag(self)
  391. if self.portal == nil then else
  392. local portal_data = get_portal_data(self.portal)
  393. local current_origin_height = get_portal_handler_origin_height(self.portal_nodename, "nametag")
  394. --print("[DEBUG] nametag current_origin_height = "..current_origin_height)
  395. if current_origin_height ~= self.origin_height then
  396. local pos = portal_data.location
  397. self.origin_height = current_origin_height
  398. self.object:set_pos({x = pos.x, y = pos.y + current_origin_height, z = pos.z})
  399. end
  400. end
  401. end,
  402. get_staticdata = function(self)
  403. local tmp = {}
  404. for _,stat in pairs(self) do
  405. local t = type(stat)
  406. if t ~= 'function'
  407. and t ~= 'nil'
  408. and t ~= 'userdata' then
  409. tmp[_] = self[_]
  410. end
  411. end
  412. return minetest.serialize(tmp)
  413. end,
  414. on_punch = function(self, hitter) --Can't get punched by anything
  415. return
  416. end,
  417. on_step = function(self)
  418. nametag_step(self)
  419. end,
  420. })