ThePython10110 c7d0ce2c6c v1.2
2024-05-31 09:40:27 -07:00

340 lines
14 KiB
Lua

local S = minetest.get_translator(minetest.get_current_modname())
better_command_blocks = {}
local command_blocks = {
{"impulse", S("Command Block")},
{"repeating", S("Repeating @1", S("Command Block"))},
{"chain", S("Chain @1", S("Command Block"))},
}
local anim = {type = "vertical_frames"}
local mesecons_rules = {
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
{x = 0, y = 1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 0, z = 0},
}
local already_run = {}
---Gets a metadata string or a fallback falue
---@param meta minetest.MetaDataRef
---@param key string
---@param fallback any
---@return any
local function get_string_or(meta, key, fallback)
local result = meta:get_string(key)
return result == "" and fallback or result
end
local types = {
{"Impulse", false},
{"Repeating", false},
{"Chain", false},
{"Impulse", true}, -- true = conditional
{"Repeating", true},
{"Chain", true},
}
---Opens command block formspec
---@param pos vector.Vector
---@param node minetest.Node
---@param player minetest.Player
local function on_rightclick(pos, node, player)
if not minetest.check_player_privs(player, "better_command_blocks") then return end
local meta = minetest.get_meta(pos)
local command = meta:get_string("_command")
local group = minetest.get_item_group(node.name, "command_block")
if not types[group] then return end
local power = meta:get_string("_power") == "false" and "Always Active" or "Needs Power"
local message = meta:get_string("_message")
local delay = get_string_or(meta, "_delay", (group == 2 or group == 5) and "1" or "0")
local formspec = table.concat({
"formspec_version[4]",
"size[10,6]",
"label[0.5,0.5;",ItemStack(node.name):get_short_description(),"]",
"field[6.5,0.5;2,1;delay;Delay (seconds);",delay,"]",
"field_close_on_enter[delay;false]",
"button[8.5,0.5;1,1;set_delay;Set]",
"field[0.5,2;8,1;command;Command;",minetest.formspec_escape(command),"]",
"field_close_on_enter[command;false]",
"button[8.5,2;1,1;set_command;Set]",
"button[0.5,3.5;3,1;type;",types[group][1],"]",
"button[3.5,3.5;3,1;conditional;",types[group][2] and "Conditional" or "Unconditional","]",
"button[6.5,3.5;3,1;power;",power,"]",
"textarea[0.5,5;9,1;;Previous output;",minetest.formspec_escape(message),"]",
})
local player_name = player:get_player_name()
minetest.show_formspec(player_name, "better_command_blocks:"..minetest.pos_to_string(pos), formspec)
end
local command_block_itemstrings = {}
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not minetest.check_player_privs(player, "better_command_blocks") then return end
-- better_command_blocks:(x,y,z)
local pos = minetest.string_to_pos(formname:match("^better_command_blocks:(%(%-?[%d%.]+,%-?[%d%.]+,%-?[%d%.]+%))$"))
if not pos then return end
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
local group = minetest.get_item_group(node.name, "command_block")
if group < 1 then return end
local show_formspec
if fields.command then
meta:set_string("_command", fields.command)
end
local delay = tonumber(fields.delay)
if delay and delay >= 0 then
meta:set_string("_delay", fields.delay)
end
if fields.key_enter_field == "command" or fields.set_command then
show_formspec = true
elseif fields.key_enter_field == "delay" or fields.set_delay then
show_formspec = true
elseif fields.type then
local new_group = group + 1
if new_group == 4 or new_group == 7 then new_group = new_group - 3 end
local new_node = table.copy(node)
new_node.name = command_block_itemstrings[new_group]
minetest.swap_node(pos, new_node)
if new_group == 2 or new_group == 5 then
minetest.get_node_timer(pos):start(1)
else
minetest.get_node_timer(pos):stop()
end
if new_group == 2 or new_group == 5 then -- repeating
if (tonumber(meta:get_string("_delay")) or 1) < 1 then
meta:set_string("_delay", "1")
end
elseif group == 2 or group == 5 then -- previous = repeating
if (tonumber(meta:get_string("_delay")) or 1) == 1 then
meta:set_string("_delay", "0")
end
end
show_formspec = true
elseif fields.conditional then
local new_group = group + 3
if new_group > 6 then new_group = new_group - 6 end
local new_node = table.copy(node)
new_node.name = command_block_itemstrings[new_group]
minetest.swap_node(pos, new_node)
show_formspec = true
elseif fields.power then
local result = fields.power == "Needs Power" and "false" or "true"
meta:set_string("_power", result)
if result ~= "true" then
if group ~= 3 and group ~= 6 then
better_command_blocks.run(pos)
end
end
show_formspec = true
end
if show_formspec then
on_rightclick(pos, minetest.get_node(pos), player)
end
end)
---Checks for chain command blocks in front of the current command block
---@param pos vector.Vector
local function check_for_chain(pos)
local dir = minetest.facedir_to_dir(minetest.get_node(pos).param2)
local next = vector.add(dir, pos)
local next_group = minetest.get_item_group(minetest.get_node(next).name, "command_block")
local next_dir = minetest.facedir_to_dir(minetest.get_node(next).param2)
if next_group == 0 then return end
if dir ~= next_dir then return end
if next_group == 3 or next_group == 6 then -- chain
local pos_string = minetest.pos_to_string(next)
if not already_run[pos_string] then
better_command_blocks.run(next)
end
end
end
local function run_command(pos, meta, cmd_def, name, param, context)
local success, message, count
if cmd_def.real_func then -- Better Commands
success, message, count = cmd_def.real_func(name, param, context)
else -- Normal command
success, message = cmd_def.func(name, param)
end
--[[if success == true then
success = 1
elseif success == false then
success = 0
end]]
meta:set_int("_success", (success == true and 1) or (success or 0))
meta:set_string("_message", message or "")
meta:set_int("_count", count)
if success == 1 and message and message ~= "" then
if minetest.settings:get_bool("better_command_blocks.command_block_output", true)
and minetest.settings:get_bool("better_commands.send_command_feedback", true) then
minetest.chat_send_all(minetest.colorize("#aaaaaa", S(
"[@1: @2]",
S("Command Block"),
minetest.strip_colors(message)
)))
end
end
check_for_chain(pos)
end
---Triggers the command block
---@param pos vector.Vector
function better_command_blocks.run(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
if meta:get_string("_power") ~= "false" then
if meta:get_string("_mesecons_active") ~= "true" then
return
end
end
local group = minetest.get_item_group(node.name, "command_block")
if group > 3 then -- conditional
local dir = minetest.facedir_to_dir(node.param2)
local previous = pos - dir
if minetest.get_meta(previous):get_int("_success") < 1 then
if group == 6 then -- chain
check_for_chain(pos)
end
return
end
end
if group == 3 or group == 6 then -- chain
local pos_string = minetest.pos_to_string(pos)
if already_run[pos_string] then return end
already_run[pos_string] = true
minetest.after(0, function() already_run[pos_string] = nil end)
end
local command = meta:get_string("_command")
if command ~= "" then
local command_type, param = command:match("(%S+)%s+(.*)$")
local def = minetest.registered_chatcommands[command_type]
if def then
local name = meta:get_string("_name")
-- Other mods' commands may require <name> to be a valid player name.
if not (better_commands and better_commands.commands[command_type]) then
name = get_string_or(meta, "_player")
if name == "" then return end
end
local context = {
executor = pos,
pos = pos,
command_block = true,
dir = minetest.facedir_to_dir(minetest.get_node(pos).param2),
}
if better_commands then context = better_commands.complete_context(S("Command Block"), context) end
if group == 2 or group == 5 then -- repeating
run_command(pos, meta, def, name, param, context)
minetest.get_node_timer(pos):start(tonumber(meta:get_string("_delay")) or 1)
else
local delay = tonumber(meta:get_string("_delay")) or 0
if delay > 0 then
minetest.after(delay, function()
run_command(pos, meta, def, name, param, context)
end)
else
run_command(pos, meta, def, name, param, context)
end
end
end
end
end
---Runs when activated by Mesecons
---@param pos vector.Vector
local function mesecons_activate(pos)
local meta = minetest.get_meta(pos)
meta:set_string("_mesecons_active", "true")
if meta:get_string("_power") ~= "false" then
local group = minetest.get_item_group(minetest.get_node(pos).name, "command_block")
if group ~= 3 and group ~= 6 then
better_command_blocks.run(pos)
end
end
end
---Runs when deactivated by Mesecons
---@param pos vector.Vector
local function mesecons_deactivate(pos)
local meta = minetest.get_meta(pos)
meta:set_string("_mesecons_active", "")
if meta:get_string("_power") ~= "false" then
minetest.get_node_timer(pos):stop()
end
end
for i, command_block in pairs(command_blocks) do
local name, desc = unpack(command_block)
local def = {
description = desc,
groups = {cracky = 1, command_block = i, creative_breakable=1, mesecon_effector_off=1, mesecon_effector_on=1},
tiles = {
{name = "better_command_blocks_"..name.."_top.png", animation = anim},
{name = "better_command_blocks_"..name.."_bottom.png", animation = anim},
{name = "better_command_blocks_"..name.."_right.png", animation = anim},
{name = "better_command_blocks_"..name.."_left.png", animation = anim},
{name = "better_command_blocks_"..name.."_front.png", animation = anim},
{name = "better_command_blocks_"..name.."_back.png", animation = anim},
},
paramtype2 = "facedir",
on_rightclick = on_rightclick,
on_timer = better_command_blocks.run,
mesecons = {
effector = {
action_on = mesecons_activate,
action_off = mesecons_deactivate,
rules = mesecons_rules
},
},
_mcl_blast_resistance = 3600000,
_mcl_hardness = -1,
can_dig = function(pos, player)
return minetest.check_player_privs(player, "better_command_blocks")
end,
drop = "",
on_place = function(itemstack, player, pointed_thing)
if minetest.check_player_privs(player, "better_command_blocks") then
return minetest.item_place(itemstack, player, pointed_thing)
end
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
minetest.get_meta(pos):set_string("_player", placer:get_player_name())
end
}
local itemstring = "better_command_blocks:"..name.."_command_block"
minetest.register_node(itemstring, def)
command_block_itemstrings[i] = itemstring
local conditional_def = table.copy(def)
conditional_def.groups.not_in_creative_inventory = 1
conditional_def.groups.command_block = i+3
conditional_def.description = S("Conditional @1", desc)
conditional_def.tiles = {
{name = "better_command_blocks_"..name.."_conditional_top.png", animation = anim},
{name = "better_command_blocks_"..name.."_conditional_bottom.png", animation = anim},
{name = "better_command_blocks_"..name.."_conditional_right.png", animation = anim},
{name = "better_command_blocks_"..name.."_conditional_left.png", animation = anim},
{name = "better_command_blocks_"..name.."_front.png", animation = anim},
{name = "better_command_blocks_"..name.."_back.png", animation = anim},
}
itemstring = "better_command_blocks:"..name.."_command_block_conditional"
minetest.register_node(itemstring, conditional_def)
command_block_itemstrings[i+3] = itemstring
end
minetest.register_alias("better_command_blocks:command_block", "better_command_blocks:impulse_command_block")
minetest.register_alias("better_command_blocks:command_block_conditional", "better_command_blocks:impulse_command_block_conditional")
---@diagnostic disable-next-line: missing-fields
minetest.register_privilege("better_command_blocks", {
description = S("Allows players to use Better Command Blocks"),
give_to_singleplayer = false,
give_to_admin = true
})