Added /clear

This commit is contained in:
ThePython 2024-05-12 16:09:20 -07:00
parent 429a3cb56f
commit da3fba377a
11 changed files with 254 additions and 44 deletions

View File

@ -497,19 +497,22 @@ end
---@return minetest.ItemStack? result
---@return string? err
---@nodiscard
function better_commands.parse_item(item_data)
function better_commands.parse_item(item_data, ignore_count)
minetest.log(dump(item_data))
if not better_commands.handle_alias(item_data[3]) then
return nil, S("Invalid item: @1", item_data[3])
end
if item_data.type == "item" and not item_data.extra_data then
local stack = ItemStack(item_data[3])
stack:set_count(tonumber(item_data[4]) or 1)
if not ignore_count then
stack:set_count(tonumber(item_data[4]) or 1)
end
stack:set_wear(tonumber(item_data[5]) or 0)
return stack
elseif item_data.type == "item" then
local arg_table = {}
if item_data[4] then
-- basically matching "(thing)=(thing)[,%]]"
-- basically matching (thing)=(thing) followed by , or ]
for key, value in item_data[4]:gmatch("([%w_]+)%s*=%s*([^,%]]+)%s*[,%]]") do
arg_table[key:trim()] = value:trim()
end
@ -518,11 +521,18 @@ function better_commands.parse_item(item_data)
if arg_table then
local meta = stack:get_meta()
for key, value in pairs(arg_table) do
meta:set_string(key, value)
minetest.log(S("@1 = @2", key, value))
if key == "wear" then
stack:set_wear(tonumber(value) or 0)
else
meta:set_string(key, value)
end
end
end
stack:set_count(tonumber(item_data[5]) or 1)
stack:set_wear(tonumber(item_data[6]) or 1)
if not ignore_count then
stack:set_count(tonumber(item_data[5]) or 1)
end
stack:set_wear(tonumber(item_data[6]) or stack:get_wear())
return stack
end
return nil, S("Invalid item: @1", item_data[3])

View File

@ -6,8 +6,4 @@ minetest.register_on_mods_loaded(function()
return true
end
end)
end)
function better_commands.set_spawn(player, pos)
better_commands.spawnpoints[player:get_player_name()] = pos
end
end)

View File

