123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- --------------------------------------------------------------------------------
- -- Core Chat System for Must Test Survival
- -- Author: GoldFireUn
- -- License: MIT
- --------------------------------------------------------------------------------
- if not minetest.global_exists("chat_core") then chat_core = {} end
- chat_core.modpath = minetest.get_modpath("chat_core")
- chat_core.players = chat_core.players or {}
- -- Localize for performance.
- local vector_distance = vector.distance
- local vector_round = vector.round
- function chat_core.on_joinplayer(player)
- local pname = player:get_player_name()
- chat_core.players[pname] = {
- last_pm_from = "",
- }
- end
- function chat_core.on_leaveplayer(player, timeout)
- local pname = player:get_player_name()
- chat_core.players[pname] = nil
- end
- if minetest.get_modpath("reload") then
- local c = "chat_core:core"
- local f = chat_core.modpath .. "/init.lua"
- if not reload.file_registered(c) then
- reload.register_file(c, f, false)
- end
- end
- local color_green = core.get_color_escape_sequence("#00ff00")
- local color_dark_green = core.get_color_escape_sequence("#00d000")
- local color_dark_cyan = core.get_color_escape_sequence("#88ffff")
- local color_nametag = core.get_color_escape_sequence("#ffd870")
- local color_white = core.get_color_escape_sequence("#ffffff")
- --local color_cyan = core.get_color_escape_sequence("#00e0ff")
- chat_core.nametag_color = color_nametag -- make public to other mods
- -- Used in PMs.
- local color_magenta = core.get_color_escape_sequence("#ff50ff")
- local color_dark_magenta = core.get_color_escape_sequence("#c800c8")
- chat_core.rewrite_message = function(chat2)
- -- Prevent players from including zero-bytes or control characters in their chat.
- local sub = string.gsub
- local chat = chat2
- chat = sub(chat, "[%z%c]", "") -- Zero byte & control bytes.
- chat = sub(chat, " +", " ") -- Excess spaces.
- --chat = sub(chat, "[qQ]", "k")
- return chat
- end
- -- Send regular chat from a player to all other players.
- -- This is called by this mod after validation checks pass.
- chat_core.send_all = function(from, prename, actname, postname, message, alwaysecho)
- -- `alwaysecho` is true in the case of a /me command.
- -- The client never echoes this command by itself.
- local player = minetest.get_player_by_name(from)
- if not player then
- return
- end
- local ppos = player:get_pos()
- local allplayers = minetest.get_connected_players()
- local mlower = string.lower(message)
- for k, v in ipairs(allplayers) do
- local pname = v:get_player_name()
- local plower = string.lower(rename.gpn(pname))
- local plowero = string.lower(pname)
- local tpos = v:get_pos()
- if pname ~= from then
- local chosen_color = ""
- local should_send = true
- local should_beep = false
- local ignored = false
- -- Execute chat filters. Order is relevant!
- -- Hide chat from players who are too far away (if feature is enabled).
- if chat_controls.player_too_far(pname, from) then
- should_send = false
- end
- if chat_controls.non_citizen_ignored(pname, from) then
- should_send = false
- end
- -- Whitelisted player chat can be see even if far away.
- if chat_controls.player_whitelisted(pname, from) then
- should_send = true
- end
- -- Ignore list takes precedence over whitelist.
- if chat_controls.player_ignored(pname, from) then
- should_send = false
- ignored = true
- end
- -- Chat from nearby players is highlighted.
- -- Even ignored players may talk if they are close enough.
- if vector_distance(ppos, tpos) < 64 then
- -- Highlight chat from nearby player only if originating player is not invisible.
- if not gdac_invis.is_invisible(from) then
- chosen_color = color_dark_cyan
- end
- should_send = true
- end
- -- Finally, check highlighting filters. But only if not ignored!
- if not ignored then
- if chat_controls.check_highlighting_filters(pname, from, message) then
- chosen_color = color_dark_green
- should_send = true
- end
- end
- if should_send then
- -- Colorize message for players if their name or alt is used.
- -- This overrides any previous coloring.
- if string.find(mlower, plower) or string.find(mlower, plowero) then
- chosen_color = color_green
- should_beep = true
- end
- -- If /me, use correct color.
- if alwaysecho then
- chosen_color = chat_colorize.COLOR_ORANGE
- end
- -- Finally send the message.
- if should_beep then
- chat_core.alert_player_sound(pname)
- end
- minetest.chat_send_player(pname, prename .. color_nametag .. actname .. color_white .. postname .. chosen_color .. message)
- end
- else -- Message being echoed back to player that sent it.
- if alwaysecho then
- -- It should be a /me command.
- minetest.chat_send_player(pname, prename .. color_nametag .. actname .. color_white .. postname .. chat_colorize.COLOR_ORANGE .. message)
- else
- -- Send chat to self if echo enabled.
- chat_echo.echo_chat(pname, prename .. color_nametag .. actname .. color_white .. postname .. message)
- end
- end
- end
- end
- -- Check player's language, and kick them if they are not protected by the PoC/KoC.
- -- Or, mute them and send a message to other players that they were muted.
- -- This function can be called from other mods.
- function chat_core.check_language(name, message, channel)
- -- Players with anticurse bypass priv cannot be kicked by this mod.
- local nokick = minetest.check_player_privs(name, {anticurse_bypass=true})
- -- If player doesn't have priv, check if their XP is high enough.
- -- (Remember, the AC mod is only intended to discourage "drive-by" pollution from people randomly joining.)
- -- By the time someone gets to this amount of XP, they probably understand that we like clean air, here. :-)
- if not nokick then
- nokick = (xp.get_xp(name, "digxp") >= 5000)
- end
- if nokick then
- if anticurse.check(name, message, "foul") then
- anticurse.log(name, message)
- elseif anticurse.check(name, message, "curse") then
- anticurse.log(name, message)
- end
- return false -- Has bypass.
- end
- if anticurse.check(name, message, "foul") then
- anticurse.log(name, message)
- -- Players who have registered (and therefore have probably played
- -- on the server more than a few days) are warned but not kicked.
- if passport.player_registered(name) then
- local ext = anticurse.get_kick_message("foul")
- if channel then
- shout.notify_channel(channel, "# Server: Talk from someone hidden in case of uninteresting language.")
- else
- minetest.chat_send_all("# Server: Talk from someone hidden in case of uninteresting language.")
- end
- minetest.chat_send_player(name, "# Server: " .. ext)
- else
- anticurse.kick(name, "foul")
- end
- return true -- Blocked.
- elseif anticurse.check(name, message, "curse") then
- anticurse.log(name, message)
- -- Players who have registered (and therefore have probably played
- -- on the server more than a few days) are warned but not kicked.
- if passport.player_registered(name) then
- local ext = anticurse.get_kick_message("curse")
- if channel then
- shout.notify_channel(channel, "# Server: Talk from someone hidden in case of uninteresting language.")
- else
- minetest.chat_send_all("# Server: Talk from someone hidden in case of uninteresting language.")
- end
- minetest.chat_send_player(name, "# Server: " .. ext)
- else
- anticurse.kick(name, "foul")
- end
- return true -- Blocked.
- end
- return false -- Nothing found.
- end
- local generate_coord_string = function(name)
- local coord_string = ""
- local entity = minetest.get_player_by_name(name)
- if not entity then
- return coord_string
- end
- local pos = vector_round(entity:get_pos())
- if command_tokens.mark.player_marked(name) or
- (sheriff.is_suspected_cheater(name) and city_block:in_city(pos)) then
- local pstr = rc.pos_to_string(pos)
- pstr = string.gsub(pstr, "[%(%)]", "")
- -- Note: 'nodeowner
- local nodeowner = protector.get_node_owner(pos)
- local cityinfo = ""
- local cityblock = city_block:nearest_named_region(pos, nodeowner)
- if cityblock and cityblock[1] and cityblock[1].area_name then
- if nodeowner and nodeowner ~= "" then
- cityinfo = "/" .. cityblock[1].area_name
- else
- cityinfo = "/Nearby - " .. cityblock[1].area_name
- end
- end
- -- Remember to include leading space!
- coord_string = " [" .. rc.realm_description_at_pos(pos) .. cityinfo .. ": " .. pstr .. "]"
- -- If server is not echoing player's chat back to the player, then their
- -- client is old (or they have chat-echo turned off). In this case, the
- -- player will need a special info message sent to them in order for them to
- -- know if they're marked.
- if not chat_echo.get_echo(name) then
- minetest.chat_send_player(name, "# Server: You are marked (" .. pstr .. ")!")
- end
- end
- return coord_string
- end
- chat_core.generate_coord_string = generate_coord_string
- function chat_core.player_status(pname)
- -- He's probably right, you know.
- --[[
- local pref = minetest.get_player_by_name(pname)
- if pref then
- local pos = pref:get_pos()
- local cblock = city_block:nearest_named_region(pos)
- local info
- if cblock[1] and cblock[1].area_name then
- info = cblock[1].area_name
- else
- info = rc.realm_description_at_pos(pos)
- end
- local xpmax = math.floor(xp.digxp_hp_max / 20)
- local xper = math.floor(xp.get_xp(pname, "digxp") / xpmax)
- return "[" .. info .. " - Lvl: " .. xper .. "] "
- end
- --]]
- return ""
- end
- chat_core.on_chat_message = function(name, message)
- -- Trim input.
- message = string.trim(message)
- if message:sub(1, 1) == "/" then
- minetest.chat_send_player(name, "# Server: Invalid command. See '/help all' for a list of valid commands.")
- easyvend.sound_error(name)
- -- It's a special command, and not one that was registered.
- -- This is actually never called?
- return
- end
- if not minetest.check_player_privs(name, {shout=true}) then
- minetest.chat_send_player(name, "# Server: You do not have 'shout' priv.")
- -- Player doesn't have shout priv.
- return
- end
- local player_muted = false
- if command_tokens.mute.player_muted(name) then
- minetest.chat_send_player(name, "# Server: You are currently gagged.")
- -- Player is muted.
- return
- end
- -- Shouts can be executed by prepending a '!' to your chat.
- if string.find(message, "^!") then
- -- Handled by the shout mod.
- shout.shout(name, string.sub(message, 2))
- return
- end
- -- Check for accidents.
- local pm_pos = string.find(message, "msg")
- if not pm_pos then
- pm_pos = string.find(message, "MSG")
- end
- if not pm_pos then
- pm_pos = string.find(message, "pm")
- end
- if not pm_pos then
- pm_pos = string.find(message, "PM")
- end
- if pm_pos and pm_pos <= 3 then -- Space for 2 symbols.
- minetest.chat_send_player(name,
- "# Server: Did you mean to send a PM? The command format is \"/msg <player> <message>\" (without quotes). Chat not sent.")
- return
- end
- if chat_core.check_language(name, message) then return end
- local coord_string = generate_coord_string(name)
- local stats = chat_core.player_status(name)
- player_labels.on_chat_message(name, message)
- chat_core.send_all(name, stats .. "<", rename.gpn(name), coord_string .. "> ", message)
- chat_logging.log_public_chat(name, stats, message, coord_string)
- afk.reset_timeout(name)
- end
- chat_core.handle_command_me = function(name, param)
- if not minetest.check_player_privs(name, {shout=true}) then
- minetest.chat_send_player(name, "# Server: You do not have 'shout' priv.")
- return -- Player doesn't have shout priv.
- end
- if command_tokens.mute.player_muted(name) then
- minetest.chat_send_player(name, "# Server: You can't do that while gagged, sorry.")
- return -- Player is muted.
- end
- param = string.trim(param)
- if #param < 1 then
- minetest.chat_send_player(name, "# Server: No action specified.")
- return
- end
- if chat_core.check_language(name, param) then return end
- local coord_string = generate_coord_string(name)
- player_labels.on_chat_message(name, param)
- chat_core.send_all(name, "* <", rename.gpn(name), coord_string .. "> ", param, true)
- chat_logging.log_public_action(name, param, coord_string)
- afk.reset_timeout(name)
- end
- chat_core.handle_command_msg = function(name, param)
- if not minetest.check_player_privs(name, {shout=true}) then
- minetest.chat_send_player(name, "# Server: You do not have 'shout' priv.")
- easyvend.sound_error(name)
- return -- Player doesn't have shout priv.
- end
- -- Gagged players cannot polute global chat, but PMs are allowed.
- -- If an annoying pervert is PM'ing someone, that person should take advantage
- -- of the ignore-list in their Key. Or use F2 if they don't have a Key.
- --[[
- if command_tokens.mute.player_muted(name) then
- minetest.chat_send_player(name, "# Server: You are gagged at the moment.")
- easyvend.sound_error(name)
- return -- Player is muted.
- end
- --]]
- local coord_string = generate_coord_string(name)
- -- Split command arguments.
- local p = string.find(param, " ")
- local to, newmsg
- if p then
- newmsg = string.trim(param:sub(p+1))
- to = string.trim(param:sub(1, p-1))
- end
- if type(to)=="string" and type(newmsg)=="string" and string.len(newmsg) > 0 and string.len(to) > 0 then
- to = rename.grn(to)
- if gdac_invis.is_invisible(to) and to ~= name then -- If target is invisible, and player sending is not same as target ...
- if chat_core.players[name] and (chat_core.players[name].last_pm_from or "") ~= to then -- Do not permit, if player did not receive a PM from this target.
- if to == "MustTest" then
- minetest.chat_send_player(name, "# Server: The server admin is not available at this time! If it's important, send mail instead.")
- else
- minetest.chat_send_player(name, "# Server: <" .. rename.gpn(to) .. "> is not available at this time! If it's important, send mail instead.")
- end
- return
- end
- end
- if minetest.get_player_by_name(to) then
- -- Bad words in PMs.
- if chat_core.check_language(name, newmsg) then
- return
- end
- -- Cannot PM player if being ignored.
- if chat_controls.player_ignored_pm(to, name) and to ~= name then
- minetest.chat_send_player(name, "# Server: <" .. rename.gpn(to) .. "> is not available for private messaging!")
- easyvend.sound_error(name)
- return
- end
- minetest.after(0, function()
- chat_core.alert_player_sound(to)
- minetest.chat_send_player(to, color_magenta .. "# PM: FROM <" .. rename.gpn(name) .. coord_string .. ">: " .. newmsg)
- -- Record name of last player to send this player a PM.
- if chat_core.players[to] then
- chat_core.players[to].last_pm_from = name
- end
- end)
- minetest.chat_send_player(name, color_dark_magenta .. "# PM: TO <" .. rename.gpn(to) .. coord_string .. ">: " .. newmsg)
- chat_logging.log_private_message(name, to, newmsg)
- afk.reset_timeout(name)
- else minetest.chat_send_player(name, "# Server: <" .. rename.gpn(to) .. "> is not online.") end
- else minetest.chat_send_player(name, "# Server: Usage: '/msg <playername> <message>'.") end
- end
- function chat_core.handle_command_r(name, param)
- local to = chat_core.players[name].last_pm_from or ""
- if to == "" then
- minetest.chat_send_player(name, "# Server: No one has sent you a PM yet that can be replied to. Use /msg instead to specify the player.")
- return
- end
- return chat_core.handle_command_msg(name, to .. " " .. param) -- Prepend target name, and call normal /msg function.
- end
- function chat_core.alert_player_sound(to)
- if chat_controls.beep_enabled(to) then
- if afk.is_afk(to) then
- minetest.sound_play("chat_alert", {to_player = to, gain = 1}, true)
- else
- if afk.seconds_since_action(to) > 60*2 then
- minetest.sound_play("chat_alert", {to_player = to, gain = 1}, true)
- else
- minetest.sound_play("chat_alert", {to_player = to, gain = 0.4}, true)
- end
- end
- if not gdac_invis.is_invisible(to) then
- local pref = minetest.get_player_by_name(to)
- if pref then
- local pos = pref:get_pos()
- ambiance.sound_play("chat_alert", pos, 0.5, 20, to)
- end
- end
- end
- end
- if not chat_core.registered then
- minetest.register_chatcommand("me", {
- params = "<action>",
- description = "Send an 'action' message beginning with your name.",
- privs = {shout=true},
- func = function(name, param)
- chat_core.handle_command_me(name, chat_core.rewrite_message(param))
- return true
- end,
- })
- minetest.register_chatcommand("msg", {
- params = "<player> <message>",
- description = "Send a private message to another player.",
- privs = {shout=true},
- func = function(name, param)
- chat_core.handle_command_msg(name, chat_core.rewrite_message(param))
- return true
- end,
- })
- minetest.register_chatcommand("r", {
- params = "<message>",
- description = "Reply via PM to the last player to send you a PM.",
- privs = {shout=true},
- func = function(name, param)
- chat_core.handle_command_r(name, chat_core.rewrite_message(param))
- return true
- end,
- })
- -- This should be the only handler registered. Only one handler can be registered.
- minetest.register_on_chat_message(function(name, message)
- chat_core.on_chat_message(name, chat_core.rewrite_message(message))
- return true -- Don't send message automatically, we already did this.
- end)
- minetest.register_on_joinplayer(function(...) return chat_core.on_joinplayer(...) end)
- minetest.register_on_leaveplayer(function(...) return chat_core.on_leaveplayer(...) end)
- chat_core.registered = true
- end
|