Compare commits

...

5 Commits

Author SHA1 Message Date
luk3yx 6ee6cd0426 Add title to mod.conf 2022-04-12 14:26:21 +12:00
luk3yx 2154a39ad4 Add more robust /status parser 2021-10-04 08:38:08 +13:00
luk3yx 6e454df37c Hopefully fix #8 2021-08-21 09:22:44 +12:00
luk3yx 32f61e45b3 Override minetest.get_objects_in_area() 2021-02-06 22:34:30 +13:00
luk3yx bdd68b0e32 Add luacheck and /shadow. 2021-02-06 13:35:28 +13:00
13 changed files with 181 additions and 101 deletions

11
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,11 @@
on: [push, pull_request]
name: build
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: lint
uses: Roang-zero1/factorio-mod-luacheck@master
with:
luacheckrc_url: ""

14
.luacheckrc Normal file
View File

@ -0,0 +1,14 @@
max_line_length = 80
globals = {
'areas',
'chat3',
'cloaking',
'minetest',
'irc',
}
read_globals = {
string = {fields = {'split', 'trim'}},
table = {fields = {'copy'}},
}

View File

@ -1,6 +1,6 @@
# The MIT License (MIT)
Copyright © 2019 by luk3yx
Copyright © 2018-2021 by luk3yx
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,9 @@
# Minetest cloaking mod
Allows players to cloak and uncloak, inspired by Star Trek's [cloaking device].
Allows admins to become invisible and appear as if they are not in-game at all.
Inspired by Star Trek's [cloaking device].
[cloaking device]: https://memory-alpha.fandom.com/wiki/Cloaking_device
## What is cloaking?
@ -8,11 +11,10 @@ In this instance, cloaking is a way to go invisible and become undetectable by
most mods (unless they explicitly want to detect cloaked players, so they can
still send chat messages to them).
## Why is cloaking so hacky, and where does it send the 'left the game' messages?
## Why is cloaking so hacky?
Cloaking sends no left the game messages, it leaves that up to the built-in left
the game functions. The aim of cloaking is to trick other mods into thinking the
player is not in-game, and for this it must be hacky.
The aim of cloaking is to trick other mods into thinking the player is not
in-game, and a lot of hacks are used to do this.
## Help, it crashes
@ -21,24 +23,28 @@ running around, or by a bug in cloaking itself.
## How do I use cloaking?
Cloaking adds a modding API and two chatcommands. Both of these require the
`cloaking` privilege to execute, however you can uncloak yourself without any
privileges.
Cloaking adds a modding API and some chatcommands. All of the chatcommands
require the "cloaking" privilege to execute, with the exception of `/uncloak`
when used on yourself.
- `/cloak [victim]`: Cloaks yourself, alternatively an unsuspecting `victim`.
- `/uncloak [victim]`: Uncloaks yourself, or a `victim`.
- `/cloak_chat <message>`: Sends `<message>` to all online players with the
`cloaking` privilege. You can disable cloaked chat by setting
`cloaking.enable_cloaked_chat` to `false` in `minetest.conf`.
`cloaking` privilege.
- You can disable cloaked chat by adding
`cloaking.enable_cloaked_chat = false` to minetest.conf.
- `/shadow [victim]`: Attaches you to a player. If `victim` is not specified,
this command will detach you instead.
- Requires both the "cloaking" and "teleport" privileges.
- This command can only be used while cloaked.
- You can disable this by adding `cloaking.enable_shadow = false` to
minetest.conf.
## How do I download cloaking?
You can either run
`git clone https://git.minetest.land/luk3yx/cloaking.git`, or download
a [zip](https://git.minetest.land/luk3yx/cloaking/archive/master.zip) or
[tar.gz](https://git.minetest.land/luk3yx/cloaking/archive/master.tar.gz)
file. You will need to rename the downloaded folder to `cloaking` (if it
doesn't already have that name) for it to work properly.
`git clone https://git.minetest.land/luk3yx/cloaking.git` or download this mod
from [ContentDB](https://content.minetest.net/packages/luk3yx/cloaking).
## How do I use the API?
@ -50,8 +56,7 @@ Cloaking adds the following functions:
- `cloaking.get_connected_names()`: Gets a list of cloaked and uncloaked player
names.
- `cloaking.is_cloaked(player)`: Checks if a player is cloaked.
- `cloaking.hide_player(player)`: Hides a player without cloaking them. If/when
minetest.hide_player() gets introduced, this will become an alias for that.
- `cloaking.hide_player(player)`: Hides a player without cloaking them.
- `cloaking.unhide_player(player)`: Unhides a player previously hidden with
`cloaking.hide_player()`.
- `cloaking.chat`: Cloaked chat API, this is `nil` if cloaked chat is disabled.
@ -70,7 +75,7 @@ If you have made chatcommand work with players that aren't in-game, you can add
`_allow_while_cloaked = true` to the chatcommand definition. If you explicitly
don't want your chatcommand working with cloaked players, you can add
`_disallow_while_cloaked = true` to the definition.
These modifications do not require that you add `cloaking` to `depends.txt`, as
These modifications do not require that you add cloaking as a dependency, as
when cloaking is not loaded this parameter is simply ignored.
## Backported bugfixes
@ -87,5 +92,3 @@ if it determines they are necessary for your server:
If you do not want this for whatever reason (although I do not recommend it),
you can disable these backports by adding `cloaking.backport_bugfixes = false`
to your minetest.conf.
[cloaking device]: https://memory-alpha.fandom.com/wiki/Cloaking_device

View File

@ -1,7 +1,7 @@
--
-- Minetest cloaking mod: chat3 fixes
--
-- © 2019 by luk3yx
-- Copyright © 2019 by luk3yx
--
-- Override minetest.get_connected_players() so it lists cloaked players for

View File

@ -1,7 +1,7 @@
--
-- Minetest cloaking mod: chatcommands
--
-- © 2019 by luk3yx
-- Copyright © 2018-2021 by luk3yx
--
minetest.register_privilege('cloaking',
@ -12,21 +12,22 @@ minetest.register_chatcommand("cloak", {
description = "Cloak a player so they are not visible.",
privs = {cloaking = true},
_allow_while_cloaked = true,
func = function(player, victim)
func = function(name, victim)
if not victim or victim == '' then
victim = player
victim = name
end
local p = cloaking.get_player_by_name(victim)
if not p then
return false, "Could not find a player with the name '" .. victim .. "'!"
return false, "Could not find a player with the name '" ..
victim .. "'!"
end
if cloaking.is_cloaked(victim) then
return false, victim .. " is already cloaked!"
end
minetest.log('action', player .. ' cloaks ' .. victim .. '.')
minetest.log('action', name .. ' cloaks ' .. victim .. '.')
cloaking.cloak(p)
return true, "Cloaked!"
end
@ -36,16 +37,16 @@ minetest.register_chatcommand("uncloak", {
params = "[victim]",
description = "Uncloak a player so they are visible.",
_allow_while_cloaked = true,
func = function(player, victim)
func = function(name, victim)
if not victim or victim == '' then
victim = player
elseif not minetest.get_player_privs(player).cloaking then
victim = name
elseif not minetest.get_player_privs(name).cloaking then
return false, "You don't have permission to uncloak someone else."
end
if victim == '*' then
minetest.log('action', player .. ' uncloaks everyone.')
minetest.log('action', name .. ' uncloaks everyone.')
for _, player in ipairs(cloaking.get_cloaked_players()) do
cloaking.uncloak(player)
end
@ -54,14 +55,15 @@ minetest.register_chatcommand("uncloak", {
local p = cloaking.get_player_by_name(victim)
if not p then
return false, "Could not find a player with the name '" .. victim .. "'!"
return false, "Could not find a player with the name '" ..
victim .. "'!"
end
if not cloaking.is_cloaked(victim) then
return false, victim .. " is not cloaked!"
end
minetest.log('action', player .. ' uncloaks ' .. victim .. '.')
minetest.log('action', name .. ' uncloaks ' .. victim .. '.')
cloaking.uncloak(p)
return true, "Uncloaked!"
end

View File

@ -1,7 +1,7 @@
--
-- Minetest player cloaking mod: "Cloaked chat"
--
-- © 2019 by luk3yx
-- Copyright © 2019 by luk3yx
--
cloaking.chat = {prefix = '-Cloaked-'}

115
core.lua
View File

@ -1,7 +1,7 @@
--
-- Minetest player cloaking mod: Core functions
--
-- © 2019 by luk3yx
-- Copyright © 2018-2021 by luk3yx
--
cloaking = {}
@ -10,6 +10,7 @@ cloaking = {}
-- can use them.
cloaking.get_player_by_name = minetest.get_player_by_name
cloaking.get_objects_inside_radius = minetest.get_objects_inside_radius
cloaking.get_objects_in_area = minetest.get_objects_in_area
cloaking.get_server_status = minetest.get_server_status
local cloaked_players = {}
@ -24,16 +25,28 @@ function minetest.get_player_by_name(player)
end
end
function minetest.get_objects_inside_radius(pos, radius)
local objs = {}
for _, obj in ipairs(cloaking.get_objects_inside_radius(pos, radius)) do
if not cloaked_players[obj:get_player_name()] then
table.insert(objs, obj)
local function remove_cloaked_players(list)
for i = #list, 1, -1 do
if cloaked_players[list[i]:get_player_name()] then
table.remove(list, i)
end
end
end
function minetest.get_objects_inside_radius(pos, radius)
local objs = cloaking.get_objects_inside_radius(pos, radius)
remove_cloaked_players(objs)
return objs
end
if cloaking.get_objects_in_area then
function minetest.get_objects_in_area(pos1, pos2)
local objs = cloaking.get_objects_in_area(pos1, pos2)
remove_cloaked_players(objs)
return objs
end
end
local override_statusline = false
if minetest.settings:get_bool('show_statusline_on_connect') ~= nil then
override_statusline = true
@ -46,16 +59,13 @@ function minetest.get_server_status(name, joined)
local status = cloaking.get_server_status(name, joined)
if not status or status == "" then return status end
local e = status:find('}', 1, true)
if not e then return status end
local motd = status:sub(e)
status = status:sub(1, status:find('{', 1, true))
local players = {}
for _, player in ipairs(minetest.get_connected_players()) do
table.insert(players, player:get_player_name())
end
players = table.concat(players, ', ')
status = status .. players .. motd
status = status:gsub('([,|] clients[:=][{ ])[^\n|}]*(}?)', function(p1, p2)
local players = {}
for _, player in ipairs(minetest.get_connected_players()) do
table.insert(players, player:get_player_name())
end
return p1 .. table.concat(players, ', ') .. p2
end, 1)
return status
end
@ -76,11 +86,13 @@ end
-- Don't allow chat or chatcommands in all commands that don't have the
-- allow_while_cloaked parameter set.
local override_chatcommands = function()
for name, def in pairs(minetest.registered_chatcommands) do
local delayed_uncloak
local function override_chatcommands()
local chatcommands = minetest.registered_chatcommands
for cmd_name, def in pairs(chatcommands) do
if not def.allow_while_cloaked and not def._allow_while_cloaked then
local real_cmd = def.func
minetest.chatcommands[name].func = function(name, param)
chatcommands[cmd_name].func = function(name, param)
if cloaked_players[name] then
local pass, r1, r2
if def._disallow_while_cloaked then
@ -106,7 +118,7 @@ local override_chatcommands = function()
for _, func in ipairs(minetest.registered_on_leaveplayers) do
c = c + 1
local f = func
if f ~= cloaking.delayed_uncloak then
if f ~= delayed_uncloak then
minetest.registered_on_leaveplayers[c] = function(p, t, cloaked)
if cloaked ~= 'cloaking' and
cloaked_players[p:get_player_name()] then
@ -166,10 +178,9 @@ end
-- "Hide" players
local hidden = {}
function cloaking.hide_player(player, preserve_attrs)
function cloaking.hide_player(player_or_name, preserve_attrs)
-- Sanity check
local victim
local player, victim = player_and_name(player, true)
local player, victim = player_and_name(player_or_name, true)
if not player then return end
-- Save existing attributes
@ -202,9 +213,9 @@ minetest.register_on_leaveplayer(function(player)
end)
-- "Unhide" players
function cloaking.unhide_player(player)
function cloaking.unhide_player(player_or_name)
-- Sanity check
local player, victim = player_and_name(player, true)
local player, victim = player_and_name(player_or_name, true)
if not player or hidden[victim] == nil then return end
-- Get the data
@ -236,11 +247,11 @@ function cloaking.unhide_player(player)
end
-- The cloak and uncloak functions
local use_areas = minetest.get_modpath('areas') and areas and areas.hud
function cloaking.cloak(player)
local use_areas = minetest.global_exists('areas') and areas.hud
function cloaking.cloak(player_or_name)
if not chatcommands_modified then override_chatcommands() end
local player, victim = player_and_name(player, true)
local player, victim = player_and_name(player_or_name, true)
if not player then return end
cloaking.hide_player(player, false)
@ -251,7 +262,7 @@ function cloaking.cloak(player)
end
for _, f in ipairs(minetest.registered_on_leaveplayers) do
if f ~= cloaking.delayed_uncloak then
if f ~= delayed_uncloak then
f(player, false, 'cloaking')
end
end
@ -275,8 +286,8 @@ function cloaking.cloak(player)
minetest.log('verbose', victim .. ' was cloaked.')
end
function cloaking.uncloak(player)
local player, victim = player_and_name(player, false)
function cloaking.uncloak(player_or_name)
local player, victim = player_and_name(player_or_name, false)
if not player then return end
cloaked_players[victim] = nil
@ -296,21 +307,17 @@ function cloaking.uncloak(player)
end
-- API functions
function cloaking.auto_uncloak(player)
if type(player) ~= "string" then
player = player:get_player_name()
end
if cloaked_players[player] then
cloaking.uncloak(player)
end
end
cloaking.auto_uncloak = cloaking.uncloak
function cloaking.delayed_uncloak(player)
-- This function removes the player from the cloaked players table on the next
-- server step.
-- Defined as a local above override_chatcommands().
function delayed_uncloak(player)
local victim = player:get_player_name()
if cloaked_players[victim] then
minetest.after(0.5, function()
minetest.after(0, function()
cloaked_players[victim] = nil
if areas and areas.hud and areas.hud[victim] then
if use_areas and areas.hud[victim] then
areas.hud[victim] = nil
end
end)
@ -319,9 +326,8 @@ end
-- Register cloaking.delayed_uncloak "manually" so that the profiler can't
-- hijack it, preventing it from running.
minetest.registered_on_leaveplayers[#minetest.registered_on_leaveplayers + 1]
= cloaking.delayed_uncloak
minetest.callback_origins[cloaking.delayed_uncloak] = {
table.insert(minetest.registered_on_leaveplayers, delayed_uncloak)
minetest.callback_origins[delayed_uncloak] = {
mod = 'cloaking',
name = 'delayed_uncloak'
}
@ -331,24 +337,25 @@ minetest.callback_origins[cloaking.delayed_uncloak] = {
local get_connected_players = minetest.get_connected_players
function minetest.get_connected_players()
local res = get_connected_players()
for i = #res, 1, -1 do
if cloaked_players[res[i]:get_player_name()] then
table.remove(res, i)
end
end
remove_cloaked_players(res)
return res
end
-- There's currently no way to check if cloaked players will appear in the
-- unmodified minetest.get_connected_players().
function cloaking.get_connected_players()
local a = minetest.get_connected_players()
for player, cloaked in pairs(cloaked_players) do
local players = minetest.get_connected_players()
for name, cloaked in pairs(cloaked_players) do
if cloaked then
table.insert(a, cloaking.get_player_by_name(player))
local player = cloaking.get_player_by_name(name)
-- The player may be nil if they have just left but haven't been
-- removed from the cloaked_players table yet.
if player then
players[#players + 1] = player
end
end
end
return a
return players
end
function cloaking.get_cloaked_players()

View File

@ -15,25 +15,22 @@ if minetest.get_modpath('irc') then
dofile(path .. '/irc.lua')
end
-- Attempt to support older versions of Minetest
local cloaked_chat = 'cloaking.enable_cloaked_chat'
local backport_bugfixes = 'cloaking.backport_bugfixes'
if minetest.settings and minetest.settings.get_bool then
cloaked_chat = minetest.settings:get_bool(cloaked_chat)
backport_bugfixes = minetest.settings:get_bool(backport_bugfixes)
else
cloaked_chat = minetest.setting_getbool(cloaked_chat)
backport_bugfixes = minetest.setting_getbool(backport_bugfixes)
end
-- Load cloaked chat if enabled
local cloaked_chat = minetest.settings:get_bool('cloaking.enable_cloaked_chat')
if cloaked_chat or cloaked_chat == nil then
dofile(path .. '/cloaked-chat.lua')
end
-- Load /shadow if enabled
local shadow = minetest.settings:get_bool('cloaking.enable_shadow')
if shadow or shadow == nil then
dofile(path .. '/shadow.lua')
end
-- The following code is for backporting bugfixes, if the setting was disabled
-- simply return now.
if backport_bugfixes ~= nil and not backport_bugfixes then
local backport_fixes = minetest.settings:get_bool('cloaking.backport_bugfixes')
if backport_fixes ~= nil and not backport_fixes then
return
end

View File

@ -1,7 +1,7 @@
--
-- Minetest cloaking mod: IRC fixes
--
-- © 2019 by luk3yx
-- Copyright © 2019 by luk3yx
--
local irc_sendLocal = irc.sendLocal

View File

@ -1,3 +1,4 @@
name = cloaking
title = Cloaking
description = Allows admins to become invisible to both mods and other players.
optional_depends = areas,chat3,irc

View File

@ -2,5 +2,8 @@
# chat channel.
cloaking.enable_cloaked_chat (Cloaked chat) bool true
# Enables /shadow.
cloaking.enable_shadow (Shadow command) bool true
# Backports certain bugfixes to older versions of Minetest.
cloaking.backport_bugfixes (Backport MT bugfixes) bool true

42
shadow.lua Normal file
View File

@ -0,0 +1,42 @@
--
-- Minetest player cloaking mod
--
-- Copyright © 2021 by luk3yx
--
minetest.register_chatcommand('shadow', {
privs = {cloaking = true, teleport = true},
description = 'Attaches you to a player. If `victim` is not specified, ' ..
'this command will detach you instead.',
params = '[victim]',
func = function(name, param)
if not cloaking.is_cloaked(name) then
return false, 'You must be cloaked to use /shadow!'
elseif name == param then
return false, 'You cannot shadow yourself!'
end
local player = cloaking.get_player_by_name(name)
if param == '' then
player:set_detach()
return true, 'You are free to move normally.'
end
local victim = cloaking.get_player_by_name(param)
if not victim then
return false, ('Invalid player %q.'):format(param)
end
player:set_attach(victim, '', {x=0, y=0, z=0}, {x=0, y=0, z=0})
return true, ('You are now shadowing %q.'):format(param)
end
})
-- Detach on uncloak
minetest.register_on_joinplayer(function(player)
if not minetest.check_player_privs(player, "cloaking", "teleport") then
return
end
local parent = player:get_attach()
if minetest.is_player(parent) and parent:is_player() then
player:set_detach()
end
end)