Compare commits

...

145 commits
v0.4 ... master

Author SHA1 Message Date
Jordan Irwin
c72b710c56
Allow running reference workflow manually 2025-01-18 03:03:35 -08:00
Jordan Irwin
209008b88d
Update changelog 2025-01-18 02:57:41 -08:00
Jordan Irwin
f35e2f1808
Add nil check after reading world data file
Closes: https://github.com/AntumMT/mod-cleaner/issues/3
2025-01-18 02:54:36 -08:00
Jordan Irwin
ecf5201220
Update version & changelog 2025-01-18 02:31:16 -08:00
Jordan Irwin
0d42a1bdd8
Update ContentDB config for move to Codeberg 2025-01-18 02:28:17 -08:00
Jordan Irwin
7d47f8ff5d
Update for name change to Luanti 2025-01-18 02:27:49 -08:00
Jordan Irwin
93fa96d6c2
Add FIXME note 2025-01-18 02:18:34 -08:00
Jordan Irwin
9ca550703b
Fix undeclared global...
https://github.com/AntumMT/mod-cleaner/issues/2
2022-01-10 14:21:48 -08:00
Jordan Irwin
b261dda15c LDoc: update gendoc script to add version info 2021-08-16 02:29:13 -07:00
Jordan Irwin
06e4408b91 Change workflow to build versioned docs on gh-pages 2021-08-16 02:24:16 -07:00
Jordan Irwin
6b3220048b LDoc: add script to build versioned docs 2021-08-16 02:18:29 -07:00
Jordan Irwin
f0004e3c7b Increment version to 1.2.1 2021-08-14 21:59:59 -07:00
Jordan Irwin
e3959a815f Add helper script for setting version 2021-08-14 21:55:00 -07:00
Jordan Irwin
45e59dbf8b Use sounds mod for playing sounds 2021-08-14 21:51:59 -07:00
Jordan Irwin
0098ef5da4 Add to TODO list 2021-08-08 20:49:39 -07:00
Jordan Irwin
51a812fa86 LDoc: add "multimodule" param to gendoc.sh script 2021-08-02 02:10:23 -07:00
Jordan Irwin
5efafec098 Fix typo in changelog & add info 2021-08-02 02:09:56 -07:00
Jordan Irwin
0ab5d51584 LDoc: remove custom css 2021-08-02 02:09:26 -07:00
Jordan Irwin
319e671fc2 Fix media_license in .cdb.json 2021-08-01 23:13:37 -07:00
Jordan Irwin
dd61f9f9cf "forums" must be an integer in .cdb.json 2021-08-01 23:08:32 -07:00
Jordan Irwin
8645c7aa49 Remove trailing comma from .cdb.json...
JSON format does not support trailing commas.
2021-08-01 17:09:08 -07:00
Jordan Irwin
3ac5558a9e LDoc: change favicon to link to Minetest official logo 2021-07-31 23:32:01 -07:00
Jordan Irwin
50c7a8cd98 LDoc: add favicon 2021-07-31 23:11:01 -07:00
Jordan Irwin
f9acae8bc7 LDoc: use cleaner_pencil texture for icon 2021-07-31 21:13:29 -07:00
Jordan Irwin
93767a2723 LDoc: fix message in gendoc.sh script 2021-07-31 21:12:43 -07:00
Jordan Irwin
da24ace035 Update docstrings 2021-07-31 18:47:34 -07:00
Jordan Irwin
e5e0d7f620 LDoc: format chat command parameters the same as functions 2021-07-31 18:44:54 -07:00
Jordan Irwin
b6ce523957 LDoc: add some special tags handling 2021-07-31 17:42:42 -07:00
Jordan Irwin
c00ee93e79 Docstrings update 2021-07-31 15:43:58 -07:00
Jordan Irwin
bb528d74a8 LDoc: add chat command handler 2021-07-31 15:42:50 -07:00
Jordan Irwin
7f9e645900 Increment version to 1.2 2021-07-31 14:37:27 -07:00
Jordan Irwin
fd6032a879 Fix node placement with pencil in write mode 2021-07-31 13:30:14 -07:00
Jordan Irwin
28f1f355c0 LDoc: clean up config 2021-07-31 04:21:41 -07:00
Jordan Irwin
df81a7755b Touch up docstrings 2021-07-31 04:19:35 -07:00
Jordan Irwin
689f911cb3 LDoc: new tags for embedding videos 2021-07-31 04:17:50 -07:00
Jordan Irwin
9c80a957e3 Fix typo in docstring 2021-07-31 03:36:19 -07:00
Jordan Irwin
29f772dc92 Add to TODO list 2021-07-31 01:20:34 -07:00
Jordan Irwin
9972213c9b Update localization template & Spanish translation 2021-07-31 01:20:16 -07:00
Jordan Irwin
0dbd7b1243 Add chat command to find names of nearby nodes 2021-07-31 01:19:52 -07:00
Jordan Irwin
0acbd3318b Fix message for find_unknown_nodes chat command 2021-07-31 01:17:58 -07:00
Jordan Irwin
95adf18f23 Check chat command radius parameter & limit max to 100 2021-07-31 01:17:07 -07:00
Jordan Irwin
09d6475ce0 Fix missing sound_hand declaration 2021-07-31 01:11:12 -07:00
Jordan Irwin
1277f7a784 Update TODO list 2021-07-30 23:55:08 -07:00
Jordan Irwin
44f643195c Add .cdb.json file for ContentDB updates 2021-07-30 23:48:00 -07:00
Jordan Irwin
657ba25890 Add screenshot 2021-07-30 23:20:45 -07:00
Jordan Irwin
df515241ed Add git attributes to exclude some dirs & files from releases 2021-07-30 23:16:37 -07:00
Jordan Irwin
f4b1053064 LDoc: some touch-ups to gendoc.sh script 2021-07-30 23:00:10 -07:00
Jordan Irwin
cbc6235f25 LDoc: set min-height for "content" element 2021-07-30 18:17:55 -07:00
Jordan Irwin
9052543301 Minor changes to Lua docstrings 2021-07-30 18:09:00 -07:00
Jordan Irwin
970d8b3aab Update API reference link in README 2021-07-30 17:57:26 -07:00
Jordan Irwin
c41f6e8a0a Delete static HTML docs 2021-07-30 17:56:24 -07:00
Jordan Irwin
f4b3e2e7c2 Add GitHub workflow for building HTML docs on gh-pages branch 2021-07-30 17:55:50 -07:00
Jordan Irwin
c13f78b471 LDoc: move files for generating docs into ".ldoc" directory 2021-07-30 17:54:33 -07:00
Jordan Irwin
99544d286f LDoc: set executable bit for gendoc.sh script 2021-07-30 17:19:48 -07:00
Jordan Irwin
fbcfe1646a Update localization template & Spanish translation 2021-07-30 17:18:37 -07:00
Jordan Irwin
4d959e69cb Update help strings for "ctool" chat command 2021-07-30 17:17:48 -07:00
Jordan Irwin
c98f6a656e Change "cleaner" chat command to "ctool" 2021-07-30 16:30:52 -07:00
Jordan Irwin
3a708a03b6 Re-generate HTML docs 2021-07-30 16:17:09 -07:00
Jordan Irwin
e0bf9a56e6 LDoc: add world path config file usage to HTML docs 2021-07-30 16:16:47 -07:00
Jordan Irwin
4c5069447c Add some details to settings docstrings 2021-07-30 16:15:52 -07:00
Jordan Irwin
64f31da3f2 LDoc: Add some functionality to config.ld 2021-07-30 16:15:21 -07:00
Jordan Irwin
c5ae889ee5 Fix typo in changelog 2021-07-30 16:13:23 -07:00
Jordan Irwin
cc11d749c2 Add to TODO list 2021-07-30 15:49:07 -07:00
Jordan Irwin
a708add44d Add/update localization strings 2021-07-30 15:29:26 -07:00
Jordan Irwin
182726e999 Clean up tool mode switching 2021-07-29 23:50:20 -07:00
Jordan Irwin
982a746e23 Re-generate HTML docs 2021-07-29 13:38:53 -07:00
Jordan Irwin
14394db3ea Add to TODO list 2021-07-29 13:38:29 -07:00
Jordan Irwin
4f9e5a6434 Move pencil into tools.lua 2021-07-29 13:38:10 -07:00
Jordan Irwin
43dadcb706 Re-generate HTML docs 2021-07-29 12:48:48 -07:00
Jordan Irwin
ca65bafb5f LDoc: use custom stylesheet to make navigation panel fixed 2021-07-29 12:48:35 -07:00
Jordan Irwin
d3082948b1 Add sound sources used to created OGG files 2021-07-29 12:42:08 -07:00
Jordan Irwin
cf01b532f0 Re-generate HTML docs 2021-07-29 12:40:38 -07:00
Jordan Irwin
d4ec55be08 Add usage docstring for pencil item 2021-07-29 12:40:25 -07:00
Jordan Irwin
9dfa90292c Flip pencil image when mode changed 2021-07-29 12:32:17 -07:00
Jordan Irwin
6c1104d19b Re-generate HTML docs 2021-07-28 01:44:52 -07:00
Jordan Irwin
6bbc63dd19 Don't list "cleaner" under unsafe commands in docs 2021-07-28 01:44:33 -07:00
Jordan Irwin
a657e566d5 Re-generate HTML docs 2021-07-28 01:41:26 -07:00
Jordan Irwin
b9455caeb2 LDoc: add "tool" type 2021-07-28 01:41:15 -07:00
Jordan Irwin
6b211cf5f6 Add pencil tool for erasing, adding, & swapping nodes 2021-07-28 01:39:38 -07:00
Jordan Irwin
84d3960664 Re-generate HTML docs 2021-07-27 20:48:51 -07:00
Jordan Irwin
a221c11ec6 Update docstrings 2021-07-27 20:48:31 -07:00
Jordan Irwin
b1fd6df8ce Add Spanish localization 2021-07-27 20:46:48 -07:00
Jordan Irwin
5568a1e91a Make chat commands plural 2021-07-27 19:30:31 -07:00
Jordan Irwin
d2209675ed Improve help messages 2021-07-27 19:22:06 -07:00
Jordan Irwin
cad47e4c88 Add to TODO list 2021-07-27 19:20:54 -07:00
Jordan Irwin
4bb6bd0300 Add required "textdomain" line to localizations template 2021-07-27 16:59:26 -07:00
Jordan Irwin
9365fd9515 Use more appropriate node swapping 2021-07-27 16:58:33 -07:00
Jordan Irwin
9fa1b2b4ff Register item replacement with node replacement 2021-07-20 19:46:58 -07:00
Jordan Irwin
9230e0c392 Re-generate HTML docs 2021-07-12 23:44:46 -07:00
Jordan Irwin
3080612409 Add remove_item function to API 2021-07-12 23:44:28 -07:00
Jordan Irwin
2c9a44fbc1 Re-generate HTML docs 2021-07-12 23:05:21 -07:00
Jordan Irwin
7a482de659 Support updating 3d_armor inventory lists via chat command...
https://content.minetest.net/packages/stu/3d_armor/
2021-07-12 23:03:23 -07:00
Jordan Irwin
5fcc824d0e make sure inventory exists before updating lists 2021-07-12 23:02:50 -07:00
Jordan Irwin
e81ed836dc Support updating items in cornernote's bags mod with chat command...
Homepage: http://cornernote.github.io/minetest-bags/
Git repo: https://github.com/cornernote/minetest-bags
2021-07-12 22:37:43 -07:00
Jordan Irwin
9592b1df73 Update player inventories when item is replaced with chat command 2021-07-12 22:11:00 -07:00
Jordan Irwin
f8fd1506eb Add to TODO list 2021-07-12 21:44:09 -07:00
Jordan Irwin
cc5856b561 Add details to chat command result messages 2021-07-12 21:43:16 -07:00
Jordan Irwin
4cece82cfa Add to TODO list 2021-07-12 20:02:44 -07:00
Jordan Irwin
df3428059c Add localization support...
Breaks compatibility with Minetever versions 0.4.x
2021-07-12 20:02:03 -07:00
Jordan Irwin
50db695476 Add to TODO list 2021-07-12 18:21:42 -07:00
Jordan Irwin
1495ebea16 Update README 2021-07-12 18:21:25 -07:00
Jordan Irwin
02489ec530 Re-generate HTML docs 2021-07-12 18:12:00 -07:00
Jordan Irwin
bb36a723d3 add support for unregistering ores via world path file 2021-07-12 18:11:37 -07:00
Jordan Irwin
efc5ef2660 use single json file for cleaning all types 2021-07-12 18:09:54 -07:00
Jordan Irwin
37ca2b7dab Rename API methods 2021-07-12 17:04:24 -07:00
Jordan Irwin
d065c3f7ae Add to TODO list 2021-07-12 16:20:36 -07:00
Jordan Irwin
ad2fc046a1 Re-generate HTML docs 2021-07-12 15:47:31 -07:00
Jordan Irwin
13a6fd04be Support "chatcmd" & "setting" types in LDoc generation 2021-07-12 15:46:33 -07:00
Jordan Irwin
2d7c5aee1f Add support for unregistering ores 2021-07-12 15:44:43 -07:00
Jordan Irwin
69f87b6b73 Add setting for enabling "unsafe" methods & commands 2021-07-12 15:41:53 -07:00
Jordan Irwin
7f042330b1 Add chat commands 2021-07-12 15:40:30 -07:00
Jordan Irwin
7d5f61756e Minor cleanup 2021-07-12 15:36:01 -07:00
Jordan Irwin
bf37b2e4a5 Add to TODO list 2021-07-12 09:38:24 -07:00
Jordan Irwin
0d4b29573c Link to API reference in README 2021-07-12 09:04:00 -07:00
Jordan Irwin
2afbdd55e2 Re-generate HTML docs 2021-07-12 09:02:51 -07:00
Jordan Irwin
fe80682515 Change API directory name 2021-07-12 09:02:33 -07:00
Jordan Irwin
bb94fa86a2 Generate HTML docs with LDoc 2021-07-12 08:59:14 -07:00
Jordan Irwin
958d45775c Add script & config for generating HTML docs with LDoc 2021-07-12 08:58:55 -07:00
Jordan Irwin
e75ee0ccd8 Add Lua docstrings 2021-07-12 08:58:30 -07:00
Jordan Irwin
261d0ed5e7 Add API 2021-07-12 08:40:45 -07:00
Jordan Irwin
774b22a55b Use register_on_mods_loaded 2021-07-12 06:17:59 -07:00
Jordan Irwin
7280c2a538 Add to TODO list 2021-07-12 06:06:01 -07:00
Jordan Irwin
3ee5184958 Increment version to 1.1 2021-05-27 20:44:01 -07:00
Jordan Irwin
2c127af4af Use "register_lbm" with "run_at_every_load" instead of...
..."register_abm" to save resources. Suggested by bell07: https://forum.minetest.net/viewtopic.php?p=325519#p325519
2021-05-27 20:42:04 -07:00
Jordan Irwin
6b50d1c52a Add ContentDB link to README 2021-05-26 22:16:09 -07:00
Jordan Irwin
f247118c63 Add to TODO list...
Suggestion by bell07 ( https://forum.minetest.net/viewtopic.php?p=325519#p325519 )
2021-05-26 22:11:41 -07:00
Jordan Irwin
80e70900b2 Update TODO list 2021-05-26 21:29:46 -07:00
Jordan Irwin
540ee14930 Increment version to 1.0 2021-05-26 21:27:39 -07:00
Jordan Irwin
51e76caeb2 Update README 2021-05-26 21:25:02 -07:00
Jordan Irwin
7a2d38a64c Add some LuaDoc 2021-05-26 20:55:00 -07:00
Jordan Irwin
0daee12f57 Add method for cleaning/replacing items 2021-05-26 20:54:43 -07:00
Jordan Irwin
8b5d09283e Add to TODO list 2021-05-26 20:18:54 -07:00
Jordan Irwin
9876268545 Rename "CHANGES.txt" to "changelog.txt" 2021-05-26 20:18:08 -07:00
Jordan Irwin
e8359d8db3 Use single ABM with group for replacing nodes 2021-05-18 21:31:13 -07:00
Jordan Irwin
35f409c5c0 Support replacing nodes 2021-05-18 20:50:14 -07:00
Jordan Irwin
8a7fc3ca04 Update changelog 2021-05-18 20:12:41 -07:00
Jordan Irwin
d69e47ca2a Convert to json format for clean_nodes file 2021-05-18 20:11:38 -07:00
Jordan Irwin
871981ddd7 Convert to json format for clean_entities file 2021-05-18 19:57:56 -07:00
Jordan Irwin
00ab41d6de Fix logging method 2021-05-18 19:57:24 -07:00
Jordan Irwin
839c32f5c0 Move node & entity functions into segregated modules 2021-05-18 18:11:24 -07:00
Jordan Irwin
5c58fd8f5d Clean up logging a tad 2021-05-18 18:04:33 -07:00
Jordan Irwin
5a918081e2 Cleanup 2021-05-18 17:57:07 -07:00
Jordan Irwin
9130ca1034 Add changelog & TODO list 2021-05-18 17:55:36 -07:00
Jordan Irwin
1f169c5774 Change license to MIT 2021-05-18 17:46:18 -07:00
Jordan Irwin
6d4fb21114 Move description into mod.conf 2021-05-18 17:44:59 -07:00
30 changed files with 2342 additions and 225 deletions

11
.cdb.json Normal file
View file

@ -0,0 +1,11 @@
{
"type": "MOD",
"title": "Cleaner",
"short_description": "Remove/Replace unknown entities, nodes, & items.",
"license": "MIT",
"media_license": "CC0-1.0",
"tags": ["world_tools"],
"repo": "https://codeberg.org/AntumLuanti/mod-cleaner",
"issue_tracker": "https://codeberg.org/AntumLuanti/mod-cleaner/issues",
"forums": 18381
}

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
.* export-ignore
*.py export-ignore

30
.github/workflows/reference.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Build Reference
on:
push:
tags:
- 'v[0-9]*'
workflow_dispatch:
jobs:
build:
name: Build Reference
runs-on: ubuntu-latest
steps:
- name: Setup Lua
uses: leafo/gh-actions-lua@v8
with:
luaVersion: 5.4
- name: Setup Lua Rocks
uses: leafo/gh-actions-luarocks@v4
- name: Setup dependencies
run: luarocks install --only-deps https://raw.githubusercontent.com/lunarmodules/LDoc/master/ldoc-scm-3.rockspec
- name: Setup LDoc
run: git clone --single-branch --branch=custom https://github.com/AntumDeluge/LDoc.git ldoc
- name: Checkout & Build Docs
run: git clone https://github.com/AntumMT/mod-cleaner.git cleaner && cd cleaner && chmod +x .ldoc/build_versioned_docs.sh && ./.ldoc/build_versioned_docs.sh
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: cleaner/docs/

98
.ldoc/build_versioned_docs.sh Executable file
View file

@ -0,0 +1,98 @@
#!/usr/bin/env bash
# place this file in mod ".ldoc" directory
d_config="$(dirname $(readlink -f $0))"
cd "${d_config}/.."
d_root="$(pwd)"
d_export="${d_export:-${d_root}/docs/reference}"
cmd_ldoc="${d_root}/../ldoc/ldoc.lua"
if test -f "${cmd_ldoc}"; then
if test ! -x "${cmd_ldoc}"; then
chmod +x "${cmd_ldoc}"
fi
else
cmd_ldoc="ldoc"
fi
# clean old files
rm -rf "${d_export}"
# store current branch
main_branch="$(git branch --show-current)"
html_out="<html>\n<head></head>\n\n<body>\n\n<ul>\n"
# generate new doc files
mkdir -p "${d_export}"
for vinfo in $(git tag -l --sort=-v:refname | grep "^v[0-9]"); do
echo -e "\nbuilding ${vinfo} docs ..."
git checkout ${vinfo}
d_temp="${d_config}/temp"
mkdir -p "${d_temp}"
# backward compat
f_config="${d_root}/docs/config.ld"
if test ! -f "${f_config}"; then
f_config="${d_config}/config.ld"
fi
if test ! -f "${f_config}"; then
echo -e "\nLDoc config not available for ${vinfo}, skipping build ..."
continue
fi
"${cmd_ldoc}" --UNSAFE_NO_SANDBOX --multimodule -c "${f_config}" -d "${d_temp}" "${d_root}"; retval=$?
if test ${retval} -ne 0; then
echo -e "\nERROR: doc build for ${vinfo} failed!"
rm -rf "${d_temp}"
continue
fi
# show version info
for html in $(find "${d_temp}" -type f -name "*.html"); do
sed -i -e "s|^<h1>[cC]leaner</h1>$|<h1>Cleaner <span style=\"font-size:12pt;\">(${vinfo})</span></h1>|" \
"${html}"
done
if test -d "${d_root}/textures"; then
# copy textures to data directory
echo -e "\ncopying textures ..."
d_data="${d_temp}/data"
mkdir -p "${d_data}"
texture_count=0
for png in $(find "${d_root}/textures" -maxdepth 1 -type f -name "*.png"); do
t_png="${d_data}/$(basename ${png})"
if test -f "${t_png}"; then
echo "WARNING: not overwriting existing file: ${t_png}"
else
cp "${png}" "${d_data}"
texture_count=$((texture_count + 1))
printf "\rcopied ${texture_count} textures"
fi
done
fi
mv "${d_temp}" "${d_export}/${vinfo}"
if test -z ${vcur+x}; then
vcur="${vinfo}"
ln -s "${d_export}/${vinfo}" "${d_export}/current"
ln -s "${d_export}/${vinfo}" "${d_export}/latest"
html_out="${html_out} <li><a href=\"current/\">current</a></li>\n"
html_out="${html_out} <li><a href=\"latest/\">latest</a></li>\n"
fi
html_out="${html_out} <li><a href=\"${vinfo}/\">${vinfo}</a></li>\n"
done
html_out="${html_out}</ul>\n\n</body></html>"
cd "${d_root}"
git checkout ${main_branch}
echo -e "${html_out}" > "${d_export}/index.html"
echo -e "\nDone!"

201
.ldoc/config.ld Normal file
View file

@ -0,0 +1,201 @@
local dofile, print, error, type, table, ipairs, string, tostring
if import then
dofile = import("dofile")
print = import("print")
error = import("error")
type = import("type")
table = import("table")
ipairs = import("ipairs")
string = import("string")
tostring = import("tostring")
end
project = "Cleaner"
title = "Cleaner mod for Luanti"
format = "markdown"
not_luadoc=true
boilerplate = false
icon = "textures/cleaner_pencil.png"
favicon = "https://www.luanti.org/media/icon.svg"
file = {
"settings.lua",
"api.lua",
"chat.lua",
"tools.lua",
".ldoc/config.luadoc",
}
new_type("chatcmd", "Chat Commands")
new_type("setting", "Settings")
new_type("tool", "Tools")
new_type("json", "JSON Configurations")
local function video_frame(src)
return '<iframe width="560" height="315" src="' .. src
.. '" title="Video Player" frameborder="0"'
.. ' allow="fullscreen;"></iframe>'
end
local tags
tags, custom_tags = dofile(".ldoc/tags.ld")
-- START: handling items to prevent re-parsing
local registered_items = {}
local function is_registered(item)
if not registered_items[item.type] then return false end
for _, tbl in ipairs(registered_items[item.type]) do
if item == tbl then
return true
end
end
return false
end
local function register(item)
if not registered_items[item.type] then
registered_items[item.type] = {}
end
if not is_registered(item) then
table.insert(registered_items[item.type], item)
end
end
-- END:
local function format_setting_tag(desc, value)
return "\n- <span style=\"font-size:80%;\">`" .. desc .. ":`</span> `" .. value .. "`"
end
local function setting_handler(item)
if not ipairs or not type then
return item
end
local tags = {
{"settype", "type"},
{"default"},
{"min", "minimum value"},
{"max", "maximum value"},
}
local def = {
["settype"] = format_setting_tag("type", "string"),
}
for _, t in ipairs(tags) do
local name = t[1]
local desc = t[2]
if not desc then desc = name end
local value = item.tags[name]
if type(value) == "table" then
if #value > 1 then
local msg = item.file.filename .. " (line " .. item.lineno
.. "): multiple instances of tag \"" .. name .. "\" found"
if error then
error(msg)
elseif print then
print("WARNING: " .. msg)
end
end
if value[1] then
def[name] = format_setting_tag(desc, value[1])
end
end
end
item.description = item.description .. "\n\n**Definition:**\n" .. def.settype
for _, t in ipairs({def.default, def.min, def.max}) do
if t then
item.description = item.description .. t
end
end
return item
end
local function chatcmd_handler(item)
for _, p in ipairs(item.params) do
if item.modifiers.param[p].opt then
item.name = item.name .. " [" .. p .. "]"
else
item.name = item.name .. " &lt;" .. p .. "&gt;"
end
end
if #item.params > 0 then
local pstring = "### Parameters:\n"
for k, param in pairs(item.params) do
if type(k) == "number" then
local value = item.params.map[param]
pstring = pstring .. '\n- <span class="parameter">'
.. param .. '</span>'
local modifiers = item.modifiers.param[param]
if modifiers and modifiers.type then
pstring = pstring .. ' <span class="types"><span class="type">`' .. modifiers.type .. '`</span></span>'
end
if value then
pstring = pstring .. value
end
if modifiers and modifiers.opt then
pstring = pstring .. " *(optional)*"
end
end
end
item.description = item.description .. "\n\n" .. pstring
-- clear parameter list
item.params = {}
end
return item
end
function custom_display_name_handler(item, default_handler)
if not is_registered(item) then
if item.type == "setting" then
item = setting_handler(item)
elseif item.type == "chatcmd" then
item = chatcmd_handler(item)
end
local parse_tags = {"priv", "note"}
for _, pt in ipairs(parse_tags) do
local tvalues = item.tags[pt]
if tvalues then
local tstring = ""
local title = tags.get_title(pt)
if title then
tstring = tstring .. "\n\n### " .. title .. ":\n"
end
for _, tv in ipairs(tvalues) do
tstring = tstring .. "\n- " .. tags.format(pt, tv)
end
item.description = item.description .. tstring
end
end
end
register(item)
return default_handler(item)
end

55
.ldoc/config.luadoc Normal file
View file

@ -0,0 +1,55 @@
--- World Path Configuration
--
-- @topic config
--- Main configuration file.
--
-- Registering items, entities, etc. for cleaning can be done in ***cleaner.json***
-- in the world directory (`<world_path>/cleaner.json`). If it does not exist
-- it will be created automatically when the server is started.
--
-- It is formatted as follows:
--
-- {
-- "entities" :
-- {
-- "remove" : []
-- },
-- "items" :
-- {
-- "replace" : {}
-- },
-- "nodes" :
-- {
-- "remove" : [],
-- "replace" : {}
-- },
-- "ores" :
-- {
-- "remove" : []
-- }
-- }
--
-- `remove` key works for nodes, entities, & ores. `replace` key works for
-- nodes & items. Their functions are self-explanatory.
--
-- @json cleaner.json
-- @usage
-- Cleaning nodes example:
-- {
-- "nodes" :
-- {
-- "remove" :
-- [
-- "old:node_1",
-- "old:node_2",
-- ],
-- "replace" :
-- {
-- "old:node_3" : "new:node_1",
-- "old:node_4" : "new:node_2",
-- },
-- },
-- }

54
.ldoc/gendoc.sh Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env bash
# place this file in mod ".ldoc" directory
d_ldoc="$(dirname $(readlink -f $0))"
f_config="${d_ldoc}/config.ld"
cd "${d_ldoc}/.."
d_root="$(pwd)"
d_export="${d_export:-${d_root}/docs/reference}"
cmd_ldoc="${d_ldoc}/ldoc/ldoc.lua"
if test ! -x "${cmd_ldoc}"; then
cmd_ldoc="ldoc"
fi
# clean old files
rm -rf "${d_export}"
vinfo="v$(grep "^version = " "${d_root}/mod.conf" | head -1 | sed -e 's/version = //')"
d_data="${d_export}/${vinfo}/data"
# generate new doc files
"${cmd_ldoc}" --UNSAFE_NO_SANDBOX --multimodule -c "${f_config}" -d "${d_export}/${vinfo}" "${d_root}"; retval=$?
# check exit status
if test ${retval} -ne 0; then
echo -e "\nan error occurred (ldoc return code: ${retval})"
exit ${retval}
fi
# show version info
for html in $(find "${d_export}/${vinfo}" -type f -name "*.html"); do
sed -i -e "s|^<h1>[cC]leaner</h1>$|<h1>Cleaner <span style=\"font-size:12pt;\">(${vinfo})</span></h1>|" "${html}"
done
# copy textures to data directory
echo -e "\ncopying textures ..."
mkdir -p "${d_data}"
texture_count=0
for png in $(find "${d_root}/textures" -maxdepth 1 -type f -name "*.png"); do
t_png="${d_data}/$(basename ${png})"
if test -f "${t_png}"; then
echo "WARNING: not overwriting existing file: ${t_png}"
else
cp "${png}" "${d_data}"
texture_count=$((texture_count + 1))
printf "\rcopied ${texture_count} textures"
fi
done
echo -e "\n\nDone!"

81
.ldoc/tags.ld Normal file
View file

@ -0,0 +1,81 @@
local tags = {}
local tag_list = {}
local custom_tags = {}
local register_tag = function(name, tag)
local new_tag = {name, title=tag.title, hidden=tag.hidden, format=tag.format}
table.insert(custom_tags, new_tag)
tag_list[name] = {title=tag.title, format=tag.format}
end
tags.get_title = function(tname)
local t = tag_list[tname]
if t then
return t.title
end
end
tags.format = function(tname, value)
local t = tag_list[tname]
if t then
if type(t.format) == "function" then
value = t.format(value)
end
end
return value
end
local new_tags = {
["priv"] = {
title = "Required Privileges",
hidden = true,
},
["note"] = {
title = "Notes",
hidden = true,
format = function(value)
return "*" .. value .. "*"
end,
},
["video"] = {
title = "Video",
format = video_frame,
},
["youtube"] = {
title = "Video",
format = function(value)
return video_frame("https://www.youtube.com/embed/" .. value)
end,
},
-- settings
["settype"] = {
title = "Setting Type",
hidden = true,
},
["default"] = {
title = "Default Value",
hidden = true,
},
-- craft items/tools
["img"] = {
title = "Image",
format = function(value)
return "<img src=\"../data/" .. value .. "\" style=\"width:32px; height:32px;\" />"
end,
},
-- chat commands
["chatparam"] = {
title = "Parameters",
hidden = true,
},
}
for k, v in pairs(new_tags) do
register_tag(k, v)
end
return tags, custom_tags

View file

@ -1,121 +1,21 @@
Creative Commons Legal Code
The MIT License (MIT)
CC0 1.0 Universal
Copyright © 2021 Jordan Irwin (AntumDeluge)
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
Statement of Purpose
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,25 +1,86 @@
## Cleaner mod for [Minetest][]
## Cleaner mod for Luanti
### Description:
---
### **Description:**
A [Luanti (Minetest)][Luanti] mod that can be used to remove/replace unknown entities, nodes, & items. Originally forked from [PilzAdam's ***clean*** mod][f.pilzadam].
A simple Minetest mod that can be used to remove unknown entities & nodes. Forked from [PilzAdam's ***clean*** mod][f.pilzadam].
![screenshot](screenshot.png)
### Licensing:
---
### **Requirements:**
- Code: [MIT](LICENSE.txt)
- Textures: CC0
- Minetest 0.4.16 or newer
### Requirements:
- Luanti minimum version: 5.0
- Depends: none
### Usage:
---
### **Licensing:**
Registering items, entities, etc. for cleaning can be done in `cleaner.json` in the world directory. If it does not exist it will be created automatically when the server is started.
[CC0](LICENSE.txt)
It is formatted as follows:
```json
{
"entities" :
{
"remove" : []
},
"items" :
{
"replace" : {}
},
"nodes" :
{
"remove" : [],
"replace" : {}
},
"ores" :
{
"remove" : []
}
}
```
Cleaning nodes example:
```json
{
"nodes" :
{
"remove" : [
"old:node_1",
"old:node_2",
],
"replace" : {
"old:node_3" : "new:node_1",
"old:node_4" : "new:node_2",
}
},
}
```
`remove` key works for nodes, entities, & ores. `replace` key works for nodes & items. Their functions are self-explanatory.
#### Settings:
```
cleaner.unsafe
- Enables unsafe methods & commands (remove_ore).
- type: bool
- default: false
```
### Links:
- [![ContentDB](https://content.luanti.org/packages/AntumDeluge/cleaner/shields/title/)][ContentDB]
- [Forum](https://forum.luanti.org/viewtopic.php?t=18381)
- [Git repo](https://github.com/AntumMT/mod-cleaner)
- [Reference](https://antummt.github.io/mod-cleaner/reference/latest/)
- [Changelog](changelog.txt)
- [TODO](TODO.txt)
[Minetest]: http://www.minetest.net/
[f.pilzadam]: https://forum.minetest.net/viewtopic.php?t=2777
[Luanti]: https://luanti.org/
[f.pilzadam]: https://forum.luanti.org/viewtopic.php?t=2777
[ContentDB]: https://content.luanti.org/packages/AntumDeluge/cleaner/

14
TODO.txt Normal file
View file

@ -0,0 +1,14 @@
TODO:
- update world file when chat commands are used
- update inventories when items are replaced:
- creative
- storage (chests, etc.)
- add LBM when removing an item if it is a node
- add "radius" option for pencil or "xlen", "ylen", & "zlen" options
- add "xrotate" & "zrorate" modes for pencil
- don't require "server" priv for "find_unknown_nodes" & "find_neaby_nodes" commands
- add chat command to find nodes with specified attributes
- may be better to update player inventories on login than add aliases for items
- use aliases for unknown nodes instead of LBM
- only use LBM when a node to replace is still registered

194
api.lua Normal file
View file

@ -0,0 +1,194 @@
--- Cleaner API
--
-- @topic api
local replace_items = {}
local replace_nodes = {}
--- Retrieves list of items to be replaced.
--
-- @treturn table Items to be replaced.
function cleaner.get_replace_items()
return replace_items
end
--- Retrieves list of nodes to be replaced.
--
-- @treturn table Nodes to be replaced.
function cleaner.get_replace_nodes()
return replace_nodes
end
--- Registers an entity to be removed.
--
-- @tparam string src Entity technical name.
function cleaner.register_entity_removal(src)
core.register_entity(":" .. src, {
on_activate = function(self, ...)
self.object:remove()
end,
})
end
--- Registers a node to be removed.
--
-- @tparam string src Node technical name.
function cleaner.register_node_removal(src)
core.register_node(":" .. src, {
groups = {to_remove=1},
})
end
local function update_list(inv, listname, src, tgt)
if not inv then
cleaner.log("error", "cannot update list of unknown inventory")
return
end
local list = inv:get_list(listname)
if not list then
cleaner.log("warning", "unknown inventory list: " .. listname)
return
end
for idx, stack in pairs(list) do
if stack:get_name() == src then
local new_stack = ItemStack(tgt)
new_stack:set_count(stack:get_count())
inv:set_stack(listname, idx, new_stack)
end
end
end
--- Replaces an item with another registered item.
--
-- @tparam string src Technical name of item to be replaced.
-- @tparam string tgt Technical name of item to be used in place.
-- @tparam[opt] bool update_players `true` updates inventory lists associated with players (default: `false`).
function cleaner.replace_item(src, tgt, update_players)
update_players = not (update_players ~= true)
if not core.registered_items[tgt] then
return false, S('Cannot use unknown item "@1" as replacement.', tgt)
end
if not core.registered_items[src] then
cleaner.log("info", "\"" .. src .. "\" not registered, not unregistering")
else
cleaner.log("warning", "overriding registered item \"" .. src .. "\"")
core.unregister_item(src)
if core.registered_items[src] then
cleaner.log("error", "could not unregister \"" .. src .. "\"")
end
end
core.register_alias(src, tgt)
if core.registered_aliases[src] == tgt then
cleaner.log("info", "registered alias \"" .. src .. "\" for \"" .. tgt .. "\"")
else
cleaner.log("error", "could not register alias \"" .. src .. "\" for \"" .. tgt .. "\"")
end
local bags = core.get_modpath("bags") ~= nil
local armor = core.get_modpath("3d_armor") ~= nil
-- update player inventories
if update_players then
for _, player in ipairs(core.get_connected_players()) do
local pinv = player:get_inventory()
update_list(pinv, "main", src, tgt)
if bags then
for i = 1, 4 do
update_list(pinv, "bag" .. i .. "contents", src, tgt)
end
end
if armor then
local armor_inv = core.get_inventory({type="detached", name=player:get_player_name() .. "_armor"})
update_list(armor_inv, "armor", src, tgt)
end
end
end
return true
end
--- Registeres an item to be replaced.
--
-- @tparam string src Technical name of item to be replaced.
-- @tparam string tgt Technical name of item to be used in place.
function cleaner.register_item_replacement(src, tgt)
replace_items[src] = tgt
end
--- Registers a node to be replaced.
--
-- @tparam string src Technical name of node to be replaced.
-- @tparam string tgt Technical name of node to be used in place.
function cleaner.register_node_replacement(src, tgt)
core.register_node(":" .. src, {
groups = {to_replace=1},
})
replace_nodes[src] = tgt
cleaner.register_item_replacement(src, tgt)
end
--- Unsafe Methods.
--
-- Enabled with [cleaner.unsafe](settings.html#cleaner.unsafe) setting.
--
-- @section unsafe
if cleaner.unsafe then
local remove_ores = {}
--- Retrieves list of ores to be removed.
--
-- @treturn table Ores to be removed.
function cleaner.get_remove_ores()
return remove_ores
end
--- Registers an ore to be removed after server startup.
--
-- @tparam string src Ore technical name.
function cleaner.register_ore_removal(src)
table.insert(remove_ores, src)
end
--- Removes an ore definition.
--
-- @tparam string src Ore technical name.
function cleaner.remove_ore(src)
local remove_ids = {}
local total_removed = 0
local registered = false
for id, def in pairs(core.registered_ores) do
if def.ore == src then
table.insert(remove_ids, id)
registered = true
end
end
for _, id in ipairs(remove_ids) do
core.registered_ores[id] = nil
if core.registered_ores[id] then
cleaner.log("error", "unable to unregister ore " .. id)
else
total_removed = total_removed + 1
end
end
return registered, total_removed
end
end

63
changelog.txt Normal file
View file

@ -0,0 +1,63 @@
2025-01-18
----------
- fix undeclared global
- added nil check after reading world data file
v1.2.1
----
- use sounds mod for sounds
- added nil check after reading world data file
v1.2
----
- added API
- added support for unregistering ores (unsafe)
- added setting for enabling "unsafe" methods & commands
- all types are loaded from <world_path>/cleaner.json file
- added localization support
- added Spanish localization
- added pencil tool for erasing, adding, & swapping nodes
- added chat commands:
- remove_entities
- remove_nodes
- replace_items
- replace_nodes
- find_unknown_nodes
- find_nearby_nodes
- remove_ores (unsafe)
- ctool (manages wielded cleaner tool settings)
v1.1
----
- uses "register_lbm" with "run_at_every_load" instead of "register_abm" to save resources
- suggested by bell07 ( https://forum.luanti.org/viewtopic.php?p=325519#p325519 )
v1.0
----
- changed license to MIT
- "clean_entities" & "clean_nodes" files now use json format
- nodes can be replaced with other nodes
- items can be replaced with other items (<world_path>/clean_items.json file)
v0.4
----
- changed technical name to "cleaner"
- re-added functionality to clean nodes
v0.3
----
- removed functionality for cleaning anything other than entities
v0.2
----
- changed license to CC0
- added some log output
- entities to be cleaned can be configured & loaded from world directory
v0.1
----
- forked from PilzAdam's "clean" mod @ forum post updated: 2013-06-08
- replaced deprecated call to "minetest.env"

660
chat.lua Normal file
View file

@ -0,0 +1,660 @@
--- Cleaner Chat Commands
--
-- @topic commands
local S = core.get_translator(cleaner.modname)
local aux = dofile(cleaner.modpath .. "/misc_functions.lua")
local function pos_list(ppos, radius)
local plist = {}
for x = ppos.x - radius, ppos.x + radius, 1 do
for y = ppos.y - radius, ppos.y + radius, 1 do
for z = ppos.z - radius, ppos.z + radius, 1 do
table.insert(plist, {x=x, y=y, z=z})
end
end
end
return plist
end
local param_def = {
radius = {name=S("radius"), desc=S("Search radius.")},
entity = {name=S("entity"), desc=S("Entity technical name.")},
node = {name=S("node"), desc=S("Node technical name.")},
old_node = {name=S("old_node"), desc=S("Technical name of node to be replaced.")},
new_node = {name=S("new_node"), desc=S("Technical name of node to be used in place.")},
old_item = {name=S("old_item"), desc=S("Technical name of item to be replaced.")},
new_item = {name=S("new_item"), desc=S("Technical name of item to be used in place.")},
ore = {name=S("ore"), desc=S("Ore technical name.")},
action = {name=S("action"),
desc=S('Action to execute. Can be one of "@1", "@2", or "@3".', "status", "setmode", "setnode")},
value = {name=S("value"), desc=S('Mode or node to be set for tool (not required for "@1" action).', "status")},
}
local cmd_repo = {
entity = {
cmd = "remove_entities",
params = {"entity"},
oparams = {radius=100},
},
rem_node = {
cmd = "remove_nodes",
params = {"node"},
oparams = {radius=5},
},
rep_node = {
cmd = "replace_nodes",
params = {"old_node", "new_node"},
oparams = {radius=5},
},
find_node = {
cmd = "find_unknown_nodes",
oparams = {radius=100},
},
near_node = {
cmd = "find_nearby_nodes",
oparams = {radius=5},
},
item = {
cmd = "replace_items",
params = {"old_item", "new_item"},
},
ore = {
cmd = "remove_ores",
params = {"ore"},
},
tool = {
cmd = "ctool",
params = {"action", "value"},
},
param = {
missing = S("Missing parameter."),
excess = S("Too many parameters."),
mal_radius = S("Radius must be a number."),
},
}
for k, def in pairs(cmd_repo) do
if k ~= "param" then
local cmd_help = {
param_string = "",
usage_string = "/" .. def.cmd,
}
if def.params or def.oparams then
if def.params then
local params = {}
for _, p in ipairs(def.params) do
-- translate
table.insert(params, S(p))
end
cmd_help.param_string = "<" .. table.concat(params, "> <") .. ">"
end
end
if def.oparams then
for k, v in pairs(def.oparams) do
local op = k
if type(op) == "number" then
op = v
end
cmd_help.param_string = cmd_help.param_string .. " [" .. S(op) .. "]"
end
end
if cmd_help.param_string ~= "" then
cmd_help.usage_string = cmd_help.usage_string .. " " .. cmd_help.param_string
end
cmd_repo[k].help = cmd_help
end
end
local function get_cmd_def(cmd)
for k, v in pairs(cmd_repo) do
if v.cmd == cmd then return v end
end
end
local function format_usage(cmd)
local def = get_cmd_def(cmd)
if def then
return S("Usage:") .. "\n " .. def.help.usage_string
end
end
local function format_params(cmd)
local def = get_cmd_def(cmd)
local param_count
-- FIXME: unused?
local all_params = {}
if def.params then
for _, p in ipairs(def.params) do
table.insert(all_params, p)
end
end
if def.oparams then
for k, v in pairs(def.oparams) do
end
end
local retval = ""
local p_count = 0
if def.params then
for _, p in ipairs(def.params) do
if p_count == 0 then
retval = retval .. S("Params:")
end
retval = retval .. "\n " .. S(p) .. ": " .. param_def[p].desc
p_count = p_count + 1
end
end
if def.oparams then
for k, v in pairs(def.oparams) do
if p_count == 0 then
retval = retval .. S("Params:")
end
local p = k
local dvalue = v
if type(p) == "number" then
p = v
dvalue = nil
end
retval = retval .. "\n " .. S(p) .. ": " .. param_def[p].desc
if dvalue then
retval = retval .. " (" .. S("default: @1", dvalue) .. ")"
end
p_count = p_count + 1
end
end
return retval
end
local function format_help(cmd)
return format_usage(cmd) .. "\n\n" .. format_params(cmd)
end
local function check_radius(radius, pname)
local is_admin = core.check_player_privs(pname, {server=true})
if not is_admin and radius > 10 then
radius = 10
return radius, S("You do not have permission to set radius that high. Reduced to @1.", radius)
end
if radius > 100 then
radius = 100
return radius, S("Radius is too high. Reduced to @1.", radius)
end
return radius
end
--- Removes nearby entities.
--
-- @chatcmd remove_entities
-- @param entity Entity technical name.
-- @tparam[opt] int radius Search radius (default: 100).
-- @priv server
-- @usage
-- # remove all mobs:horse entities within a radius of 10 nodes
-- /remove_entities mobs:horse 10
core.register_chatcommand(cmd_repo.entity.cmd, {
privs = {server=true},
description = S("Remove an entity from game.") .. "\n\n"
.. format_params(cmd_repo.entity.cmd),
params = cmd_repo.entity.help.param_string,
func = function(name, param)
local entity
local radius = cmd_repo.entity.oparams.radius
if param:find(" ") then
entity = param:split(" ")
radius = tonumber(entity[2])
entity = entity[1]
else
entity = param
end
local err
if not entity or entity:trim() == "" then
err = cmd_repo.param.missing
elseif not radius then
err = cmd_repo.param.mal_radius
end
local radius, msg = check_radius(radius, name)
if msg then
core.chat_send_player(name, msg)
end
if err then
return false, err .. "\n\n" .. format_help(cmd_repo.entity.cmd)
end
local player = core.get_player_by_name(name)
local total_removed = 0
for _, object in ipairs(core.get_objects_inside_radius(player:get_pos(), radius)) do
local lent = object:get_luaentity()
if lent then
if lent.name == entity then
object:remove()
total_removed = total_removed + 1
end
else
if object:get_properties().infotext == entity then
object:remove()
total_removed = total_removed + 1
end
end
end
return true, S("Removed @1 entities.", total_removed)
end,
})
--- Removes nearby nodes.
--
-- @chatcmd remove_nodes
-- @param node Node technical name.
-- @tparam[opt] int radius Search radius (default: 5).
-- @priv server
-- @usage
-- # remove all default:dirt nodes within a radius of 10
-- /remove_nodes default:dirt 10
core.register_chatcommand(cmd_repo.rem_node.cmd, {
privs = {server=true},
description = S("Remove a node from game.") .. "\n\n"
.. format_params(cmd_repo.rem_node.cmd),
params = cmd_repo.rem_node.help.param_string,
func = function(name, param)
local nname
local radius = cmd_repo.rem_node.oparams.radius
if param:find(" ") then
nname = param:split(" ")
radius = tonumber(nname[2])
nname = nname[1]
else
nname = param
end
local err
if not nname or nname:trim() == "" then
err = cmd_repo.param.missing
elseif not radius then
err = cmd_repo.param.mal_radius
end
local radius, msg = check_radius(radius, name)
if msg then
core.chat_send_player(name, msg)
end
if err then
return false, err .. "\n\n" .. format_help(cmd_repo.rem_node.cmd)
end
local ppos = core.get_player_by_name(name):get_pos()
local total_removed = 0
for _, npos in ipairs(pos_list(ppos, radius)) do
local node = core.get_node_or_nil(npos)
if node and node.name == nname then
core.remove_node(npos)
total_removed = total_removed + 1
end
end
return true, S("Removed @1 nodes.", total_removed)
end,
})
--- Replaces an item.
--
-- @chatcmd replace_items
-- @param old_item Technical name of item to replace.
-- @param new_item Technical name of item to be used in place.
-- @priv server
-- @usage
-- # replace default:sword_wood with default:sword_mese
-- /replace_items default:sword_wood default:sword_mese
core.register_chatcommand(cmd_repo.item.cmd, {
privs = {server=true},
description = S("Replace an item in game.") .. "\n\n"
.. format_params(cmd_repo.item.cmd),
params = cmd_repo.item.help.param_string,
func = function(name, param)
if not param:find(" ") then
return false, cmd_repo.param.missing .. "\n\n" .. format_help(cmd_repo.item.cmd)
end
local src = param:split(" ")
local tgt = src[2]
src = src[1]
local retval, msg = cleaner.replace_item(src, tgt, true)
if not retval then
return false, msg
end
return true, S("Success!")
end,
})
--- Replaces nearby nodes.
--
-- @chatcmd replace_nodes
-- @param old_node Technical name of node to replace.
-- @param new_node Technical name of node to be used in place.
-- @tparam[opt] int radius Search radius (default: 5).
-- @priv server
-- @usage
-- # replace all default:dirt nodes with default:cobble within a radius of 10
-- /replace_nodes default:dirt default:cobble 10
core.register_chatcommand(cmd_repo.rep_node.cmd, {
privs = {server=true},
description = S("Replace a node in game.") .. "\n\n"
.. format_params(cmd_repo.rep_node.cmd),
params = cmd_repo.rep_node.help.param_string,
func = function(name, param)
local help = format_help(cmd_repo.rep_node.cmd)
if not param:find(" ") then
return false, cmd_repo.param.missing .. "\n\n" .. help
end
local radius = cmd_repo.rep_node.oparams.radius
local params = param:split(" ")
local src = params[1]
local tgt = tostring(params[2])
if #params > 2 then
radius = tonumber(params[3])
end
if not radius then
return false, cmd_repo.param.mal_radius .. "\n\n" .. help
end
local radius, msg = check_radius(radius, name)
if msg then
core.chat_send_player(name, msg)
end
if not core.registered_nodes[tgt] then
return false, S('Cannot use unknown node "@1" as replacement.', tgt)
end
local total_replaced = 0
local ppos = core.get_player_by_name(name):get_pos()
for _, npos in ipairs(pos_list(ppos, radius)) do
local node = core.get_node_or_nil(npos)
if node and node.name == src then
if core.swap_node(npos, {name=tgt}) then
total_replaced = total_replaced + 1
else
cleaner.log("error", "could not replace node at " .. core.pos_to_string(npos, 0))
end
end
end
return true, S("Replaced @1 nodes.", total_replaced)
end,
})
--- Checks for nearby unknown nodes.
--
-- @chatcmd find_unknown_nodes
-- @tparam[opt] int radius Search radius (default: 100).
-- @priv server
-- @usage
-- # print names of all unknown nodes within radius of 10
-- /find_unknown_nodes 10
core.register_chatcommand(cmd_repo.find_node.cmd, {
privs = {server=true},
description = S("Find names of unknown nodes.") .. "\n\n"
.. format_params(cmd_repo.find_node.cmd),
params = cmd_repo.find_node.help.param_string,
func = function(name, param)
local help = format_help(cmd_repo.find_node.cmd)
if param:find(" ") then
return false, cmd_repo.param.excess .. "\n\n" .. help
end
local radius = cmd_repo.find_node.oparams.radius
if param and param:trim() ~= "" then
radius = tonumber(param)
end
if not radius then
return false, cmd_repo.param.mal_radius .. "\n\n" .. help
end
local radius, msg = check_radius(radius, name)
if msg then
core.chat_send_player(name, msg)
end
local ppos = core.get_player_by_name(name):get_pos()
local checked_nodes = {}
local unknown_nodes = {}
for _, npos in ipairs(pos_list(ppos, radius)) do
local node = core.get_node_or_nil(npos)
if node and not checked_nodes[node.name] then
if not core.registered_nodes[node.name] then
table.insert(unknown_nodes, node.name)
end
checked_nodes[node.name] = true
end
end
local node_count = #unknown_nodes
if node_count > 0 then
msg = S("Found unknown nodes: @1", node_count) .. "\n " .. table.concat(unknown_nodes, ", ")
else
msg = S("No unknown nodes found.")
end
return true, msg
end,
})
--- Finds names of nearby nodes.
--
-- @chatcmd find_nearby_nodes
-- @tparam[opt] int radius Search radius (default: 5).
-- @priv server
-- @usage
-- # print names of all node types found within radius of 10
-- /find_nearby_nodes 10
core.register_chatcommand(cmd_repo.near_node.cmd, {
privs = {server=true},
description = S("Find names of nearby nodes.") .. "\n\n"
.. format_params(cmd_repo.near_node.cmd),
params = cmd_repo.near_node.help.param_string,
func = function(name, param)
local help = format_help(cmd_repo.near_node.cmd)
if param:find(" ") then
return false, cmd_repo.param.excess .. "\n\n" .. help
end
local radius = cmd_repo.near_node.oparams.radius
if param and param:trim() ~= "" then
radius = tonumber(param)
end
if not radius then
return false, cmd_repo.param.mal_radius .. "\n\n" .. help
end
local radius, msg = check_radius(radius, name)
if msg then
core.chat_send_player(name, msg)
end
local ppos = core.get_player_by_name(name):get_pos()
local node_names = {}
for _, npos in ipairs(pos_list(ppos, radius)) do
local node = core.get_node_or_nil(npos)
if node and not node_names[node.name] then
node_names[node.name] = true
end
end
local found_nodes = {}
for k, _ in pairs(node_names) do
table.insert(found_nodes, k)
end
local msg
local node_count = #found_nodes
if node_count > 0 then
msg = S("Nearby nodes: @1", node_count) .. "\n " .. table.concat(found_nodes, ", ")
else
msg = S("No nearby nodes found.")
end
return true, msg
end,
})
--- Unsafe Commands.
--
-- Enabled with [cleaner.unsafe](settings.html#cleaner.unsafe) setting.
--
-- @section unsafe
if cleaner.unsafe then
--- Registers an ore to be removed.
--
-- @chatcmd remove_ores
-- @param ore Ore technical name.
-- @priv server
-- @note This action is reverted after server restart. To make changes permanent,
-- use the [cleaner.json](config.html#cleaner.json) config.
-- @usage
-- # remove all registered ores that add default:stone_with_iron to world
-- /remove_ores default:stone_with_iron
core.register_chatcommand(cmd_repo.ore.cmd, {
privs = {server=true},
description = S("Remove an ore from game.") .. "\n\n"
.. format_params(cmd_repo.ore.cmd),
params = cmd_repo.ore.help.param_string,
func = function(name, param)
local err
if not param or param:trim() == "" then
err = cmd_repo.param.missing
elseif param:find(" ") then
err = cmd_repo.param.excess
end
if err then
return false, err .. "\n\n" .. format_help(cmd_repo.ore.cmd)
end
local success = false
local msg
local registered, total_removed = cleaner.remove_ore(param)
if not registered then
msg = S('Ore "@1" not found, not unregistering.', param)
else
msg = S("Unregistered @1 ores (this will be undone after server restart).", total_removed)
success = true
end
return success, msg
end
})
end
--- @section end
--- Manages settings for wielded [cleaner tool](tools.html).
--
-- <h3>Required Privileges:</h3>
--
-- - server
--
-- @chatcmd ctool
-- @param action Action to execute. Can be "status", "setmode", or "setnode".
-- @param value Mode or node to be set for tool (not required for "status" action).
-- @usage
-- # while cleaner:pencil is wielded, configure to place default:dirt node when used
-- /ctool setmode write
-- /ctool setnode default:dirt
core.register_chatcommand(cmd_repo.tool.cmd, {
privs = {server=true},
description = S("Manage settings for wielded cleaner tool.") .. "\n\n"
.. format_params(cmd_repo.tool.cmd),
params = cmd_repo.tool.help.param_string,
func = function(name, param)
local action, value = param
local idx = param:find(" ")
if idx then
param = string.split(param, " ")
action = param[1]
value = param[2]
end
local help = format_help(cmd_repo.tool.cmd)
local player = core.get_player_by_name(name)
local stack = player:get_wielded_item()
local iname = aux.tool:format_name(stack)
local imeta = stack:get_meta()
if iname ~= "cleaner:pencil" then
return false, S("Unrecognized wielded item: @1", iname) .. "\n\n" .. help
end
if action == "status" then
core.chat_send_player(name, iname .. ": " .. S("mode") .. "=" .. imeta:get_string("mode")
.. ", " .. S("node") .. "=" .. imeta:get_string("node"))
return true
end
if not action or not value then
return false, S("Missing parameter.") .. "\n\n" .. help
end
if action == "setmode" then
stack = aux.tool:set_mode(stack, value, name)
elseif action == "setnode" then
stack = aux.tool:set_node(stack, value, name)
else
return false, S("Unrecognized action: @1", action) .. "\n\n" .. help
end
return player:set_wielded_item(stack)
end,
})

View file

@ -1 +0,0 @@
A simple mod that can be used to remove unknown entities & nodes.

59
entities.lua Normal file
View file

@ -0,0 +1,59 @@
local aux = dofile(cleaner.modpath .. "/misc_functions.lua")
-- populate entities list from file in world path
local entities_data = aux.get_world_data().entities
-- START: backward compat
local e_path = core.get_worldpath() .. "/clean_entities.json"
local e_file = io.open(e_path, "r")
if e_file then
cleaner.log("action", "found deprecated clean_entities.json, updating")
local data_in = core.parse_json(e_file:read("*a"))
e_file:close()
if data_in and data_in.remove then
for _, r in ipairs(data_in.remove) do
table.insert(entities_data.remove, r)
end
end
-- don't read deprecated file again
os.rename(e_path, e_path .. ".old")
end
local e_path_old = core.get_worldpath() .. "/clean_entities.txt"
e_file = io.open(e_path_old, "r")
if e_file then
cleaner.log("action", "found deprecated clean_entities.txt, converting to json")
local data_in = string.split(e_file:read("*a"), "\n")
for _, e in ipairs(data_in) do
e = e:trim()
if e ~= "" and e:sub(1, 1) ~= "#" then
table.insert(entities_data.remove, e)
end
end
e_file:close()
os.rename(e_path_old, e_path_old .. ".bak") -- don't read deprecated file again
end
-- END: backward compat
entities_data.remove = aux.clean_duplicates(entities_data.remove)
-- update json file with any changes
aux.update_world_data("entities", entities_data)
core.register_on_mods_loaded(function()
for _, e in ipairs(entities_data.remove) do
cleaner.log("action", "registering entity for removal: " .. e)
cleaner.register_entity_removal(e)
end
end)

124
init.lua
View file

@ -1,104 +1,48 @@
--[[ Cleaner mod
License: CC0
]]
cleaner = {}
cleaner.name = core.get_current_modname()
cleaner.modname = core.get_current_modname()
cleaner.modpath = core.get_modpath(cleaner.modname)
local debug = core.settings:get_bool('enable_debug_mods')
local cleaner_debug = core.settings:get_bool("enable_debug_mods", false)
local function log(level, msg)
core.log(level, '[' .. cleaner.name .. '] ' .. msg)
function cleaner.log(lvl, msg)
if lvl == "debug" and not cleaner_debug then return end
if lvl and not msg then
msg = lvl
lvl = nil
end
local function logDebug(msg)
if debug then
core.log('DEBUG: [' .. cleaner.name .. '] ' .. msg)
end
msg = "[" .. cleaner.modname .. "] " .. msg
if lvl == "debug" then
msg = "[DEBUG] " .. msg
lvl = nil
end
-- ENTITIES
local old_entities = {}
-- Populate entities list from file in world path
local e_list = nil
local e_path = core.get_worldpath() .. '/clean_entities.txt'
local e_file = io.open(e_path, 'r')
if e_file then
e_list = e_file:read('*a')
e_file:close()
if not lvl then
core.log(msg)
else
-- Create empty file
e_file = io.open(e_path, 'w')
if e_file then
e_file:close()
core.log(lvl, msg)
end
end
if e_list then
logDebug('Loading entities to clean from file ...')
local aux = dofile(cleaner.modpath .. "/misc_functions.lua")
e_list = string.split(e_list, '\n')
for _, entity_name in ipairs(e_list) do
table.insert(old_entities, entity_name)
-- initialize world file
aux.update_world_data()
local scripts = {
"settings",
"api",
"chat",
"tools",
"entities",
"nodes",
"items",
"ores",
}
for _, script in ipairs(scripts) do
dofile(cleaner.modpath .. "/" .. script .. ".lua")
end
end
for _, entity_name in ipairs(old_entities) do
logDebug('Cleaning entity: ' .. entity_name)
core.register_entity(':' .. entity_name, {
on_activate = function(self, staticdata)
self.object:remove()
end,
})
end
-- NODES
local old_nodes = {}
-- Populate nodes list from file in world path
local n_list = nil
local n_path = core.get_worldpath() .. '/clean_nodes.txt'
local n_file = io.open(n_path, 'r')
if n_file then
n_list = n_file:read('*a')
n_file:close()
else
-- Create empty file
n_file = io.open(n_path, 'w')
if n_file then
n_file:close()
end
end
if n_list then
logDebug('Loading nodes to clean from file ...')
n_list = string.split(n_list, '\n')
for _, node_name in ipairs(n_list) do
table.insert(old_nodes, node_name)
end
end
for _, node_name in ipairs(old_nodes) do
logDebug('Cleaning node: ' .. node_name)
core.register_node(':' .. node_name, {
groups = {old=1},
})
end
core.register_abm({
nodenames = {'group:old'},
interval = 1,
chance = 1,
action = function(pos, node)
core.remove_node(pos)
end,
})

49
items.lua Normal file
View file

@ -0,0 +1,49 @@
local aux = dofile(cleaner.modpath .. "/misc_functions.lua")
-- populate items list from file in world path
local items_data = aux.get_world_data().items
-- START: backward compat
local i_path = core.get_worldpath() .. "/clean_items.json"
local i_file = io.open(i_path, "r")
if i_file then
cleaner.log("action", "found deprecated clean_items.json, updating")
local data_in = core.parse_json(i_file:read("*a"))
i_file:close()
if data_in and data_in.replace then
for k, v in pairs(data_in.replace) do
if not items_data.replace[k] then
items_data.replace[k] = v
end
end
end
-- don't read deprecated file again
os.rename(i_path, i_path .. ".old")
end
-- END: backward compat
aux.update_world_data("items", items_data)
for i_old, i_new in pairs(items_data.replace) do
cleaner.register_item_replacement(i_old, i_new)
end
-- register actions for after server startup
core.register_on_mods_loaded(function()
for i_old, i_new in pairs(cleaner.get_replace_items()) do
cleaner.log("action", "registering item \"" .. i_old .. "\" to be replaced with \"" .. i_new .. "\"")
local retval, msg = cleaner.replace_item(i_old, i_new)
if not retval then
cleaner.log("warning", msg)
end
end
end)

66
locale/cleaner.es.tr Normal file
View file

@ -0,0 +1,66 @@
# textdomain:cleaner
# Translators: Jordan Irwin (AntumDeluge)
# chat commands
entity=entidad
mode=modo
node=nodo
radius=radio
old_item=objeto_antiguo
new_item=objeto_nuevo
old_node=nodo_antiguo
new_node=nodo_nuevo
ore=mineral
action=acción
value=valor
Usage:=Uso:
Params:=Parámetros:
default: @1=por defecto: @1
Search radius.=Radio de búsqueda.
Entity technical name.=Nombre técnico de entidad.
Node technical name.=Nombre técnico de nodo.
Technical name of node to be replaced.=Nombre técnico del nodo reemplazado.
Technical name of node to be used in place.=Nombre técnico del nodo de reemplazo.
Technical name of item to be replaced.=Nombre técnico del objeto reemplazado.
Technical name of item to be used in place.=Nombre técnico del objeto de reemplazo.
Ore technical name.=Nombre técnico de mineral.
Action to execute. Can be one of "@1", "@2", or "@3".=La acción para ejecutar. Puede ser "@1", "@2", o "@3".
Mode or node to be set for tool (not required for "@1" action).=Modo o nodo para configurar a la herramienta (no se requiere para la acción de "@1").
Remove an entity from game.=Eliminar una entidad del juego.
Remove a node from game.=Eliminar un nodo del juego.
Replace an item in game.=Sustituir un objecto del juego.
Replace a node in game.=Sustituir un nodo del juego.
Find names of unknown nodes.=Descubrir los nombres de nodos desconocidos.
Find names of nearby nodes.=Descubrir los nombres de nodos cercanos.
Remove an ore from game.=Eliminar un mineral del juego.
Missing parameter.=Parámetro extraviado.
Too many parameters.=Demasiados parámetros.
Radius must be a number.=El radio debe ser un número.
Cannot use unknown item "@1" as replacement.=El objeto "@1" es desonocido, no se puede utilizar como sustitución.
Cannot use unknown node "@1" as replacement.=El nodo "@1" es desonocido, no se puede utilizar como sustitución.
Replaced @1 nodes.=Nodos sustituidos: @1
Removed @1 nodes.=Se eliminaron @1 nodos.
Removed @1 entities.=Se eliminaron @1 entidades.
Found unknown nodes: @1=Se encontraron @1 nodos desconocidos.
No unknown nodes found.=No se encontraron nodos desconocidos.
Nearby nodes: @1=Nodos cercanos: @1
No nearby nodes found.=No se encontraron nodos cercanos.
Ore "@1" not found, not unregistering.=No se encontró el mineral "@1", se mantiene registrado.
Unregistered @1 ores (this will be undone after server restart).=Se anuló @1 minerales del registro.
Success!=¡Éxito!
Manage settings for wielded cleaner tool.=Administrar a los ajustes de la herramienta cleaner empuñada.
Unrecognized wielded item: @1=Objeto empuñado desconocido: @1
Unrecognized action: @1=Acción desconocido: @1
You do not have permission to set radius that high. Reduced to @1.=No tienes permiso para poner al radio tan alto. Se reduce a @1.
Radius is too high. Reduced to @1.=El radio es demasiado alto. Se reduce a @1.
# tools:
@1: mode set to: @2=@1: modo configurado para: @2
@1: node set to: @2=@1: nodo configurado para: @2
Modes for tool "@1" not available.=Modos para herramienta "@1" no disponibles.
You do not have permission to use this item. Missing privs: @1=No tienes permiso para usar este objeto. Privs que faltan: @1
Unknown mode: @1=Modo desconocido: @1
Can't place node there.=No se puede poner nodo allí.
Cannot place unknown node: @1=No se puede poner nodo desconocido: @1

66
locale/template.txt Normal file
View file

@ -0,0 +1,66 @@
# textdomain:cleaner
# Translators:
# chat commands
entity=
mode=
node=
radius=
old_item=
new_item=
old_node=
new_node=
ore=
action=
value=
Usage:=
Params:=
default: @1=
Search radius.=
Entity technical name.=
Node technical name.=
Technical name of node to be replaced.=
Technical name of node to be used in place.=
Technical name of item to be replaced.=
Technical name of item to be used in place.=
Ore technical name.=
Action to execute. Can be one of "@1", "@2", or "@3".=
Mode or node to be set for tool (not required for "@1" action).=
Remove an entity from game.=
Remove a node from game.=
Replace an item in game.=
Replace a node in game.=
Find names of unknown nodes.=
Find names of nearby nodes.=
Remove an ore from game.=
Missing parameter.=
Too many parameters.=
Radius must be a number.=
Cannot use unknown item "@1" as replacement.=
Cannot use unknown node "@1" as replacement.=
Replaced @1 nodes.=
Removed @1 nodes.=
Removed @1 entities.=
Found unknown nodes: @1=
No unknown nodes found.=
Nearby nodes: @1=
No nearby nodes found.=
Ore "@1" not found, not unregistering.=
Unregistered @1 ores (this will be undone after server restart).=
Success!=
Manage settings for wielded cleaner tool.=
Unrecognized wielded item: @1=
Unrecognized action: @1=
You do not have permission to set radius that high. Reduced to @1.=
Radius is too high. Reduced to @1.=
# tools:
@1: mode set to: @2=
Modes for tool "@1" not available.=
@1: node set to: @2=
You do not have permission to use this item. Missing privs: @1=
Can't place node there.=
Unknown mode: @1=
Cannot place unknown node: @1=

258
misc_functions.lua Normal file
View file

@ -0,0 +1,258 @@
local S = core.get_translator(cleaner.modname)
--- Cleans duplicate entries from indexed table.
--
-- @local
-- @function clean_duplicates
-- @tparam table t
-- @treturn table
local function clean_duplicates(t)
local tmp = {}
for _, v in ipairs(t) do
tmp[v] = true
end
t = {}
for k in pairs(tmp) do
table.insert(t, k)
end
return t
end
local world_file = core.get_worldpath() .. "/cleaner.json"
local function get_world_data()
local wdata = {}
local buffer = io.open(world_file, "r")
if buffer then
local err
wdata, err = core.parse_json(buffer:read("*a"), nil, true)
buffer:close()
if wdata == nil then
cleaner.log("warning", "reading world data file failed: " .. world_file)
wdata = {}
end
end
local rem_types = {"entities", "nodes", "ores",}
local rep_types = {"items", "nodes",}
for _, t in ipairs(rem_types) do
wdata[t] = wdata[t] or {}
wdata[t].remove = wdata[t].remove or {}
end
for _, t in ipairs(rep_types) do
wdata[t] = wdata[t] or {}
wdata[t].replace = wdata[t].replace or {}
end
return wdata
end
local function update_world_data(t, data)
local wdata = get_world_data()
if t and data then
wdata[t].remove = data.remove
wdata[t].replace = data.replace
end
local json_string = core.write_json(wdata, true):gsub("\"remove\" : null", "\"remove\" : []")
:gsub("\"replace\" : null", "\"replace\" : {}")
local buffer = io.open(world_file, "w")
if buffer then
buffer:write(json_string)
buffer:close()
return true
end
return false
end
local tool = {
modes = {
["cleaner:pencil"] = {"erase", "write", "swap"},
},
format_name = function(self, stack)
local iname = stack:get_name()
if iname == "cleaner:pencil_1" then
iname = "cleaner:pencil"
end
return iname
end,
set_mode = function(self, stack, mode, pname)
local iname = self:format_name(stack)
if not self.modes[iname] then
if pname then
core.chat_send_player(pname, iname .. ": " .. S("unknown mode: @1", mode))
end
cleaner.log("warning", iname .. ": unknown mode: " .. mode)
return stack
end
local imeta = stack:get_meta()
imeta:set_string("mode", mode)
if pname then
core.chat_send_player(pname, S("@1: mode set to: @2", iname, imeta:get_string("mode")))
end
local new_stack
if mode == "erase" then
new_stack = ItemStack("cleaner:pencil_1")
else
new_stack = ItemStack("cleaner:pencil")
end
local new_meta = new_stack:get_meta()
new_meta:from_table(imeta:to_table())
return new_stack
end,
next_mode = function(self, stack, pname)
local iname = self:format_name(stack)
local modes = self.modes[iname]
if not modes then
return false, stack, S('Modes for tool "@1" not available.', stack:get_name())
end
local imeta = stack:get_meta()
local current_mode = imeta:get_string("mode")
if not current_mode or current_mode:trim() == "" then
return true, self:set_mode(stack, modes[1], pname)
end
local idx = 1
for _, m in ipairs(modes) do
if current_mode == m then
break
end
idx = idx + 1
end
return true, self:set_mode(stack, modes[idx+1] or modes[1], pname)
end,
set_node = function(self, stack, node, pname)
local imeta = stack:get_meta()
imeta:set_string("node", node)
if pname then
core.chat_send_player(pname, S("@1: node set to: @2", stack:get_name(), imeta:get_string("node")))
end
return stack
end,
}
local use_sounds = core.global_exists("sounds")
local sound_handle
tool.on_use = function(stack, user, pointed_thing)
if not user:is_player() then return end
local pname = user:get_player_name()
if not core.get_player_privs(pname).server then
core.chat_send_player(pname, S("You do not have permission to use this item. Missing privs: @1", "server"))
return stack
end
if sound_handle then
core.sound_stop(sound_handle)
sound_handle = nil
end
if pointed_thing.type == "node" then
local npos = core.get_pointed_thing_position(pointed_thing)
local imeta = stack:get_meta()
local mode = imeta:get_string("mode")
local new_node_name = imeta:get_string("node")
if mode == "erase" then
core.remove_node(npos)
if use_sounds then
local sound_handle = sounds.pencil_erase({object=user})
end
return stack
elseif core.registered_nodes[new_node_name] then
if mode == "swap" then
core.swap_node(npos, {name=new_node_name})
if use_sounds then
local sound_handle = sounds.pencil_write({object=user})
end
elseif mode == "write" then
local node_above = core.get_node_or_nil(pointed_thing.above)
if not node_above or node_above.name == "air" then
core.set_node(pointed_thing.above, {name=new_node_name})
if use_sounds then
local sound_handle = sounds.pencil_write({object=user})
end
else
core.chat_send_player(pname, S("Can't place node there."))
end
else
core.chat_send_player(pname, S("Unknown mode: @1", mode))
end
return stack
end
core.chat_send_player(pname, S("Cannot place unknown node: @1", new_node_name))
return stack
end
end
tool.on_secondary_use = function(stack, user, pointed_thing)
if not user:is_player() then return end
local pname = user:get_player_name()
if not core.get_player_privs(pname).server then
core.chat_send_player(pname, S("You do not have permission to use this item. Missing privs: @1", "server"))
return stack
end
local success, stack, msg = tool.next_mode(tool, stack, pname)
if not success then
core.chat_send_player(pname, msg)
end
return stack
end
tool.on_place = function(stack, placer, pointed_thing)
if not placer:is_player() then return end
local pname = placer:get_player_name()
if not core.get_player_privs(pname).server then
core.chat_send_player(pname, S("You do not have permission to use this item. Missing privs: @1", "server"))
return stack
end
if pointed_thing.type == "node" then
local node = core.get_node_or_nil(core.get_pointed_thing_position(pointed_thing))
if node then
stack = tool:set_node(stack, node.name, pname)
end
end
return stack
end
return {
clean_duplicates = clean_duplicates,
get_world_data = get_world_data,
update_world_data = update_world_data,
tool = tool,
}

View file

@ -1,4 +1,7 @@
name = cleaner
description = A mod that can be used to remove/replace unknown entities, nodes, & items.
version = 2025-01-18
license = MIT
author = PilzAdam, Jordan Irwin (AntumDeluge)
license = CC0
version = 0.4
min_minetest_version = 5.0
optional_depends = sounds

97
nodes.lua Normal file
View file

@ -0,0 +1,97 @@
local aux = dofile(cleaner.modpath .. "/misc_functions.lua")
-- populate nodes list from file in world path
local nodes_data = aux.get_world_data().nodes
-- START: backward compat
local n_path = core.get_worldpath() .. "/clean_nodes.json"
local n_file = io.open(n_path, "r")
if n_file then
cleaner.log("action", "found deprecated clean_nodes.json, updating")
local data_in = core.parse_json(n_file:read("*a"))
n_file:close()
if data_in then
if data_in.remove then
for _, r in ipairs(data_in.remove) do
table.insert(nodes_data.remove, r)
end
end
if data_in.replace then
for k, v in pairs(data_in.replace) do
if not nodes_data.replace[k] then
nodes_data.replace[k] = v
end
end
end
end
-- don't read deprecated file again
os.rename(n_path, n_path .. ".old")
end
local n_path_old = core.get_worldpath() .. "/clean_nodes.txt"
n_file = io.open(n_path_old, "r")
if n_file then
cleaner.log("action", "found deprecated clean_nodes.txt, converting to json")
local data_in = string.split(n_file:read("*a"), "\n")
for _, e in ipairs(data_in) do
e = e:trim()
if e ~= "" and e:sub(1, 1) ~= "#" then
table.insert(nodes_data.remove, e)
end
end
n_file:close()
os.rename(n_path_old, n_path_old .. ".old") -- don't read deprecated file again
end
-- END: backward compat
nodes_data.remove = aux.clean_duplicates(nodes_data.remove)
-- update json file with any changes
aux.update_world_data("nodes", nodes_data)
core.register_lbm({
name = "cleaner:remove_nodes",
nodenames = {"group:to_remove"},
run_at_every_load = true,
action = function(pos, node)
core.remove_node(pos)
end,
})
core.register_lbm({
name = "cleaner:replace_nodes",
nodenames = {"group:to_replace"},
run_at_every_load = true,
action = function(pos, node)
local new_node_name = cleaner.get_replace_nodes()[node.name]
if core.registered_nodes[new_node_name] then
core.swap_node(pos, {name=new_node_name})
else
cleaner.log("error", "cannot replace with unregistered node \"" .. tostring(new_node_name) .. "\"")
end
end,
})
core.register_on_mods_loaded(function()
for _, n in ipairs(nodes_data.remove) do
cleaner.log("action", "registering node for removal: " .. n)
cleaner.register_node_removal(n)
end
for n_old, n_new in pairs(nodes_data.replace) do
cleaner.log("action", "registering node \"" .. n_old .. "\" to be replaced with \"" .. n_new .. "\"")
cleaner.register_node_replacement(n_old, n_new)
end
end)

17
ores.lua Normal file
View file

@ -0,0 +1,17 @@
if not cleaner.unsafe then return end
local aux = dofile(cleaner.modpath .. "/misc_functions.lua")
local ores_data = aux.get_world_data().ores
for _, ore in ipairs(ores_data.remove) do
cleaner.register_ore_removal(ore)
end
core.register_on_mods_loaded(function()
for _, ore in ipairs(cleaner.get_remove_ores()) do
cleaner.log("action", "unregistering ore: " .. ore)
cleaner.remove_ore(ore)
end
end)

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

70
set_version.py Executable file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env python
import sys, os, codecs
f_script = os.path.realpath(__file__)
d_root = os.path.dirname(f_script)
os.chdir(d_root)
args = sys.argv[1:]
if len(args) < 1:
print("ERROR: must supply version as parameter")
sys.exit(1)
new_version = args[0]
to_update = {
"mod.conf": "version =",
"changelog.txt": "next",
os.path.normpath(".ldoc/config.ld"): "local version =",
}
for f in to_update:
f_path = os.path.join(d_root, f)
if not os.path.isfile(f_path):
print("WARNING: {} not found, skipping ...".format(f))
continue
print("\nsetting version to {} in {}".format(new_version, f_path))
buffer = codecs.open(f_path, "r", "utf-8")
if not buffer:
print("WARNING: could not open {} for reading, skipping ...".format(f))
continue
read_in = buffer.read()
buffer.close()
read_in = read_in.replace("\r\n", "\n").replace("\r", "\n")
replacement = to_update[f]
new_lines = []
version_set = False
for li in read_in.split("\n"):
if not version_set:
if "=" in replacement and li.startswith(replacement):
key = li.split(" = ")[0]
li = "{} = {}".format(key, new_version)
version_set = True
elif li == replacement:
li = "v{}".format(new_version)
version_set = True
new_lines.append(li)
write_out = "\n".join(new_lines)
if write_out == read_in:
print("no changes for {}, skipping ...".format(f))
continue
buffer = codecs.open(f_path, "w", "utf-8")
if not buffer:
print("WARNING: could not open {} for writing, skipping ...".format(f))
continue
buffer.write("\n".join(new_lines))
buffer.close()
print("done")

15
settings.lua Normal file
View file

@ -0,0 +1,15 @@
--- Cleaner Settings
--
-- @topic settings
--- Enables unsafe methods & chat commands.
--
-- - `cleaner.remove_ore`
-- - `/remove_ores`
--
-- @setting cleaner.unsafe
-- @settype bool
-- @default false
cleaner.unsafe = core.settings:get_bool("cleaner.unsafe", false)

6
settingtypes.txt Normal file
View file

@ -0,0 +1,6 @@
# Enables unsafe methods & chat commands.
#
# - cleaner.remove_ore
# - /remove_ores
cleaner.unsafe (Enable unsafe methods) bool false

BIN
textures/cleaner_pencil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

44
tools.lua Normal file
View file

@ -0,0 +1,44 @@
--- Cleaner Tools
--
-- @topic tools
local S = core.get_translator(cleaner.modname)
local aux = dofile(cleaner.modpath .. "/misc_functions.lua")
--- Master Pencil
--
-- @tool cleaner:pencil
-- @img cleaner_pencil.png
-- @priv server
-- @usage
-- place (right-click):
-- - when not pointing at a node, changes modes
-- - when pointing at a node, sets node to be used
--
-- use (left-click):
-- - executes action for current mode:
-- - erase: erases pointed node
-- - write: adds node
-- - swap: replaces pointed node
core.register_tool(cleaner.modname .. ":pencil", {
description = S("Master Pencil"),
inventory_image = "cleaner_pencil.png",
liquids_pointable = true,
on_use = aux.tool.on_use,
on_secondary_use = aux.tool.on_secondary_use,
on_place = aux.tool.on_place,
})
core.register_tool(cleaner.modname .. ":pencil_1", {
description = S("Master Pencil"),
inventory_image = "cleaner_pencil.png^[transformFXFY",
liquids_pointable = true,
groups = {not_in_creative_inventory=1},
on_use = aux.tool.on_use,
on_secondary_use = aux.tool.on_secondary_use,
on_place = aux.tool.on_place,
})