init.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. shout = shout or {}
  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. "Players are not sent to jail for kills committed with ranged weapons or other indirect munitions.",
  33. "Prevent griefing by protecting your structures. Serious builders are advised to protect the land around their builds as well.",
  34. "Protect your bases to prevent griefing by others.",
  35. "Protection stops most types of environment damage, but does not prevent damage from lava.",
  36. "Certain blocks are vulnerable to lava griefing even when protected, such as stone and cobble.",
  37. "Certain foods refresh stamina, and can be used to give your avatar an extra endurance boost.",
  38. "A hurt player will usually heal slowly, up to the last two hearts. Use bandages to speed this up.",
  39. "Travel speed is affected by the surfaces you travel on. Prefer using roads and avoid deep snow.",
  40. "Flowers, plants, and saplings can be found in the deep wastes far from the city.",
  41. "Use the /mapfix command to correct issues with lighting or liquid flow. See /help mapfix.",
  42. "Lava does not respect protection and is very dangerous; however lava cannot exist above sea level.",
  43. "You can place water for farms above sea level by melting ice with torches or fire.",
  44. "You can travel over flat snow or ice quickly using a sled.",
  45. "Icemen are the primary source of mossy cobble, which is used as a fuel in some teleports.",
  46. "You can place a public bed (not ownable by anyone) by holding 'E' when you place it.",
  47. "You can place nearly any block as a falling node by holding 'E' when you place it, with air below.",
  48. "The brimstone ocean contains a few resources not found anywhere else; however the entire underworld is very dangerous.",
  49. "Locked chests are unlocked (and items may be stolen) if the chest lid is open.",
  50. "When you die, sometimes your bones are placed in odd places. Look around very carefully before giving up.",
  51. "Starvation will drain your health down to half a heart, but will not kill you.",
  52. "You can sleep in a bed to set your respawn position on death.",
  53. "Use bandages to keep your health full. Stamina and hunger will benefit.",
  54. "Being hurt reduces your ability to sprint and jump.",
  55. "Prefer traveling by road or teleport when possible for faster transit times.",
  56. "Your speech is published live on channel 40,000. Please do not swear.",
  57. "Bonebox deployment is part of this adventure.",
  58. "Information-latency can be a nuisance.",
  59. "A diving helmet and canisters of compressed air will allow you to stay underwater longer.",
  60. "Lava causes strong, localized convection currents.",
  61. "Water is wet.",
  62. "Beware the guards of the Deep.",
  63. "This is a useless message.",
  64. "Obsidian Gateways and Flameportals are fragile methods of transportation. Protect them!",
  65. "This is a confused message.",
  66. "This message doesn't have any meaning.",
  67. "Pay no attention to the man behind the curtain.",
  68. "Shears and silver picks can sometimes be used to obtain materials or resources that other tools cannot.",
  69. "Walk softly and carry a big stick.",
  70. "Punching an itemframe or pedestal with a stick will restore missing item entities.",
  71. "Items placed in itemframes or pedestals may occasionally become invisible. Punch with a stick to restore them.",
  72. "Dry shrubs and torches (lit or not) may be crafted into sticks.",
  73. "New player short on fuel? You can burn wooden armor in stone furnaces.",
  74. "That's a Waitabit Tree. Don't go near a Waitabit Tree. They grab you.",
  75. "Most trees are sensitive to cold conditions and may refuse to grow. The Firetree is particularly ornery.",
  76. "All positions given in server messages are in realm-coordinates. Absolute (F5) coordinates are not used.",
  77. "Found a bug? You can send in-game email to MustTest to report it.",
  78. "This sentence is false.",
  79. "You can send game-balance suggestions to MustTest using the in-game email system.",
  80. "Mr. Momoa, your trailer's on fire.",
  81. "You can use a trade booth to buy and sell items remotely, for shops set up to support it.",
  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. minetest.log("error", "Failed to open " .. shout.datafile .. " for reading: " .. err)
  144. else
  145. local datastring = file:read("*all")
  146. if datastring and datastring ~= "" then
  147. local records = string.split(datastring, "\n")
  148. for record_number, record in ipairs(records) do
  149. local data = record:trim()
  150. if data:len() > 0 then
  151. table.insert(loaded_hints, data)
  152. end
  153. end
  154. end
  155. file:close()
  156. end
  157. -- Recombine both tables.
  158. shout.HINTS = {}
  159. for k, v in ipairs(shout.BUILTIN_HINTS) do
  160. table.insert(shout.HINTS, v)
  161. end
  162. for k, v in ipairs(loaded_hints) do
  163. table.insert(shout.HINTS, v)
  164. end
  165. end
  166. local HINT_DELAY_MIN = 60*45
  167. local HINT_DELAY_MAX = 60*90
  168. function shout.print_hint()
  169. local HINTS = shout.HINTS
  170. -- Only if hints are available.
  171. if #HINTS > 0 then
  172. minetest.chat_send_all("# Server: " .. HINTS[math_random(1, #HINTS)])
  173. end
  174. -- Print another hint after some delay.
  175. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  176. end
  177. -- Shout a message.
  178. function shout.shout(name, param)
  179. param = string.trim(param)
  180. if #param < 1 then
  181. minetest.chat_send_player(name, "# Server: No message specified.")
  182. easyvend.sound_error(name)
  183. return
  184. end
  185. if command_tokens.mute.player_muted(name) then
  186. minetest.chat_send_player(name, "# Server: You cannot shout while gagged!")
  187. easyvend.sound_error(name)
  188. return
  189. end
  190. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  191. if chat_core.check_language(name, param) then return end
  192. local mk = chat_core.generate_coord_string(name)
  193. local dname = rename.gpn(name)
  194. local players = minetest.get_connected_players()
  195. for _, player in ipairs(players) do
  196. local target_name = player:get_player_name() or ""
  197. if not chat_controls.player_ignored_shout(target_name, name) or target_name == name then
  198. chat_core.alert_player_sound(target_name)
  199. minetest.chat_send_player(target_name, "<!" .. chat_core.nametag_color .. dname .. WHITE .. mk .. "!> " .. SHOUT_COLOR .. param)
  200. end
  201. end
  202. afk_removal.reset_timeout(name)
  203. chat_logging.log_public_shout(name, param, mk)
  204. end
  205. function shout.player_channel(pname)
  206. if shout.players[pname] and shout.players[pname] ~= "" then
  207. return shout.players[pname]
  208. end
  209. end
  210. function shout.channel_players(channel)
  211. local players = minetest.get_connected_players()
  212. local result = {}
  213. for k, v in ipairs(players) do
  214. local n = v:get_player_name()
  215. if shout.players[n] and shout.players[n] == channel then
  216. result[#result+1] = n
  217. end
  218. end
  219. return result
  220. end
  221. function shout.notify_channel(channel, message)
  222. local players = minetest.get_connected_players()
  223. -- Send message to all players in the same channel.
  224. for k, v in ipairs(players) do
  225. local n = v:get_player_name()
  226. if shout.players[n] and shout.players[n] == channel then
  227. minetest.chat_send_player(n, TEAM_COLOR .. message)
  228. end
  229. end
  230. end
  231. -- let player join, leave channels
  232. function shout.channel(name, param)
  233. param = string.trim(param)
  234. if param == "" then
  235. if shout.players[name] then
  236. shout.notify_channel(shout.players[name],
  237. "# Server: Player <" .. rename.gpn(name) .. "> has left channel '" .. shout.players[name] .. "'.")
  238. end
  239. minetest.chat_send_player(name, "# Server: Channel cleared.")
  240. shout.players[name] = nil
  241. return
  242. end
  243. if shout.players[name] and shout.players[name] == param then
  244. minetest.chat_send_player(name, "# Server: You are already on channel '" .. param .. "'.")
  245. return
  246. end
  247. -- Require channel names to match specific format.
  248. if not string.find(param, "^[_%w]+$") then
  249. minetest.chat_send_player(name, "# Server: Invalid channel name! Use only alphanumeric characters and underscores.")
  250. easyvend.sound_error(name)
  251. return
  252. end
  253. if shout.players[name] and param ~= shout.players[name] then
  254. shout.notify_channel(shout.players[name],
  255. "# Server: Player <" .. rename.gpn(name) .. "> has left channel '" .. shout.players[name] .. "'.")
  256. end
  257. minetest.chat_send_player(name, "# Server: Chat channel set to '" .. param .. "'.")
  258. shout.players[name] = param
  259. shout.notify_channel(shout.players[name], "# Server: Player <" .. rename.gpn(name) .. "> has joined channel '" .. shout.players[name] .. "'.")
  260. end
  261. -- let player put a message onto a channel
  262. function shout.x(name, param)
  263. param = string.trim(param)
  264. if not shout.players[name] then
  265. minetest.chat_send_player(name, "# Server: You have not specified a channel.")
  266. easyvend.sound_error(name)
  267. return
  268. end
  269. if #param < 1 then
  270. minetest.chat_send_player(name, "# Server: No message specified.")
  271. easyvend.sound_error(name)
  272. return
  273. end
  274. if command_tokens.mute.player_muted(name) then
  275. minetest.chat_send_player(name, "# Server: You cannot talk while gagged!")
  276. easyvend.sound_error(name)
  277. return
  278. end
  279. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  280. if chat_core.check_language(name, param) then return end
  281. local mk = ""
  282. if command_tokens.mark.player_marked(name) then
  283. local pos = minetest.get_player_by_name(name):getpos()
  284. mk = " [" .. math_floor(pos.x) .. "," .. math_floor(pos.y) .. "," .. math_floor(pos.z) .. "]"
  285. end
  286. local dname = rename.gpn(name)
  287. local channel = shout.players[name]
  288. local players = minetest.get_connected_players()
  289. -- Send message to all players in the same channel.
  290. -- The player who sent the message always receives it.
  291. for k, v in ipairs(players) do
  292. local n = v:get_player_name()
  293. if shout.players[n] and shout.players[n] == channel then
  294. local ignored = false
  295. -- Don't send teamchat if player is ignored.
  296. if chat_controls.player_ignored(n, name) then
  297. ignored = true
  298. end
  299. if not ignored then
  300. minetest.chat_send_player(n, "<!" .. chat_core.nametag_color .. rename.gpn(name) .. WHITE .. mk .. "!> " .. TEAM_COLOR .. param)
  301. end
  302. end
  303. end
  304. --minetest.chat_send_all(SHOUT_COLOR .. "<!" .. dname .. mk .. "!> " .. param)
  305. --chat_logging.log_public_shout(name, param, mk)
  306. chat_logging.log_team_chat(name, param, channel)
  307. afk_removal.reset_timeout(name)
  308. end
  309. if not shout.run_once then
  310. -- Post 'startup complete' message only in multiplayer.
  311. if not minetest.is_singleplayer() then
  312. minetest.after(10, function()
  313. minetest.chat_send_all("# Server: Startup complete.")
  314. end)
  315. end
  316. minetest.register_chatcommand("shout", {
  317. params = "<message>",
  318. description = "Yell a message to everyone on the server. You can also prepend your chat with '!'.",
  319. privs = {shout=true},
  320. func = function(name, param)
  321. shout.shout(name, param)
  322. return true
  323. end,
  324. })
  325. minetest.register_chatcommand("channel", {
  326. params = "<id>",
  327. description = "Set channel name.",
  328. privs = {shout=true},
  329. func = function(name, param)
  330. shout.channel(name, param)
  331. return true
  332. end,
  333. })
  334. minetest.register_chatcommand("x", {
  335. params = "<message>",
  336. description = "Speak on current channel.",
  337. privs = {shout=true},
  338. func = function(name, param)
  339. shout.x(name, param)
  340. return true
  341. end,
  342. })
  343. minetest.register_chatcommand("hint_add", {
  344. params = "<message>",
  345. description = "Add a hint message to the hint list. Example between quotes: '/hint_add This is a hint message. Another sentance.'",
  346. privs = {server=true},
  347. func = function(name, param)
  348. shout.hint_add(name, param)
  349. return true
  350. end,
  351. })
  352. -- Start hints. A hint is written into public chat every so often.
  353. -- But not too often, or it becomes annoying.
  354. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  355. local c = "shout:core"
  356. local f = shout.modpath .. "/init.lua"
  357. reload.register_file(c, f, false)
  358. shout.run_once = true
  359. end