init.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. if not minetest.global_exists("shout") then shout = {} end
  2. shout.modpath = minetest.get_modpath("shout")
  3. shout.worldpath = minetest.get_worldpath()
  4. shout.datafile = shout.worldpath .. "/hints.txt"
  5. shout.players = shout.players or {}
  6. -- Localize for performance.
  7. local math_floor = math.floor
  8. local math_random = math.random
  9. local SHOUT_COLOR = core.get_color_escape_sequence("#ff2a00")
  10. local TEAM_COLOR = core.get_color_escape_sequence("#a8ff00")
  11. local WHITE = core.get_color_escape_sequence("#ffffff")
  12. shout.HINTS = {}
  13. shout.BUILTIN_HINTS = {
  14. "You can ignore players who create drama or ruin chat by using the chat-filter interface, accessed through the Key of Citizenship.",
  15. "Mobs sometimes place blocks in protected areas. This is not griefing, because the blocks are not protected. Anyone may remove them.",
  16. "You may use the '/r <message>' command to quickly reply via PM to the last player to send you a PM.",
  17. "Strenuous activity increases your exhaustion and therefore hunger over time. This includes swimming, mining, or sprinting.",
  18. "Fallen blocks can be dug by anyone, even in protected areas.",
  19. "Use /channel to join a group PM channel, and /x to communicate to the channel's members.",
  20. "Stamina comes back faster when you are not hungry and resting in one place.",
  21. "Use the /players command when part of a group PM channel to see others on your channel.",
  22. "The ID Marker item hides your name, and obfuscates the public bones report when you die or pick bones.",
  23. "Most plants grow poorly near ice. Some plants won't grow at all.",
  24. "You can test protection without harming yourself by punching nodes with a stick.",
  25. "Falling nodes dropped above farms and gardens will do no damage to protected plants.",
  26. "Use teleports, gateways, and flameportals to get around quickly.",
  27. "Place city marker blocks to mark your land as part of the city, to help suppress unwanted PvP.",
  28. "Some rare foods heal hearts immediately. You may prefer to save these for combat situations.",
  29. "Most trees will refuse to grow underground. Firetrees do not suffer this limitation.",
  30. "You can get information about a node using the node inspector tool.",
  31. "To maintain your account, always keep a passport token (Proof or Key) in your main inventory.",
  32. "Prevent griefing by protecting your structures. Serious builders are advised to protect the land around their builds as well.",
  33. "Protect your bases to prevent griefing by others.",
  34. "Protection stops most types of environment damage, but does not prevent damage from lava.",
  35. "Certain blocks are vulnerable to lava griefing even when protected, such as stone and cobble.",
  36. "Certain foods refresh stamina, and can be used to give your avatar an extra endurance boost.",
  37. "Passive healing is very slow for serious injuries, but quick for light wounds.",
  38. "Travel speed is affected by the surfaces you travel on. Prefer using roads and avoid deep snow.",
  39. "Flowers, plants, and saplings can be found in the deep wastes far from the city.",
  40. "Use the /mapfix command to correct issues with lighting or liquid flow. See /help mapfix.",
  41. "Lava does not respect protection and is very dangerous!",
  42. "You can place water for farms above sea level by melting ice with torches or fire.",
  43. "You can travel over flat snow or ice quickly using a sled.",
  44. "Icemen are the primary source of mossy cobble, which is used as a fuel in some teleports.",
  45. "You can place a public bed (not ownable by anyone) by holding 'E' when you place it.",
  46. "You can place nearly any block as a falling node by holding 'E' when you place it, with air below.",
  47. "The brimstone ocean contains a few resources not found anywhere else; however the entire underworld is very dangerous.",
  48. "Locked chests are unlocked (and items may be stolen) if the chest lid is open.",
  49. "When you die, sometimes your bones are placed in odd places. Look around very carefully before giving up.",
  50. "Starvation will drain your health down to half a heart, but will not kill you.",
  51. "Sleep in a bed regularly to keep your respawn position from expiring after death.",
  52. "Use bandages to keep your health full. Stamina and hunger will benefit.",
  53. "Being hurt reduces your ability to sprint and jump.",
  54. "Prefer traveling by road or teleport when possible for faster transit times.",
  55. "Your speech is published live on channel 40,000. Please do not swear.",
  56. "Bonebox deployment is part of this adventure.",
  57. "Information-latency can be a nuisance.",
  58. "A diving helmet and canisters of compressed air will allow you to stay underwater longer.",
  59. "Lava causes strong, localized convection currents.",
  60. "Water is wet.",
  61. "Beware the guards of the Deep.",
  62. "This is a useless message.",
  63. "Obsidian Gateways and Flameportals are fragile methods of transportation. Protect them!",
  64. "This is a confused message.",
  65. "This message doesn't have any meaning.",
  66. "Pay no attention to the man behind the curtain.",
  67. "Shears and silver picks can sometimes be used to obtain materials or resources that other tools cannot.",
  68. "Walk softly and carry a big stick.",
  69. "Punching an itemframe or pedestal with a stick will restore missing item entities.",
  70. "Items placed in itemframes or pedestals may occasionally become invisible. Punch with a stick to restore them.",
  71. "Dry shrubs and torches (lit or not) may be crafted into sticks.",
  72. "New player short on fuel? You can burn wooden armor in stone furnaces.",
  73. "That's a Waitabit Tree. Don't go near a Waitabit Tree. They grab you.",
  74. "Most trees are sensitive to cold conditions and may refuse to grow. The Firetree is particularly ornery.",
  75. "All positions printed in server chat are realm-local.",
  76. "Found a bug? You can send in-game email to MustTest to report it.",
  77. "This sentence is false.",
  78. "You can send game-balance suggestions to MustTest using the in-game email system.",
  79. "Mr. Momoa, your trailer's on fire.",
  80. "You can use a trade booth to buy and sell items remotely, for shops set up to support it.",
  81. "If you get stuck somewhere, you can use /emergency_recall to get out. But beware its cost.",
  82. }
  83. function shout.hint_add(name, param)
  84. name = name:trim()
  85. param = param:trim()
  86. param = param:gsub("%s+", " ")
  87. if param:len() == 0 then
  88. minetest.chat_send_player(name, "# Server: Not adding an empty hint message.")
  89. return
  90. end
  91. minetest.chat_send_player(name, "# Server: Will add hint message: \"" .. param .. "\".")
  92. -- Will store all hints loaded from file.
  93. local loaded_hints = {}
  94. -- Load all hints from world datafile.
  95. local file, err = io.open(shout.datafile, "r")
  96. if err then
  97. minetest.chat_send_player(name, "# Server: Failed to open \"" .. shout.datafile .. "\" for reading: " .. err)
  98. else
  99. local datastring = file:read("*all")
  100. if datastring and datastring ~= "" then
  101. local records = string.split(datastring, "\n")
  102. for record_number, record in ipairs(records) do
  103. local data = record:trim()
  104. if data:len() > 0 then
  105. table.insert(loaded_hints, data)
  106. end
  107. end
  108. end
  109. file:close()
  110. end
  111. minetest.chat_send_player(name, "# Server: Loaded " .. #loaded_hints .. " previously saved hints.")
  112. -- Add the new hint message.
  113. table.insert(loaded_hints, param)
  114. -- Custom file format. minetest.serialize() is unusable for large tables.
  115. local datastring = ""
  116. for k, record in ipairs(loaded_hints) do
  117. datastring = datastring .. record .. "\n"
  118. end
  119. -- Now save all non-builtin hints back to the file.
  120. local file, err = io.open(shout.datafile, "w")
  121. if err then
  122. minetest.chat_send_player(name, "# Server: Failed to open \"" .. shout.datafile .. "\" for writing: " .. err)
  123. else
  124. file:write(datastring)
  125. file:close()
  126. end
  127. -- Recombine both tables.
  128. shout.HINTS = {}
  129. for k, v in ipairs(shout.BUILTIN_HINTS) do
  130. table.insert(shout.HINTS, v)
  131. end
  132. for k, v in ipairs(loaded_hints) do
  133. table.insert(shout.HINTS, v)
  134. end
  135. end
  136. -- Load any saved hints whenever mod is reloaded or server starts.
  137. do
  138. -- Will store all hints loaded from file.
  139. local loaded_hints = {}
  140. -- Load all hints from world datafile.
  141. local file, err = io.open(shout.datafile, "r")
  142. if err then
  143. if not err:find("No such file") then
  144. minetest.log("error", "Failed to open " .. shout.datafile .. " for reading: " .. err)
  145. end
  146. else
  147. local datastring = file:read("*all")
  148. if datastring and datastring ~= "" then
  149. local records = string.split(datastring, "\n")
  150. for record_number, record in ipairs(records) do
  151. local data = record:trim()
  152. if data:len() > 0 then
  153. table.insert(loaded_hints, data)
  154. end
  155. end
  156. end
  157. file:close()
  158. end
  159. -- Recombine both tables.
  160. shout.HINTS = {}
  161. for k, v in ipairs(shout.BUILTIN_HINTS) do
  162. table.insert(shout.HINTS, v)
  163. end
  164. for k, v in ipairs(loaded_hints) do
  165. table.insert(shout.HINTS, v)
  166. end
  167. end
  168. local HINT_DELAY_MIN = 60*45
  169. local HINT_DELAY_MAX = 60*90
  170. function shout.print_hint()
  171. local HINTS = shout.HINTS
  172. -- Only if hints are available.
  173. if #HINTS > 0 then
  174. -- Don't speak to an empty room.
  175. local players = minetest.get_connected_players()
  176. if #players > 0 then
  177. minetest.chat_send_all("# Server: " .. HINTS[math_random(1, #HINTS)])
  178. end
  179. end
  180. -- Print another hint after some delay.
  181. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  182. end
  183. -- Shout a message.
  184. function shout.shout(name, param)
  185. param = string.trim(param)
  186. if #param < 1 then
  187. minetest.chat_send_player(name, "# Server: No message specified.")
  188. easyvend.sound_error(name)
  189. return
  190. end
  191. if command_tokens.mute.player_muted(name) then
  192. minetest.chat_send_player(name, "# Server: You cannot shout while gagged!")
  193. easyvend.sound_error(name)
  194. return
  195. end
  196. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  197. if chat_core.check_language(name, param) then return end
  198. local mk = chat_core.generate_coord_string(name)
  199. local stats = chat_core.player_status(name)
  200. local dname = rename.gpn(name)
  201. local players = minetest.get_connected_players()
  202. for _, player in ipairs(players) do
  203. local target_name = player:get_player_name() or ""
  204. if not chat_controls.player_ignored_shout(target_name, name) or target_name == name then
  205. chat_core.alert_player_sound(target_name)
  206. minetest.chat_send_player(target_name, stats .. "<!" .. chat_core.nametag_color .. dname .. WHITE .. mk .. "!> " .. SHOUT_COLOR .. param)
  207. end
  208. end
  209. afk.reset_timeout(name)
  210. chat_logging.log_public_shout(name, stats, param, mk)
  211. end
  212. -- Get player's current "in-memory" channel name, or nil.
  213. function shout.player_channel(pname)
  214. if shout.players[pname] and shout.players[pname] ~= "" then
  215. return shout.players[pname]
  216. end
  217. end
  218. -- Get list of all players in a channel.
  219. function shout.channel_players(channel)
  220. local players = minetest.get_connected_players()
  221. local result = {}
  222. for k, v in ipairs(players) do
  223. local n = v:get_player_name()
  224. if shout.players[n] and shout.players[n] == channel then
  225. result[#result+1] = n
  226. end
  227. end
  228. return result
  229. end
  230. -- Use this only to send server messages to all players in a channel.
  231. -- This bypasses players' chat filters.
  232. function shout.notify_channel(channel, message)
  233. local players = minetest.get_connected_players()
  234. -- Send message to all players in the same channel.
  235. for k, v in ipairs(players) do
  236. local n = v:get_player_name()
  237. if shout.players[n] and shout.players[n] == channel then
  238. minetest.chat_send_player(n, TEAM_COLOR .. message)
  239. end
  240. end
  241. end
  242. -- let player join, leave channels
  243. function shout.channel(name, param, on_join, on_leave)
  244. param = string.trim(param)
  245. local player = minetest.get_player_by_name(name)
  246. if not player or not player:is_player() then
  247. return
  248. end
  249. if shout.players[name] and shout.players[name] ~= "" and param ~= shout.players[name] then
  250. shout.notify_channel(shout.players[name],
  251. "# Server: User <" .. rename.gpn(name) .. "> has left channel '" ..
  252. shout.players[name] .. "'.")
  253. end
  254. if param == "" then
  255. if not on_join then
  256. if shout.players[name] then
  257. minetest.chat_send_player(name, "# Server: Channel cleared.")
  258. else
  259. minetest.chat_send_player(name, "# Server: Not on any channel.")
  260. end
  261. end
  262. shout.players[name] = nil
  263. if not on_leave then
  264. player:get_meta():set_string("active_channel", "")
  265. end
  266. return
  267. end
  268. if not on_join then
  269. if shout.players[name] and shout.players[name] == param then
  270. minetest.chat_send_player(name,
  271. "# Server: Already on channel '" .. param .. "'.")
  272. return
  273. end
  274. end
  275. -- Require channel names to match specific format.
  276. if not string.find(param, "^[_%w]+$") then
  277. minetest.chat_send_player(name,
  278. "# Server: Invalid channel name! Use only alphanumeric characters and underscores.")
  279. easyvend.sound_error(name)
  280. return
  281. end
  282. -- Only print this if called by explicit chatcommand.
  283. if not on_join then
  284. minetest.chat_send_player(name, "# Server: Chat channel set to '" .. param .. "'.")
  285. end
  286. shout.players[name] = param
  287. player:get_meta():set_string("active_channel", param)
  288. shout.notify_channel(shout.players[name],
  289. "# Server: User <" .. rename.gpn(name) .. "> has joined channel '" ..
  290. shout.players[name] .. "'.")
  291. end
  292. -- let player put a message onto a channel
  293. function shout.x(name, param)
  294. param = string.trim(param)
  295. if not shout.players[name] then
  296. minetest.chat_send_player(name, "# Server: You have not specified a channel.")
  297. easyvend.sound_error(name)
  298. return
  299. end
  300. if #param < 1 then
  301. minetest.chat_send_player(name, "# Server: No message specified.")
  302. easyvend.sound_error(name)
  303. return
  304. end
  305. -- Allow player to use channel speak even while gagged.
  306. -- Rational: if the gagged player is on a channel with others,
  307. -- then probably they're in a group together, or are related.
  308. -- Chat between such shouldn't be blocked.
  309. --[[
  310. if command_tokens.mute.player_muted(name) then
  311. minetest.chat_send_player(name, "# Server: You cannot talk while gagged!")
  312. easyvend.sound_error(name)
  313. return
  314. end
  315. --]]
  316. local stats = chat_core.player_status(name)
  317. local dname = rename.gpn(name)
  318. local channel = shout.players[name]
  319. local players = minetest.get_connected_players()
  320. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  321. if chat_core.check_language(name, param, channel) then return end
  322. local mk = chat_core.generate_coord_string(name)
  323. -- Send message to all players in the same channel.
  324. -- The player who sent the message always receives it.
  325. for k, v in ipairs(players) do
  326. local n = v:get_player_name()
  327. if shout.players[n] and shout.players[n] == channel then
  328. local ignored = false
  329. -- Don't send teamchat if player is ignored.
  330. if chat_controls.player_ignored(n, name) then
  331. ignored = true
  332. end
  333. if not ignored then
  334. minetest.chat_send_player(n, stats .. "<!" .. chat_core.nametag_color .. rename.gpn(name) .. WHITE .. mk .. "!> " .. TEAM_COLOR .. param)
  335. end
  336. end
  337. end
  338. --minetest.chat_send_all(SHOUT_COLOR .. "<!" .. dname .. mk .. "!> " .. param)
  339. --chat_logging.log_public_shout(name, param, shout.channelmk)
  340. chat_logging.log_team_chat(name, stats, param, channel)
  341. afk.reset_timeout(name)
  342. end
  343. -- Join channel on login, if no channel currently set.
  344. function shout.join_channel(player)
  345. local pname = player:get_player_name()
  346. if not shout.player_channel(pname) then
  347. local channel = player:get_meta():get_string("active_channel")
  348. if channel and channel ~= "" then
  349. minetest.after(0, function() shout.channel(pname, channel, true) end)
  350. end
  351. end
  352. end
  353. -- Leave channel on logout, if a channel is currently set.
  354. function shout.leave_channel(player)
  355. local pname = player:get_player_name()
  356. local curchan = shout.player_channel(pname)
  357. if curchan and curchan ~= "" then
  358. shout.channel(pname, "", false, true)
  359. end
  360. end
  361. if not shout.run_once then
  362. -- Post 'startup complete' message only in multiplayer.
  363. if not minetest.is_singleplayer() then
  364. minetest.after(0, function()
  365. minetest.chat_send_all("# Server: Startup complete.")
  366. end)
  367. end
  368. minetest.register_chatcommand("shout", {
  369. params = "<message>",
  370. description = "Yell a message to everyone on the server. You can also prepend your chat with '!'.",
  371. privs = {shout=true},
  372. func = function(name, param)
  373. shout.shout(name, param)
  374. return true
  375. end,
  376. })
  377. minetest.register_chatcommand("channel", {
  378. params = "<id>",
  379. description = "Set channel name.",
  380. privs = {shout=true},
  381. func = function(name, param)
  382. shout.channel(name, param)
  383. return true
  384. end,
  385. })
  386. minetest.register_chatcommand("x", {
  387. params = "<message>",
  388. description = "Speak on current channel.",
  389. privs = {shout=true},
  390. func = function(name, param)
  391. shout.x(name, param)
  392. return true
  393. end,
  394. })
  395. minetest.register_chatcommand("hint_add", {
  396. params = "<message>",
  397. description = "Add a hint message to the hint list. Example between quotes: '/hint_add This is a hint message. Another sentance.'",
  398. privs = {server=true},
  399. func = function(name, param)
  400. shout.hint_add(name, param)
  401. return true
  402. end,
  403. })
  404. -- Start hints. A hint is written into public chat every so often.
  405. -- But not too often, or it becomes annoying.
  406. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  407. minetest.register_on_joinplayer(function(...)
  408. return shout.join_channel(...) end)
  409. minetest.register_on_leaveplayer(function(...)
  410. return shout.leave_channel(...) end)
  411. local c = "shout:core"
  412. local f = shout.modpath .. "/init.lua"
  413. reload.register_file(c, f, false)
  414. shout.run_once = true
  415. end