init.lua 15 KB

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