init.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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. "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. "A hurt player will usually heal slowly, up to the last two hearts. Use bandages to speed this up.",
  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; however lava cannot exist above sea level.",
  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. "You can sleep in a bed to set your respawn position on 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 given in server messages are in realm-coordinates. Absolute (F5) coordinates are not used.",
  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. }
  82. function shout.hint_add(name, param)
  83. name = name:trim()
  84. param = param:trim()
  85. param = param:gsub("%s+", " ")
  86. if param:len() == 0 then
  87. minetest.chat_send_player(name, "# Server: Not adding an empty hint message.")
  88. return
  89. end
  90. minetest.chat_send_player(name, "# Server: Will add hint message: \"" .. param .. "\".")
  91. -- Will store all hints loaded from file.
  92. local loaded_hints = {}
  93. -- Load all hints from world datafile.
  94. local file, err = io.open(shout.datafile, "r")
  95. if err then
  96. minetest.chat_send_player(name, "# Server: Failed to open \"" .. shout.datafile .. "\" for reading: " .. err)
  97. else
  98. local datastring = file:read("*all")
  99. if datastring and datastring ~= "" then
  100. local records = string.split(datastring, "\n")
  101. for record_number, record in ipairs(records) do
  102. local data = record:trim()
  103. if data:len() > 0 then
  104. table.insert(loaded_hints, data)
  105. end
  106. end
  107. end
  108. file:close()
  109. end
  110. minetest.chat_send_player(name, "# Server: Loaded " .. #loaded_hints .. " previously saved hints.")
  111. -- Add the new hint message.
  112. table.insert(loaded_hints, param)
  113. -- Custom file format. minetest.serialize() is unusable for large tables.
  114. local datastring = ""
  115. for k, record in ipairs(loaded_hints) do
  116. datastring = datastring .. record .. "\n"
  117. end
  118. -- Now save all non-builtin hints back to the file.
  119. local file, err = io.open(shout.datafile, "w")
  120. if err then
  121. minetest.chat_send_player(name, "# Server: Failed to open \"" .. shout.datafile .. "\" for writing: " .. err)
  122. else
  123. file:write(datastring)
  124. file:close()
  125. end
  126. -- Recombine both tables.
  127. shout.HINTS = {}
  128. for k, v in ipairs(shout.BUILTIN_HINTS) do
  129. table.insert(shout.HINTS, v)
  130. end
  131. for k, v in ipairs(loaded_hints) do
  132. table.insert(shout.HINTS, v)
  133. end
  134. end
  135. -- Load any saved hints whenever mod is reloaded or server starts.
  136. do
  137. -- Will store all hints loaded from file.
  138. local loaded_hints = {}
  139. -- Load all hints from world datafile.
  140. local file, err = io.open(shout.datafile, "r")
  141. if err then
  142. minetest.log("error", "Failed to open " .. shout.datafile .. " for reading: " .. err)
  143. else
  144. local datastring = file:read("*all")
  145. if datastring and datastring ~= "" then
  146. local records = string.split(datastring, "\n")
  147. for record_number, record in ipairs(records) do
  148. local data = record:trim()
  149. if data:len() > 0 then
  150. table.insert(loaded_hints, data)
  151. end
  152. end
  153. end
  154. file:close()
  155. end
  156. -- Recombine both tables.
  157. shout.HINTS = {}
  158. for k, v in ipairs(shout.BUILTIN_HINTS) do
  159. table.insert(shout.HINTS, v)
  160. end
  161. for k, v in ipairs(loaded_hints) do
  162. table.insert(shout.HINTS, v)
  163. end
  164. end
  165. local HINT_DELAY_MIN = 60*45
  166. local HINT_DELAY_MAX = 60*90
  167. function shout.print_hint()
  168. local HINTS = shout.HINTS
  169. -- Only if hints are available.
  170. if #HINTS > 0 then
  171. minetest.chat_send_all("# Server: " .. HINTS[math_random(1, #HINTS)])
  172. end
  173. -- Print another hint after some delay.
  174. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  175. end
  176. -- Shout a message.
  177. function shout.shout(name, param)
  178. param = string.trim(param)
  179. if #param < 1 then
  180. minetest.chat_send_player(name, "# Server: No message specified.")
  181. easyvend.sound_error(name)
  182. return
  183. end
  184. if command_tokens.mute.player_muted(name) then
  185. minetest.chat_send_player(name, "# Server: You cannot shout while gagged!")
  186. easyvend.sound_error(name)
  187. return
  188. end
  189. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  190. if chat_core.check_language(name, param) then return end
  191. local mk = chat_core.generate_coord_string(name)
  192. local dname = rename.gpn(name)
  193. local players = minetest.get_connected_players()
  194. for _, player in ipairs(players) do
  195. local target_name = player:get_player_name() or ""
  196. if not chat_controls.player_ignored_shout(target_name, name) or target_name == name then
  197. chat_core.alert_player_sound(target_name)
  198. minetest.chat_send_player(target_name, "<!" .. chat_core.nametag_color .. dname .. WHITE .. mk .. "!> " .. SHOUT_COLOR .. param)
  199. end
  200. end
  201. afk.reset_timeout(name)
  202. chat_logging.log_public_shout(name, param, mk)
  203. end
  204. function shout.player_channel(pname)
  205. if shout.players[pname] and shout.players[pname] ~= "" then
  206. return shout.players[pname]
  207. end
  208. end
  209. function shout.channel_players(channel)
  210. local players = minetest.get_connected_players()
  211. local result = {}
  212. for k, v in ipairs(players) do
  213. local n = v:get_player_name()
  214. if shout.players[n] and shout.players[n] == channel then
  215. result[#result+1] = n
  216. end
  217. end
  218. return result
  219. end
  220. function shout.notify_channel(channel, message)
  221. local players = minetest.get_connected_players()
  222. -- Send message to all players in the same channel.
  223. for k, v in ipairs(players) do
  224. local n = v:get_player_name()
  225. if shout.players[n] and shout.players[n] == channel then
  226. minetest.chat_send_player(n, TEAM_COLOR .. message)
  227. end
  228. end
  229. end
  230. -- let player join, leave channels
  231. function shout.channel(name, param)
  232. param = string.trim(param)
  233. if param == "" then
  234. if shout.players[name] then
  235. shout.notify_channel(shout.players[name],
  236. "# Server: Player <" .. rename.gpn(name) .. "> has left channel '" .. shout.players[name] .. "'.")
  237. end
  238. minetest.chat_send_player(name, "# Server: Channel cleared.")
  239. shout.players[name] = nil
  240. return
  241. end
  242. if shout.players[name] and shout.players[name] == param then
  243. minetest.chat_send_player(name, "# Server: You are already on channel '" .. param .. "'.")
  244. return
  245. end
  246. -- Require channel names to match specific format.
  247. if not string.find(param, "^[_%w]+$") then
  248. minetest.chat_send_player(name, "# Server: Invalid channel name! Use only alphanumeric characters and underscores.")
  249. easyvend.sound_error(name)
  250. return
  251. end
  252. if shout.players[name] and param ~= shout.players[name] then
  253. shout.notify_channel(shout.players[name],
  254. "# Server: Player <" .. rename.gpn(name) .. "> has left channel '" .. shout.players[name] .. "'.")
  255. end
  256. minetest.chat_send_player(name, "# Server: Chat channel set to '" .. param .. "'.")
  257. shout.players[name] = param
  258. shout.notify_channel(shout.players[name], "# Server: Player <" .. rename.gpn(name) .. "> has joined channel '" .. shout.players[name] .. "'.")
  259. end
  260. -- let player put a message onto a channel
  261. function shout.x(name, param)
  262. param = string.trim(param)
  263. if not shout.players[name] then
  264. minetest.chat_send_player(name, "# Server: You have not specified a channel.")
  265. easyvend.sound_error(name)
  266. return
  267. end
  268. if #param < 1 then
  269. minetest.chat_send_player(name, "# Server: No message specified.")
  270. easyvend.sound_error(name)
  271. return
  272. end
  273. -- Allow player to use channel speak even while gagged.
  274. -- Rational: if the gagged player is on a channel with others,
  275. -- then probably they're in a group together, or are related.
  276. -- Chat between such shouldn't be blocked.
  277. --[[
  278. if command_tokens.mute.player_muted(name) then
  279. minetest.chat_send_player(name, "# Server: You cannot talk while gagged!")
  280. easyvend.sound_error(name)
  281. return
  282. end
  283. --]]
  284. -- If this succeeds, the player was either kicked, or muted and a message about that sent to everyone else.
  285. if chat_core.check_language(name, param) then return end
  286. local mk = ""
  287. if command_tokens.mark.player_marked(name) then
  288. local pos = minetest.get_player_by_name(name):get_pos()
  289. mk = " [" .. math_floor(pos.x) .. "," .. math_floor(pos.y) .. "," .. math_floor(pos.z) .. "]"
  290. end
  291. local dname = rename.gpn(name)
  292. local channel = shout.players[name]
  293. local players = minetest.get_connected_players()
  294. -- Send message to all players in the same channel.
  295. -- The player who sent the message always receives it.
  296. for k, v in ipairs(players) do
  297. local n = v:get_player_name()
  298. if shout.players[n] and shout.players[n] == channel then
  299. local ignored = false
  300. -- Don't send teamchat if player is ignored.
  301. if chat_controls.player_ignored(n, name) then
  302. ignored = true
  303. end
  304. if not ignored then
  305. minetest.chat_send_player(n, "<!" .. chat_core.nametag_color .. rename.gpn(name) .. WHITE .. mk .. "!> " .. TEAM_COLOR .. param)
  306. end
  307. end
  308. end
  309. --minetest.chat_send_all(SHOUT_COLOR .. "<!" .. dname .. mk .. "!> " .. param)
  310. --chat_logging.log_public_shout(name, param, mk)
  311. chat_logging.log_team_chat(name, param, channel)
  312. afk.reset_timeout(name)
  313. end
  314. if not shout.run_once then
  315. -- Post 'startup complete' message only in multiplayer.
  316. if not minetest.is_singleplayer() then
  317. minetest.after(10, function()
  318. minetest.chat_send_all("# Server: Startup complete.")
  319. end)
  320. end
  321. minetest.register_chatcommand("shout", {
  322. params = "<message>",
  323. description = "Yell a message to everyone on the server. You can also prepend your chat with '!'.",
  324. privs = {shout=true},
  325. func = function(name, param)
  326. shout.shout(name, param)
  327. return true
  328. end,
  329. })
  330. minetest.register_chatcommand("channel", {
  331. params = "<id>",
  332. description = "Set channel name.",
  333. privs = {shout=true},
  334. func = function(name, param)
  335. shout.channel(name, param)
  336. return true
  337. end,
  338. })
  339. minetest.register_chatcommand("x", {
  340. params = "<message>",
  341. description = "Speak on current channel.",
  342. privs = {shout=true},
  343. func = function(name, param)
  344. shout.x(name, param)
  345. return true
  346. end,
  347. })
  348. minetest.register_chatcommand("hint_add", {
  349. params = "<message>",
  350. description = "Add a hint message to the hint list. Example between quotes: '/hint_add This is a hint message. Another sentance.'",
  351. privs = {server=true},
  352. func = function(name, param)
  353. shout.hint_add(name, param)
  354. return true
  355. end,
  356. })
  357. -- Start hints. A hint is written into public chat every so often.
  358. -- But not too often, or it becomes annoying.
  359. minetest.after(math_random(HINT_DELAY_MIN, HINT_DELAY_MAX), function() shout.print_hint() end)
  360. local c = "shout:core"
  361. local f = shout.modpath .. "/init.lua"
  362. reload.register_file(c, f, false)
  363. shout.run_once = true
  364. end