init.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  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. dofile(shout.modpath .. "/builtin_tips.lua")
  15. function shout.hint_add(name, param)
  16. name = name:trim()
  17. param = param:trim()
  18. param = param:gsub("%s+", " ")
  19. if param:len() == 0 then
  20. minetest.chat_send_player(name, "# Server: Not adding an empty hint message.")
  21. return
  22. end
  23. minetest.chat_send_player(name, "# Server: Will add hint message: \"" .. param .. "\".")
  24. -- Will store all hints loaded from file.
  25. local loaded_hints = {}
  26. -- Load all hints from world datafile.
  27. local file, err = io.open(shout.datafile, "r")
  28. if err then
  29. minetest.chat_send_player(name, "# Server: Failed to open \"" .. shout.datafile .. "\" for reading: " .. err)
  30. else
  31. local datastring = file:read("*all")
  32. if datastring and datastring ~= "" then
  33. local records = string.split(datastring, "\n")
  34. for record_number, record in ipairs(records) do
  35. local data = record:trim()
  36. if data:len() > 0 then
  37. table.insert(loaded_hints, data)
  38. end
  39. end
  40. end
  41. file:close()
  42. end
  43. minetest.chat_send_player(name, "# Server: Loaded " .. #loaded_hints .. " previously saved hints.")
  44. -- Add the new hint message.
  45. table.insert(loaded_hints, param)
  46. -- Custom file format. minetest.serialize() is unusable for large tables.
  47. local datastring = ""
  48. for k, record in ipairs(loaded_hints) do
  49. datastring = datastring .. record .. "\n"
  50. end
  51. -- Now save all non-builtin hints back to the file.
  52. local file, err = io.open(shout.datafile, "w")
  53. if err then
  54. minetest.chat_send_player(name, "# Server: Failed to open \"" .. shout.datafile .. "\" for writing: " .. err)
  55. else
  56. file:write(datastring)
  57. file:close()
  58. end
  59. -- Recombine both tables.
  60. shout.HINTS = {}
  61. for k, v in ipairs(shout.BUILTIN_HINTS) do
  62. table.insert(shout.HINTS, v)
  63. end
  64. for k, v in ipairs(loaded_hints) do
  65. table.insert(shout.HINTS, v)
  66. end
  67. end
  68. -- Load any saved hints whenever mod is reloaded or server starts.
  69. do
  70. -- Will store all hints loaded from file.
  71. local loaded_hints = {}
  72. -- Load all hints from world datafile.
  73. local file, err = io.open(shout.datafile, "r")
  74. if err then
  75. if not err:find("No such file") then
  76. minetest.log("error", "Failed to open " .. shout.datafile .. " for reading: " .. err)
  77. end
  78. else
  79. local datastring = file:read("*all")
  80. if datastring and datastring ~= "" then
  81. local records = string.split(datastring, "\n")
  82. for record_number, record in ipairs(records) do
  83. local data = record:trim()
  84. if data:len() > 0 then
  85. table.insert(loaded_hints, data)
  86. end
  87. end
  88. end
  89. file:close()
  90. end
  91. -- Recombine both tables.
  92. shout.HINTS = {}
  93. for k, v in ipairs(shout.BUILTIN_HINTS) do
  94. table.insert(shout.HINTS, v)
  95. end
  96. for k, v in ipairs(loaded_hints) do
  97. table.insert(shout.HINTS, v)
  98. end
  99. end
  100. local function get_non_admin_players()
  101. local t = minetest.get_connected_players()
  102. local b = {}
  103. for k, v in ipairs(t) do
  104. if not minetest.check_player_privs(v, "server") then
  105. b[#b + 1] = v
  106. end
  107. end
  108. return b
  109. end
  110. local HINT_DELAY_MIN = 60*45
  111. local HINT_DELAY_MAX = 60*90
  112. function shout.print_hint()
  113. local HINTS = shout.HINTS
  114. -- Only if hints are available.
  115. if #HINTS > 0 then
  116. -- Don't speak to an empty room.
  117. local players = get_non_admin_players()
  118. if #players > 0 then
  119. minetest.chat_send_all("# Server: " .. HINTS[math_random(1, #HINTS)])
  120. end
  121. end
  122. -- Print another hint after some delay.
  123. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  124. end
  125. -- Shout a message.
  126. function shout.shout(name, param)
  127. param = string.trim(param)
  128. if #param < 1 then
  129. minetest.chat_send_player(name, "# Server: No message specified.")
  130. easyvend.sound_error(name)
  131. return
  132. end
  133. if command_tokens.mute.player_muted(name) then
  134. minetest.chat_send_player(name, "# Server: You cannot shout while gagged!")
  135. easyvend.sound_error(name)
  136. return
  137. end
  138. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  139. if chat_core.check_language(name, param) then return end
  140. local mk = chat_core.generate_coord_string(name)
  141. local stats = chat_core.player_status(name)
  142. local dname = rename.gpn(name)
  143. local players = minetest.get_connected_players()
  144. for _, player in ipairs(players) do
  145. local target_name = player:get_player_name() or ""
  146. if not chat_controls.player_ignored_shout(target_name, name) or target_name == name then
  147. chat_core.alert_player_sound(target_name)
  148. minetest.chat_send_player(target_name, stats .. "<!" .. chat_core.nametag_color .. dname .. WHITE .. mk .. "!> " .. SHOUT_COLOR .. param)
  149. end
  150. end
  151. afk.reset_timeout(name)
  152. chat_logging.log_public_shout(name, stats, param, mk)
  153. end
  154. -- Get player's current "in-memory" channel name, or nil.
  155. function shout.player_channel(pname)
  156. if shout.players[pname] and shout.players[pname] ~= "" then
  157. return shout.players[pname]
  158. end
  159. end
  160. -- Get list of all players in a channel.
  161. function shout.channel_players(channel)
  162. local players = minetest.get_connected_players()
  163. local result = {}
  164. for k, v in ipairs(players) do
  165. local n = v:get_player_name()
  166. if shout.players[n] and shout.players[n] == channel then
  167. result[#result+1] = n
  168. end
  169. end
  170. return result
  171. end
  172. -- Use this only to send server messages to all players in a channel.
  173. -- This bypasses players' chat filters.
  174. function shout.notify_channel(channel, message)
  175. local players = minetest.get_connected_players()
  176. -- Send message to all players in the same channel.
  177. for k, v in ipairs(players) do
  178. local n = v:get_player_name()
  179. if shout.players[n] and shout.players[n] == channel then
  180. minetest.chat_send_player(n, TEAM_COLOR .. message)
  181. end
  182. end
  183. end
  184. -- let player join, leave channels
  185. function shout.channel(name, param, on_join, on_leave)
  186. param = string.trim(param)
  187. local player = minetest.get_player_by_name(name)
  188. if not player or not player:is_player() then
  189. return
  190. end
  191. if shout.players[name] and shout.players[name] ~= "" and param ~= shout.players[name] then
  192. shout.notify_channel(shout.players[name],
  193. "# Server: User <" .. rename.gpn(name) .. "> has left channel '" ..
  194. shout.players[name] .. "'.")
  195. end
  196. if param == "" then
  197. if not on_join then
  198. if shout.players[name] then
  199. minetest.chat_send_player(name, "# Server: Channel cleared.")
  200. else
  201. minetest.chat_send_player(name, "# Server: Not on any channel.")
  202. end
  203. end
  204. shout.players[name] = nil
  205. if not on_leave then
  206. player:get_meta():set_string("active_channel", "")
  207. end
  208. return
  209. end
  210. if not on_join then
  211. if shout.players[name] and shout.players[name] == param then
  212. minetest.chat_send_player(name,
  213. "# Server: Already on channel '" .. param .. "'.")
  214. return
  215. end
  216. end
  217. -- Require channel names to match specific format.
  218. if not string.find(param, "^[_%w]+$") then
  219. minetest.chat_send_player(name,
  220. "# Server: Invalid channel name! Use only alphanumeric characters and underscores.")
  221. easyvend.sound_error(name)
  222. return
  223. end
  224. -- Only print this if called by explicit chatcommand.
  225. if not on_join then
  226. minetest.chat_send_player(name, "# Server: Chat channel set to '" .. param .. "'.")
  227. end
  228. shout.players[name] = param
  229. player:get_meta():set_string("active_channel", param)
  230. shout.notify_channel(shout.players[name],
  231. "# Server: User <" .. rename.gpn(name) .. "> has joined channel '" ..
  232. shout.players[name] .. "'.")
  233. end
  234. -- let player put a message onto a channel
  235. function shout.x(name, param)
  236. param = string.trim(param)
  237. if not shout.players[name] then
  238. minetest.chat_send_player(name, "# Server: You have not specified a channel.")
  239. easyvend.sound_error(name)
  240. return
  241. end
  242. if #param < 1 then
  243. minetest.chat_send_player(name, "# Server: No message specified.")
  244. easyvend.sound_error(name)
  245. return
  246. end
  247. -- Allow player to use channel speak even while gagged.
  248. -- Rational: if the gagged player is on a channel with others,
  249. -- then probably they're in a group together, or are related.
  250. -- Chat between such shouldn't be blocked.
  251. --[[
  252. if command_tokens.mute.player_muted(name) then
  253. minetest.chat_send_player(name, "# Server: You cannot talk while gagged!")
  254. easyvend.sound_error(name)
  255. return
  256. end
  257. --]]
  258. local stats = chat_core.player_status(name)
  259. local dname = rename.gpn(name)
  260. local channel = shout.players[name]
  261. local players = minetest.get_connected_players()
  262. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  263. if chat_core.check_language(name, param, channel) then return end
  264. local mk = chat_core.generate_coord_string(name)
  265. -- Send message to all players in the same channel.
  266. -- The player who sent the message always receives it.
  267. for k, v in ipairs(players) do
  268. local n = v:get_player_name()
  269. if shout.players[n] and shout.players[n] == channel then
  270. local ignored = false
  271. -- Don't send teamchat if player is ignored.
  272. if chat_controls.player_ignored(n, name) then
  273. ignored = true
  274. end
  275. if not ignored then
  276. minetest.chat_send_player(n, stats .. "<!" .. chat_core.nametag_color .. rename.gpn(name) .. WHITE .. mk .. "!> " .. TEAM_COLOR .. param)
  277. end
  278. end
  279. end
  280. --minetest.chat_send_all(SHOUT_COLOR .. "<!" .. dname .. mk .. "!> " .. param)
  281. --chat_logging.log_public_shout(name, param, shout.channelmk)
  282. chat_logging.log_team_chat(name, stats, param, channel)
  283. afk.reset_timeout(name)
  284. end
  285. -- Join channel on login, if no channel currently set.
  286. function shout.join_channel(player)
  287. local pname = player:get_player_name()
  288. if not shout.player_channel(pname) then
  289. local channel = player:get_meta():get_string("active_channel")
  290. if channel and channel ~= "" then
  291. minetest.after(0, function() shout.channel(pname, channel, true) end)
  292. end
  293. end
  294. end
  295. -- Leave channel on logout, if a channel is currently set.
  296. function shout.leave_channel(player)
  297. local pname = player:get_player_name()
  298. local curchan = shout.player_channel(pname)
  299. if curchan and curchan ~= "" then
  300. shout.channel(pname, "", false, true)
  301. end
  302. end
  303. if not shout.run_once then
  304. -- Post 'startup complete' message only in multiplayer.
  305. if not minetest.is_singleplayer() then
  306. minetest.after(0, function()
  307. minetest.chat_send_all("# Server: Startup complete.")
  308. end)
  309. end
  310. minetest.register_chatcommand("shout", {
  311. params = "<message>",
  312. description = "Yell a message to everyone on the server. You can also prepend your chat with '!'.",
  313. privs = {shout=true},
  314. func = function(name, param)
  315. shout.shout(name, param)
  316. return true
  317. end,
  318. })
  319. minetest.register_chatcommand("channel", {
  320. params = "<id>",
  321. description = "Set channel name.",
  322. privs = {shout=true},
  323. func = function(name, param)
  324. shout.channel(name, param)
  325. return true
  326. end,
  327. })
  328. minetest.register_chatcommand("x", {
  329. params = "<message>",
  330. description = "Speak on current channel.",
  331. privs = {shout=true},
  332. func = function(name, param)
  333. shout.x(name, param)
  334. return true
  335. end,
  336. })
  337. minetest.register_chatcommand("hint_add", {
  338. params = "<message>",
  339. description = "Add a hint message to the hint list. Example between quotes: '/hint_add This is a hint message. Another sentance.'",
  340. privs = {server=true},
  341. func = function(name, param)
  342. shout.hint_add(name, param)
  343. return true
  344. end,
  345. })
  346. -- Start hints. A hint is written into public chat every so often.
  347. -- But not too often, or it becomes annoying.
  348. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  349. minetest.register_on_joinplayer(function(...)
  350. return shout.join_channel(...) end)
  351. minetest.register_on_leaveplayer(function(...)
  352. return shout.leave_channel(...) end)
  353. local c = "shout:core"
  354. local f = shout.modpath .. "/init.lua"
  355. reload.register_file(c, f, false)
  356. shout.run_once = true
  357. end