init.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. -- This mod provides an item which enables players to teleport to registered locations without the need of a teleporter.
  2. -- This item doubles as the means by which the server control scripts decide which playerfiles to delete and which to keep.
  3. passport = passport or {}
  4. passport.recalls = passport.recalls or {}
  5. passport.players = passport.players or {}
  6. passport.registered_players = passport.registered_players or {} -- Cache of registered players.
  7. passport.keyed_players = passport.keyed_players or {}
  8. passport.modpath = minetest.get_modpath("passport")
  9. -- List of players with open keys.
  10. -- On formspec close, playername should be removed and close-sound played.
  11. passport.open_keys = passport.open_keys or {}
  12. -- Localize for performance.
  13. local vector_distance = vector.distance
  14. local vector_round = vector.round
  15. local math_floor = math.floor
  16. local math_random = math.random
  17. local PASSPORT_TELEPORT_RANGE = 3000 -- 3 Kilometers.
  18. minetest.register_privilege("recall", {
  19. description = "Player can request a teleport back to the city.",
  20. give_to_singleplayer = false,
  21. })
  22. function passport.is_passport(name)
  23. if name == "passport:passport" then
  24. return true
  25. end
  26. if name == "passport:passport_adv" then
  27. return true
  28. end
  29. return false
  30. end
  31. -- Public API function. Other mods are expected to register recall locations.
  32. passport.register_recall = function(recalldef)
  33. local name = recalldef.name
  34. local position = recalldef.position
  35. local min_dist = recalldef.min_dist
  36. local on_success = recalldef.on_success
  37. local on_failure = recalldef.on_failure
  38. local tname = recalldef.codename
  39. local suppress = recalldef.suppress
  40. local idx = #(passport.recalls) + 1
  41. local code = "v" .. idx .. ""
  42. passport.recalls[idx] = {
  43. name = name,
  44. position = position,
  45. code = code,
  46. on_success = on_success,
  47. min_dist = min_dist,
  48. on_failure = on_failure,
  49. tname = tname,
  50. suppress = suppress,
  51. }
  52. end
  53. passport.compose_formspec = function(pname)
  54. local buttons = ""
  55. local i = 1
  56. for k, v in pairs(passport.recalls) do
  57. local n = v.name
  58. local c = v.code
  59. buttons = buttons .. "button_exit[6," .. (i-0.3) .. ";3,1;" .. c .. ";" .. n .. "]"
  60. i = i + 1
  61. end
  62. local boolecho = 'true'
  63. local echo = chat_echo.get_echo(pname)
  64. if echo == true then boolecho = 'true' end
  65. if echo == false then boolecho = 'false' end
  66. local boolparticle = 'true'
  67. local particle = default.particles_enabled_for(pname)
  68. if particle == true then boolparticle = 'true' end
  69. if particle == false then boolparticle = 'false' end
  70. local formspec = "size[10,7]" ..
  71. default.gui_bg ..
  72. default.gui_bg_img ..
  73. default.gui_slots ..
  74. "label[1,0.0;" ..
  75. minetest.formspec_escape("Active Interface to your Key of Citizenship. Owner: <" .. rename.gpn(pname) .. ">") .. "]" ..
  76. buttons ..
  77. "button_exit[1,5.7;2,1;exit;Close]" ..
  78. "button_exit[1,2.7;2,1;mapfix;Fix Map]" ..
  79. "button[3,0.7;2,1;email;Mail]" ..
  80. "button[1,1.7;2,1;survivalist;Survivalist]" ..
  81. "button[3,1.7;2,1;rename;Nickname]" ..
  82. "button[3,2.7;2,1;chatfilter;Chat Filter]" ..
  83. "button[1,0.7;2,1;marker;Markers]" ..
  84. "tooltip[email;Hold 'E' while using the Key to access directly.]" ..
  85. "tooltip[marker;Hold 'sneak' while using the Key to access directly.]" ..
  86. "checkbox[3,5.0;togglechat;Enable Echo;" ..
  87. boolecho .. "]" ..
  88. "checkbox[1,5.0;toggleparticles;Want Particles;" ..
  89. boolparticle .. "]" ..
  90. "tooltip[togglechat;" ..
  91. minetest.formspec_escape(
  92. "Toggle whether the server should echo your chat back to your client.\n" ..
  93. "Newer clients should keep this checked.") .. "]" ..
  94. "tooltip[toggleparticles;" ..
  95. minetest.formspec_escape(
  96. "Toggle whether the server should send game-enhancing particle effects to your client.\n" ..
  97. "Sometimes these are purely for visual effect, sometimes they have gameplay meaning ...") .. "]"
  98. -- Special abilities are revoked for cheaters.
  99. if not sheriff.is_cheater(pname) then
  100. if survivalist.player_beat_cave_challenge(pname) then
  101. formspec = formspec .. "button[1,3.7;2,1;jaunt;Jaunt]"
  102. end
  103. if survivalist.player_beat_nether_challenge(pname) then
  104. if cloaking.is_cloaked(pname) then
  105. formspec = formspec .. "button[3,3.7;2,1;cloak;Uncloak]"
  106. else
  107. formspec = formspec .. "button[3,3.7;2,1;cloak;Cloak]"
  108. end
  109. end
  110. end
  111. for i=1, 7, 1 do
  112. local name = "xdecor:ivy"
  113. if i == 1 then
  114. name = "passport:passport_adv"
  115. elseif i == 7 then
  116. name = "default:sword_steel"
  117. end
  118. formspec = formspec .. "item_image[0," .. i-1 .. ";1,1;" .. name .. "]"
  119. formspec = formspec .. "item_image[9," .. i-1 .. ";1,1;" .. name .. "]"
  120. end
  121. return formspec
  122. end
  123. passport.show_formspec = function(pname)
  124. local formspec = passport.compose_formspec(pname)
  125. minetest.show_formspec(pname, "passport:passport", formspec)
  126. end
  127. passport.on_use = function(itemstack, user, pointed)
  128. local changed = false
  129. if user and user:is_player() then
  130. local pname = user:get_player_name()
  131. -- Check (and if needed, set) owner.
  132. local meta = itemstack:get_meta()
  133. local owner = meta:get_string("owner")
  134. if owner == "" then
  135. owner = pname
  136. -- Store owner and data of activation.
  137. meta:set_string("owner", owner)
  138. meta:set_int("date", os.time())
  139. minetest.after(3, function()
  140. minetest.chat_send_player(pname, "# Server: A newly initialized Key of Citizenship begins to emit a soft blue glow.")
  141. end)
  142. changed = true
  143. end
  144. -- Initialize data if not set.
  145. if meta:get_int("date") == 0 then
  146. meta:set_int("date", os.time())
  147. changed = true
  148. end
  149. if owner ~= pname then
  150. minetest.chat_send_player(pname, "# Server: This Key was initialized by someone else! You cannot access it.")
  151. easyvend.sound_error(pname)
  152. return
  153. end
  154. -- Record number of uses.
  155. meta:set_int("uses", meta:get_int("uses") + 1)
  156. changed = true
  157. local control = user:get_player_control()
  158. if control.sneak then
  159. marker.show_formspec(pname)
  160. elseif control.aux1 then
  161. mailgui.show_formspec(pname)
  162. else
  163. -- Show KoC interface.
  164. passport.show_formspec(pname)
  165. end
  166. passport.open_keys[pname] = true
  167. local ppos = user:get_pos()
  168. minetest.after(0, ambiance.sound_play, "fancy_chime1", ppos, 1.0, 20, "", false)
  169. end
  170. if changed then
  171. return itemstack
  172. end
  173. end
  174. passport.on_use_simple = function(itemstack, user, pointed)
  175. if user and user:is_player() then
  176. minetest.chat_send_player(user:get_player_name(),
  177. "# Server: This awkward chunk of reflective metal seems to mock you, " ..
  178. "yet remains strangely inert. Perhaps it can be upgraded?")
  179. end
  180. return itemstack
  181. end
  182. passport.on_receive_fields = function(player, formname, fields)
  183. if formname ~= "passport:passport" then return end
  184. local pname = player:get_player_name()
  185. if fields.mapfix then
  186. mapfix.command(pname, "")
  187. return true
  188. end
  189. if fields.email then
  190. mailgui.show_formspec(pname)
  191. return true
  192. end
  193. if fields.chatfilter then
  194. chat_controls.show_formspec(pname)
  195. return true
  196. end
  197. if fields.marker then
  198. marker.show_formspec(pname)
  199. return true
  200. end
  201. if fields.jaunt and survivalist.player_beat_cave_challenge(pname) then
  202. -- Jaunt code performs its own security validation.
  203. jaunt.show_formspec(pname)
  204. return true
  205. end
  206. if fields.cloak and survivalist.player_beat_nether_challenge(pname) then
  207. -- Security check to make sure player can use this feature.
  208. if not passport.player_has_key(pname) then
  209. return true
  210. end
  211. if not survivalist.player_beat_nether_challenge(pname) then
  212. return true
  213. end
  214. -- Cloaking ability is revoked for cheaters.
  215. if sheriff.is_cheater(pname) then
  216. return true
  217. end
  218. cloaking.toggle_cloak(pname)
  219. passport.show_formspec(pname) -- Reshow formspec.
  220. return true
  221. end
  222. if fields.survivalist then
  223. survivalist.show_formspec(pname)
  224. return true
  225. end
  226. if fields.rename then
  227. rename.show_formspec(pname)
  228. return true
  229. end
  230. if fields.togglechat then
  231. if fields.togglechat == 'true' then
  232. chat_echo.set_echo(pname, true)
  233. elseif fields.togglechat == 'false' then
  234. chat_echo.set_echo(pname, false)
  235. end
  236. passport.show_formspec(pname) -- Reshow formspec.
  237. return true
  238. end
  239. if fields.toggleparticles then
  240. if fields.toggleparticles == 'true' then
  241. default.enable_particles_for(pname, true)
  242. elseif fields.toggleparticles == 'false' then
  243. default.enable_particles_for(pname, false)
  244. end
  245. passport.show_formspec(pname) -- Reshow formspec.
  246. return true
  247. end
  248. for k, v in pairs(passport.recalls) do
  249. local c = v.code
  250. if fields[c] then
  251. if not minetest.check_player_privs(pname, {recall=true}) then
  252. minetest.chat_send_player(pname, "# Server: You are not authorized to request transport.")
  253. easyvend.sound_error(pname)
  254. return true
  255. end
  256. passport.attempt_teleport(player, v)
  257. return true
  258. end
  259. end
  260. return true
  261. end
  262. passport.attempt_teleport = function(player, data)
  263. local pp = player:get_pos()
  264. local nn = player:get_player_name()
  265. local tg = data.position(player)
  266. if rc.current_realm_at_pos(tg) ~= rc.current_realm_at_pos(pp) then
  267. minetest.chat_send_player(nn, "# Server: Beacon signal is in another dimension!")
  268. -- Wrong realm.
  269. return
  270. end
  271. for k, v in pairs(passport.recalls) do
  272. if v.suppress then
  273. if v.suppress(nn) then
  274. minetest.chat_send_player(nn, "# Server: Beacon signal is suppressed and cannot be triangulated.")
  275. easyvend.sound_error(nn)
  276. return -- Someone suppressed the ability to teleport.
  277. end
  278. end
  279. end
  280. for k, v in pairs(passport.recalls) do
  281. if vector_distance(pp, v.position(player)) < v.min_dist then
  282. if data.on_failure then data.on_failure(nn, "too_close", v.tname) end
  283. minetest.chat_send_player(nn, "# Server: You are too close to a nearby beacon signal.")
  284. easyvend.sound_error(nn)
  285. return -- To close to a beacon.
  286. end
  287. end
  288. if vector_distance(pp, tg) > PASSPORT_TELEPORT_RANGE then
  289. if data.on_failure then data.on_failure(nn, "too_far", data.tname) end
  290. local dist = math_floor(vector_distance(pp, tg))
  291. minetest.chat_send_player(nn, "# Server: Beacon signal is too weak. You are out of range: distance " .. dist/1000 .. " kilometers.")
  292. easyvend.sound_error(nn)
  293. return -- To far from requested beacon.
  294. end
  295. if passport.players[nn] then
  296. if data.on_failure then data.on_failure(nn, "in_progress", data.tname) end
  297. minetest.chat_send_player(nn, "# Server: Signal triangulation already underway; stand by.")
  298. return -- Teleport already in progress.
  299. end
  300. -- Everything satisfied. Let's teleport!
  301. local dist = vector_distance(pp, tg)
  302. local time = math.ceil(math.sqrt(dist / 10))
  303. minetest.chat_send_player(nn, "# Server: Recall beacon signal requires " .. time .. " seconds to triangulate; please hold still.")
  304. passport.players[nn] = true
  305. local pos = vector.add(tg, {x=math_random(-2, 2), y=0, z=math_random(-2, 2)})
  306. minetest.after(time, passport.do_teleport, nn, pp, pos, data.on_success)
  307. end
  308. -- Called from minetest.after() to actually execute a teleport.
  309. passport.do_teleport = function(name, start_pos, target_pos, func)
  310. passport.players[name] = nil
  311. local player = minetest.get_player_by_name(name)
  312. if player and player:is_player() then
  313. if sheriff.is_cheater(name) then
  314. if sheriff.punish_probability(name) then
  315. sheriff.punish_player(name)
  316. return
  317. end
  318. end
  319. if vector_distance(player:getpos(), start_pos) < 0.1 then
  320. preload_tp.execute({
  321. player_name = name,
  322. target_position = target_pos,
  323. emerge_radius = 32,
  324. post_teleport_callback = func,
  325. callback_param = name,
  326. send_blocks = true,
  327. particle_effects = true,
  328. })
  329. else
  330. minetest.chat_send_player(name, "# Server: Unable to accurately triangulate beacon position! Aborted.")
  331. easyvend.sound_error(name)
  332. end
  333. end
  334. end
  335. function passport.exec_spawn(name, param)
  336. local player = minetest.get_player_by_name(name)
  337. if not player then return false end
  338. local pos = vector_round(player:get_pos())
  339. if jail.suppress(name) then
  340. return true
  341. end
  342. local target = randspawn.get_respawn_pos(pos, name)
  343. if vector_distance(pos, target) < 20 then
  344. minetest.chat_send_player(name, "# Server: Too close to the spawnpoint!")
  345. easyvend.sound_error(name)
  346. return true
  347. end
  348. if sheriff.is_cheater(name) then
  349. if sheriff.punish_probability(name) then
  350. sheriff.punish_player(name)
  351. return true
  352. end
  353. end
  354. if vector_distance(pos, target) <= 256 then
  355. randspawn.reposition_player(name, pos)
  356. minetest.after(1, function()
  357. minetest.chat_send_player(name, "# Server: You have been returned to the spawnpoint.")
  358. portal_sickness.on_use_portal(name)
  359. end)
  360. else
  361. minetest.chat_send_player(name, "# Server: You are too far from the spawnpoint!")
  362. easyvend.sound_error(name)
  363. end
  364. return true
  365. end
  366. function passport.award_cash(pname, player)
  367. local inv = player:get_inventory()
  368. if not inv then
  369. return
  370. end
  371. local cash_stack = ItemStack("currency:minegeld_20 10")
  372. local prot_stack = ItemStack("protector:protect3")
  373. local cash_left = inv:add_item("main", cash_stack)
  374. local prot_left = inv:add_item("main", prot_stack)
  375. minetest.chat_send_player(pname,
  376. core.get_color_escape_sequence("#ffff00") ..
  377. "# Server: Bank notice - As this is the first recorded time you have obtained a PoC, the Colony grants you 200 minegeld.")
  378. minetest.chat_send_player(pname,
  379. core.get_color_escape_sequence("#ffff00") ..
  380. "# Server: This is roughly equivalent to 8 gold ingots according to the Guild of Weights and Measures.")
  381. if cash_left:is_empty() and prot_left:is_empty() then
  382. minetest.chat_send_player(pname,
  383. core.get_color_escape_sequence("#ffff00") ..
  384. "# Server: The cash has been directly added to your inventory. Trade wisely and well, Adventurer!")
  385. else
  386. local pos = vector_round(player:get_pos())
  387. pos.y = pos.y + 1
  388. if not cash_left:is_empty() then
  389. minetest.add_item(pos, cash_left)
  390. end
  391. if not prot_left:is_empty() then
  392. minetest.add_item(pos, prot_left)
  393. end
  394. minetest.chat_send_player(pname,
  395. core.get_color_escape_sequence("#ffff00") ..
  396. "# Server: The cash could not be added to your inventory (no space). Check near your position for drops.")
  397. end
  398. end
  399. function passport.on_craft(itemstack, player, old_craft_grid, craft_inv)
  400. local name = itemstack:get_name()
  401. if name == "passport:passport_adv" then
  402. local pname = player:get_player_name()
  403. local meta = itemstack:get_meta()
  404. -- Store owner and data of activation.
  405. meta:set_string("owner", pname)
  406. meta:set_int("date", os.time())
  407. minetest.after(3, function()
  408. minetest.chat_send_player(pname,
  409. "# Server: A newly fashioned Key of Citizenship emits a soft blue glow mere moments after its crafter finishes the device.")
  410. end)
  411. -- Clear cache of player registration.
  412. passport.keyed_players[pname] = nil
  413. passport.registered_players[pname] = nil
  414. elseif name == "passport:passport" then
  415. -- Check if this is the first time this player has crafted a PoC.
  416. local pname = player:get_player_name()
  417. local meta = passport.modstorage
  418. local key = pname .. ":crafted_poc"
  419. if meta:get_int(key) == 0 then
  420. meta:set_int(key, 1)
  421. passport.award_cash(pname, player)
  422. end
  423. -- Clear cache of player registration.
  424. passport.keyed_players[pname] = nil
  425. passport.registered_players[pname] = nil
  426. end
  427. end
  428. if not passport.registered then
  429. -- Obtain modstorage.
  430. passport.modstorage = minetest.get_mod_storage()
  431. -- Keep this in inventory to prevent deletion.
  432. minetest.register_craftitem("passport:passport", {
  433. description = "Proof Of Citizenship\n\n" ..
  434. "Keep this in your MAIN inventory at ALL times!\n" ..
  435. "This preserves your Account during server purge.\n" ..
  436. "It cannot be stolen or lost by dying.",
  437. inventory_image = "default_bronze_block.png^default_tool_steelpick.png",
  438. stack_max = 1,
  439. on_use = function(...) return passport.on_use_simple(...) end,
  440. })
  441. -- Keep this in inventory to prevent deletion.
  442. minetest.register_craftitem("passport:passport_adv", {
  443. description = "Key Of Citizenship\n\n" ..
  444. "Keep this in your MAIN inventory at ALL times!\n" ..
  445. "This preserves your Account during server purge.\n" ..
  446. "It cannot be stolen or lost by dying.",
  447. inventory_image = "adv_passport.png",
  448. stack_max = 1,
  449. on_use = function(...) return passport.on_use(...) end,
  450. })
  451. minetest.register_craft({
  452. output = 'passport:passport 1',
  453. recipe = {
  454. {'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'},
  455. },
  456. })
  457. minetest.register_craft({
  458. output = 'passport:passport_adv 1',
  459. recipe = {
  460. {'mese_crystals:zentamine', 'passport:passport', 'mese_crystals:zentamine'},
  461. {'techcrafts:control_logic_unit', 'quartz:quartz_crystal_piece', 'techcrafts:control_logic_unit'},
  462. {'dusts:diamond_shard', 'techcrafts:control_logic_unit', 'default:obsidian_shard'},
  463. },
  464. })
  465. minetest.register_on_player_receive_fields(function(...) return passport.on_receive_fields(...) end)
  466. minetest.register_alias("command_tokens:live_preserver", "passport:passport")
  467. -- It's very common for servers to have a /spawn command. This one is limited.
  468. minetest.register_chatcommand("spawn", {
  469. params = "",
  470. description = "Teleport the player back to the spawnpoint. This only works within 256 meters of spawn.",
  471. privs = {recall=true},
  472. func = function(...)
  473. return passport.exec_spawn(...)
  474. end,
  475. })
  476. -- Let players used to using /recall know that gameplay has changed in this respect.
  477. minetest.register_chatcommand("recall", {
  478. params = "",
  479. description = "Teleport the player back to the spawnpoint. This only works within 256 meters of spawn.",
  480. privs = {recall=true},
  481. func = function(...)
  482. return passport.exec_spawn(...)
  483. end,
  484. })
  485. minetest.register_on_leaveplayer(function(...)
  486. return passport.on_leaveplayer(...)
  487. end)
  488. minetest.register_on_craft(function(...) passport.on_craft(...) end)
  489. passport.registered = true
  490. end
  491. -- This function may be called serveral times on player-login and other times.
  492. -- We cache the result on first call.
  493. passport.player_registered = function(pname)
  494. local all_players = passport.registered_players
  495. -- Read cache if available.
  496. local registered = all_players[pname]
  497. if registered ~= nil then
  498. return registered
  499. end
  500. local player = minetest.get_player_by_name(pname)
  501. if player and player:is_player() then
  502. local inv = player:get_inventory()
  503. if inv then
  504. if inv:contains_item("main", "passport:passport") or inv:contains_item("main", "passport:passport_adv") then
  505. all_players[pname] = true -- Cache for next time.
  506. return true
  507. else
  508. all_players[pname] = false -- Cache for next time.
  509. return false
  510. end
  511. end
  512. end
  513. -- Return false, but don't cache the value -- we could not confirm it!
  514. return false
  515. end
  516. -- This checks (and caches the result!) of whether the player has a KEY OF CITIZENSHIP.
  517. -- Second param is optional, may be nil.
  518. passport.player_has_key = function(pname, player)
  519. local all_players = passport.keyed_players
  520. -- Read cache if available.
  521. local keyed = all_players[pname]
  522. if keyed ~= nil then
  523. return keyed
  524. end
  525. local pref = player or minetest.get_player_by_name(pname)
  526. if pref then
  527. local inv = pref:get_inventory()
  528. if inv then
  529. if inv:contains_item("main", "passport:passport_adv") then
  530. all_players[pname] = true -- Cache for next time.
  531. return true
  532. else
  533. all_players[pname] = false -- Cache for next time.
  534. return false
  535. end
  536. end
  537. end
  538. -- Return false, but don't cache the value -- we could not confirm it!
  539. return false
  540. end
  541. function passport.on_leaveplayer(player, timeout)
  542. local pname = player:get_player_name()
  543. -- Remove cache of player registration.
  544. passport.registered_players[pname] = nil
  545. passport.keyed_players[pname] = nil
  546. end
  547. if minetest.get_modpath("reload") then
  548. local c = "passport:core"
  549. local f = passport.modpath .. "/init.lua"
  550. if not reload.file_registered(c) then
  551. reload.register_file(c, f, false)
  552. end
  553. end