@ -1,22 +1,26 @@
local storage = minetest.get_mod_storage()
function better_commands.load(key)
local value = storage:get_string("teams")
function better_commands.load(key, default)
local value = storage:get_string(key)
if value and value ~= "" then
better_commands[key] = minetest.deserialize(value)
better_commands[key] = minetest.deserialize(value) or default
else
better_commands[key] = {teams = {}, players = {}}
better_commands[key] = default
end
end
better_commands.load("teams")
better_commands.load("scoreboard")
better_commands.load("spawnpoints")
function better_commands.save(key)
storage:set_string(key, minetest.serialize(better_commands[key]))
end
better_commands.load("teams", {teams = {}})
better_commands.load("scoreboard", {objectives = {}, players = {}, displays = {}})
better_commands.load("spawnpoints", {})
better_commands.register_on_update(function ()
storage:set_string("scoreboard", minetest.serialize(better_commands.scoreboard))
storage:set_string("teams", minetest.serialize(better_commands.teams))
storage:set_string("spawnpoints", minetest.serialize(better_commands.spawnpoints))
better_commands.save("scoreboard")
better_commands.save("teams")
better_commands.save("spawnpoints")
end)
minetest.register_on_shutdown(function()

View File

@ -19,9 +19,11 @@ Initial release. Missing *lots* of commands, several `execute` subcommands, lots
* `placed.<itemstring>`
* `crafted.<itemstring>`
* Added `ascending|descending` argument for `/scoreboard objectives setdisplay`
* Added `/gamemode` (grants/revokes `creative` priv in non-MCL games)
* Added `/gamemode` command (grants/revokes `creative` priv in non-MCL games)
* Added `/spawnpoint` and `/clearspawnpoint` commands
* Added `gamemode`/`m` selector argument
* Added `level`/`l`/`lm` selector arguments
* Added `/clear` command
### Bugfixes
* The `/kill` command is more likely to successfully kill entities.
* The `rm`/`r` selector arguments now actually treat their values as numbers (not strings), and are now inclusive as intended.

View File

@ -15,6 +15,8 @@ local command_files = {
"setblock",
"summon",
"gamemode",
"spawnpoint",
"clear",
}
for _, file in ipairs(command_files) do

138
COMMANDS/clear.lua Normal file
View File

@ -0,0 +1,138 @@
local S = minetest.get_translator(minetest.get_current_modname())
better_commands.register_command("clear", {
description = S("Clears player inventories"),
privs = { server = true },
params = S("[targets] [items] [maxCount]"),
func = function(name, param, context)
context = better_commands.complete_context(name, context)
if not context then return false, S("Missing context"), 0 end
local split_param = better_commands.parse_params(param)
local selector = split_param[1]
local targets, err
if not selector then
targets = {context.executor}
else
targets, err = better_commands.parse_selector(selector, context)
if err or not targets then return false, err, 0 end
end
local filter
if split_param[2] and split_param[2][3] == "*" then
filter = "*"
elseif split_param[2] then
filter, err = better_commands.parse_item(split_param[2], true)
if err or not filter then return false, err, 0 end
minetest.log(dump(filter:get_name()))
end
local remove_max = tonumber(split_param[3] and split_param[3][3])
if split_param[3] and not remove_max then
return false, S("maxCount must be a number"), 0
end
if remove_max then
remove_max = math.floor(remove_max)
else
remove_max = -1
end
local all = not filter or filter == "*"
local last
local count = 0
local match_total = 0
minetest.log(dump(split_param))
for _, target in ipairs(targets) do
if target.is_player and target:is_player() then
local found = false
local match_count = 0
local inv = target:get_inventory()
for _, list in ipairs(better_commands.settings.clear_lists) do
if inv:get_list(list) then
if all and remove_max == -1 then
inv:set_list(list, {})
found = true
elseif remove_max == 0 then
for _, stack in ipairs(inv:get_list(list)) do
if all then
found = true
match_count = match_count + stack:get_count()
elseif split_param[2].extra_data then
if stack:peek_item(1):equals(filter) then
found = true
match_count = match_count + stack:get_count()
end
---@diagnostic disable-next-line: param-type-mismatch
elseif stack:get_name() == filter:get_name() then
found = true
match_count = match_count + stack:get_count()
end
end
else
for i, stack in ipairs(inv:get_list(list)) do
local matches = false
if all then
matches = true
elseif split_param[2].extra_data then
if stack:peek_item(1):equals(filter) then
matches = true
end
---@diagnostic disable-next-line: param-type-mismatch
elseif stack:get_name() == filter:get_name() then
matches = true
end
if matches then
found = true
local count = stack:get_count()
local to_remove = count
if remove_max > 0 then
to_remove = math.min(remove_max - match_count, count)
end
if to_remove == count then
inv:set_stack(list, i, ItemStack(""))
match_count = match_count + to_remove
elseif to_remove > 0 then
local result_count = count - to_remove
if result_count > 0 then
stack:set_count(result_count)
inv:set_stack(list, i, stack)
else
inv:set_stack(list, i, ItemStack(""))
end
match_count = match_count + to_remove
end
if match_count >= remove_max and remove_max > 0 then
break
end
end
end
if match_count >= remove_max and remove_max > 0 then
break
end
end
match_total = match_total + match_count
end
end
if found then
count = count + 1
last = better_commands.get_entity_name(target)
end
end
end
if count < 1 then
return false, S("No matching players/items")
elseif count == 1 then
if remove_max == 0 then
return true, S("@1 has @2 matching items", last, match_total), match_total
elseif all and remove_max == -1 then
return true, S("Removed all items from @1", last), 1
else
return true, S("Removed @1 items from @2", match_total, last), 1
end
else
if remove_max == 0 then
return true, S("@1 matching items found in @2 players' inventories", match_total, count), match_total
elseif all and remove_max == -1 then
return true, S("Removed all items from @1 players", count), 1
else
return true, S("Removed @1 items from @2 players", match_total, count), count
end
end
end
})

View File

@ -134,9 +134,9 @@ better_commands.register_command("scoreboard", {
display = better_commands.scoreboard.displays.sidebar
sortable = true
else
local color = location:match("^sidebar%.(.+)")
local color = location:match("^sidebar%.team.(.+)")
if not color then
return false, S("Must be 'list', 'below_name', 'sidebar', or 'sidebar.<color>"), 0
return false, S("Must be 'list', 'below_name', 'sidebar', or 'sidebar.team.<color>"), 0
elseif better_commands.team_colors[color] then
display = better_commands.scoreboard.displays.colors[color]
better_commands.scoreboard.displays.colors[color] = {objective = objective}
@ -172,6 +172,7 @@ better_commands.register_command("scoreboard", {
end
local score = tonumber(split_param[5] and split_param[5][3])
if not score then return false, S("Missing score"), 0 end
score = math.floor(score)
local names, err = better_commands.get_scoreboard_names(selector, context, objective)
if err or not names then return false, err, 0 end
local last
@ -416,20 +417,28 @@ better_commands.register_command("scoreboard", {
source_scores[source] = {score = 0}
end
if operator == "+=" then
target_scores[target].score = target_scores[target].score + source_scores[source].score
target_scores[target].score = math.floor(target_scores[target].score + source_scores[source].score)
op_string, preposition = "Added", "to"
elseif operator == "-=" then
target_scores[target].score = target_scores[target].score - source_scores[source].score
target_scores[target].score = math.floor(target_scores[target].score - source_scores[source].score)
op_string, preposition = "Subtracted", "from"
elseif operator == "*=" then
target_scores[target].score = target_scores[target].score * source_scores[source].score
target_scores[target].score = math.floor(target_scores[target].score * source_scores[source].score)
op_string, preposition, swap = "Multiplied", "by", true
elseif operator == "/=" then
target_scores[target].score = target_scores[target].score / source_scores[source].score
op_string, preposition, swap = "Divided", "by", true
if source_scores[source].score == 0 then
minetest.chat_send_player(name, S("Skipping attempt to divide by zero"))
else
target_scores[target].score = math.floor(target_scores[target].score / source_scores[source].score)
op_string, preposition, swap = "Divided", "by", true
end
elseif operator == "%=" then
target_scores[target].score = target_scores[target].score % source_scores[source].score
op_string, preposition, swap = "Modulo-ed (?)", "and", true
if source_scores[source].score == 0 then
minetest.chat_send_player(name, S("Skipping attempt to divide by zero"))
else
target_scores[target].score = math.floor(target_scores[target].score % source_scores[source].score)
op_string, preposition, swap = "Modulo-ed (?)", "and", true
end
elseif operator == "=" then
target_scores[target].score = source_scores[source].score
op_string, preposition, swap = "Set", "to", true
@ -605,13 +614,14 @@ better_commands.register_command("trigger", {
else
local value = split_param[3] and split_param[3][3]
if not value then return false, S("Missing value"), 0 end
if not tonumber(value) then return false, S("Value must be a number"), 0 end
value = tonumber(value)
if not value then return false, S("Value must be a number"), 0 end
if subcommand == "add" then
scores.score = scores.score + tonumber(value)
scores.score = scores.score + math.floor(value)
scores.enabled = false
return true, S("Triggered [@1] (added @2 to value)", display_name, value), scores.score
elseif subcommand == "set" then
scores.score = tonumber(value)
scores.score = math.floor(value)
scores.enabled = false
return true, S("Triggered [@1] (set value to @2)", display_name, value), scores.score
else

View File

@ -3,7 +3,7 @@ local S = minetest.get_translator(minetest.get_current_modname())
better_commands.register_command("spawnpoint", {
description = S("Sets players' spawnpoints"),
privs = {server = true},
params = "[targets]",
params = S("[targets]"),
func = function (name, param, context)
context = better_commands.complete_context(name, context)
if not context then return false, S("Missing context"), 0 end
@ -13,7 +13,7 @@ better_commands.register_command("spawnpoint", {
local selector = split_param[1]
if not selector then
if context.executor.is_player and context.executor:is_player() then
better_commands.spawnpoints[context.executor:get_player_name()] = context.executor:get_pos()
better_commands.spawnpoints[context.executor:get_player_name()] = context.pos
return true, S("Spawn point set"), 1
else
return false, S("Non-player entities are not supported by this command")
@ -25,7 +25,7 @@ better_commands.register_command("spawnpoint", {
local count = 0
for _, target in ipairs(targets) do
if target.is_player and target:is_player() then
better_commands.spawnpoints[target:get_player_name()] = target:get_pos()
better_commands.spawnpoints[target:get_player_name()] = context.pos
count = count + 1
last = better_commands.get_entity_name(target)
end
@ -33,9 +33,50 @@ better_commands.register_command("spawnpoint", {
if count < 1 then
return false, S("No matching players found."), 0
elseif count == 1 then
return true, S("Set spawn point for @1", last), 1
return true, S("Set spawn point to @1 for @2", minetest.pos_to_string(context.pos), last), 1
else
return true, S("Set spawn point for @1 players", count), count
return true, S("Set spawn point to @1 for @2 players", minetest.pos_to_string(context.pos), count), count
end
end
end
})
better_commands.register_command("clearspawnpoint", {
description = S("Clear players' spawnpoints"),
privs = {server = true},
params = "[targets]",
func = function (name, param, context)
context = better_commands.complete_context(name, context)
if not context then return false, S("Missing context"), 0 end
if not context.executor then return false, S("Missing executor"), 0 end
local split_param, err = better_commands.parse_params(param)
if err then return false, err, 0 end
local selector = split_param[1]
if not selector then
if context.executor.is_player and context.executor:is_player() then
better_commands.spawnpoints[context.executor:get_player_name()] = nil
return true, S("Spawn point cleared"), 1
else
return false, S("Non-player entities are not supported by this command")
end
else
local targets, err = better_commands.parse_selector(selector, context)
if err or not targets then return false, err, 0 end
local last
local count = 0
for _, target in ipairs(targets) do
if target.is_player and target:is_player() then
better_commands.spawnpoints[target:get_player_name()] = nil
count = count + 1
last = better_commands.get_entity_name(target)
end
end
if count < 1 then
return false, S("No matching players found."), 0
elseif count == 1 then
return true, S("Cleared spawn point for @2", last), 1
else
return true, S("Set spawn point for @2 players", count), count
end
end
end

View File

@ -1,5 +1,5 @@
# TODO
A place for me to write out my future plans. Also, I can just copy/paste into a random generator and decide what to do next.
A place for me to write out my future plans. I decide what to do next [randomly](https://wheelofnames.com/24r-ygp) (yes, this is genuinely how I do it).
- [ ] Add scoreboard playerlist and nametags (?)
- [ ] Figure out feet/eyes since most entities don't have that
@ -52,9 +52,9 @@ A place for me to write out my future plans. Also, I can just copy/paste into a
- [ ] `advancement`
- [ ] `fill` (Extra argument for LBM vs `set_node(s)`)
- [ ] `changesetting`?
- [ ] `clear`
- [ ] `spawnpoint`
- [ ] `clearspawnpoint`
- [x] `clear`
- [x] `spawnpoint`
- [x] `clearspawnpoint`
- [ ] `clone`
- [ ] `damage`
- [ ] `data`

View File

@ -12,6 +12,9 @@ local function get_setting(setting, default, type)
better_commands.settings[setting] = minetest.settings:get_bool(long_setting, default)
elseif type == "number" then
better_commands.settings[setting] = tonumber(minetest.settings:get(long_setting)) or default
elseif type == "comma_separated" then
local value = minetest.settings:get(long_setting)
better_commands.settings[setting] = value and value:split(",") or default
end
end
@ -20,6 +23,7 @@ local settings = {
{"acovg_time", false, "bool"},
{"save_interval", 3, "number"},
{"kill_creative_players", false, "bool"},
{"clear_lists", {"main", "craft", "offhand"}, "comma_separated"},
{"scoreboard_picked_up", true, "bool"},
{"scoreboard_mined", true, "bool"},

View File

@ -23,4 +23,7 @@ better_commands_scoreboard_placed (Update "placed" objectives?) bool true
better_commands_scoreboard_health (Update "health" objectives?) bool true
# Check player deaths for scoreboard objectives? If false, "deathCount", "teamKill", "playerKillCount", and "killedByTeam" scoreboard objectives will not update.
better_commands_scoreboard_death (Update kill/death objectives?) bool true
better_commands_scoreboard_death (Update kill/death objectives?) bool true
# Comma-separated value of lists (or *) to clear with the /clear command.
better_commands_clear_lists (Lists to /clear) string main,craft,offhand