Add chests, the beginnings of a machines API, and other things

This commit is contained in:
Signal 2025-10-22 18:25:22 -04:00
parent 3720070a28
commit 4d8312b79d
22 changed files with 557 additions and 16 deletions

View file

@ -46,6 +46,13 @@ function ns.register_node(name, def)
alias = name alias = name
name = "red_glazed_terracotta:"..name name = "red_glazed_terracotta:"..name
end end
if def.groups then
if def.groups.interactable then
def.on_rightclick = function(pos, _, p)
rgt.players[p:get_player_name()].interacting_with = pos
end
end
end
if def._variants then if def._variants then
if type(def._variants) == "string" then if type(def._variants) == "string" then
rgt_world["register_"..def._variants](def) rgt_world["register_"..def._variants](def)
@ -87,6 +94,15 @@ function ns.register_tool(name, def)
end end
end end
-- Allow nodes to provide a callback to run on activation without
-- needing to register a bunch of mostly identical LBMs.
minetest.register_lbm {
name = ":red_glazed_terracotta:on_activate",
nodenames = {"group:run_on_activate"},
action = function(pos, node)
minetest.registered_nodes[node.name].on_activate(pos)
end
}
minetest.register_on_joinplayer(function(p) minetest.register_on_joinplayer(function(p)

130
mods/rgt_chests/init.lua Normal file
View file

@ -0,0 +1,130 @@
local rgt_chests = {
chests = {}
}
local ns = rgt_chests
minetest.register_entity(":red_glazed_terracotta:chest_display", {
initial_properties = {
visual = "mesh",
mesh = "rgt_chest.gltf",
textures = {"rgt_chest.png"},
pointable = false,
-- static_save = false
},
on_activate = function(e, data)
if not minetest.get_node(e.object:get_pos()).name:find "chest" then
e.object:remove()
return
end
e.object:set_armor_groups{immortal = 1}
e._pos = minetest.deserialize(data)
ns.chests[vector.to_string(e._pos)] = e
e.users = {}
end,
on_deactivate = function(e)
ns.chests[vector.to_string(e._pos)] = nil
end,
get_staticdata = function(e)
return minetest.serialize(e._pos)
end
})
local function make_chest_entity(pos, rot)
local e = minetest.add_entity(pos, "red_glazed_terracotta:chest_display", minetest.serialize(pos))
e:set_rotation(rot)
return e
end
-- This node is a hack to allow for a pretty wielditem while having an airlike drawtype on the actual underlying node.
rgt.register_node("chest", {
drawtype = "mesh",
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "4dir",
mesh = "rgt_chest.gltf",
tiles = {"rgt_chest.png"},
on_place = function(s, p, pt)
return minetest.item_place_node(ItemStack("real_chest"), p, pt)
end
})
rgt.register_node("real_chest", {
drawtype = "airlike",
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "4dir",
selection_box = {
type = "fixed",
fixed = {
-7/16, -0.5, -7/16,
7/16, 6/16, 7/16
}
},
groups = {
dig_immediate = 3,
},
drop = "chest",
on_construct = function(pos)
make_chest_entity(pos, minetest.fourdir_to_dir(minetest.get_node(pos).param2):dir_to_rotation())
local m = minetest.get_meta(pos)
local inv = m:get_inventory()
inv:set_size("inv", 8*3)
local fs = {"\
formspec_version[10]\
size[12,11]\
style_type[button,image_button;border=false]\
"}
for x = 0, 7 do
for y = 0, 2 do
fs[#fs +1] = "\
image["..(x *1.25 +1.125 -0.0625)..","..(y *1.25 +1 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
"
end
end
for x = 0, 7 do
for y = 0, 3 do
fs[#fs +1] = "\
image["..(x *1.25 +1.125 -0.0625)..","..(y *1.25 +5.5 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
"
end
end
fs[#fs +1] = "\
list[context;inv;1.125,1;8,3;]\
list[current_player;main;1.125,5.5;8,4;]\
listring[]\
"
m:set_string("formspec", table.concat(fs))
end,
on_destruct = function(pos)
local e = ns.chests[pos:to_string()]
local m = minetest.get_meta(pos)
local inv = m:get_inventory()
for i = 1, inv:get_size("inv") do
local item = minetest.add_item(pos, inv:get_stack("inv", i))
if item then
item:set_velocity(vector.random_direction() *math.random(1, 3))
end
end
e.object:remove()
end,
on_rightclick = function(pos, node, p, s, pt)
local e = ns.chests[pos:to_string()]
if not e.open then
e.open = true
e.object:set_animation({x=0,y=0.5}, 1, 0.1, false)
end
e.users[p:get_player_name()] = true
end,
on_receive_fields = function(pos, _, data, p)
local e = ns.chests[pos:to_string()]
if data.quit then
e.users[p:get_player_name()] = nil
if not next(e.users) then
e.open = false
e.object:set_animation({x=0.5,y=1}, 1, 0.1, false)
end
end
end
})

2
mods/rgt_chests/mod.conf Normal file
View file

@ -0,0 +1,2 @@
name = rgt_chests
depends = rgt_base

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

View file

@ -72,6 +72,9 @@ function ns.apply_wearable(m, wearable)
item[i] = ns.apply_wearable_part(m, x, i) item[i] = ns.apply_wearable_part(m, x, i)
end end
m.wearing[w.name] = item m.wearing[w.name] = item
if w.logical_height_offset then
m:set_logical_height_offset(m.logical_height_offset +w.logical_height_offset)
end
end end
function ns.remove_wearable(m, wearable) function ns.remove_wearable(m, wearable)
@ -82,6 +85,9 @@ function ns.remove_wearable(m, wearable)
x:remove() x:remove()
end end
m.wearing[wearable] = nil m.wearing[wearable] = nil
if ns.wearables[wearable].logical_height_offset then
m:set_logical_height_offset(m.logical_height_offset -ns.wearables[wearable].logical_height_offset)
end
end end
end end
@ -94,6 +100,7 @@ end)
ns.register_wearable { ns.register_wearable {
name = "top_hat", name = "top_hat",
logical_height_offset = 4.5,
attachments = { attachments = {
{ {
mesh = "rgt_top_hat.gltf", mesh = "rgt_top_hat.gltf",

95
mods/rgt_inv/init.lua Normal file
View file

@ -0,0 +1,95 @@
rgt_inv = {}
local ns = rgt_inv
Inventory = setmetatable({
new = function(p)
local e = setmetatable({
player = p,
state = {
proximate_machines = {}
}
}, {
__index = Inventory,
-- Setting a value on the Inventory instance directly will
-- automatically update the player's inventory formspec.
-- `inv.state` should be used in cases where this is not desirable.
__newindex = function(e, k, v)
e.state[k] = v
e:rebuild()
end
})
e:rebuild()
return e
end,
rebuild = function(e)
local fs = {"\
formspec_version[10]\
size[12,10]\
style_type[button,image_button;border=false]\
"}
for x = 0, 7 do
for y = 0, 3 do
fs[#fs +1] = "\
image["..(x *1.25 +1.125 -0.0625)..","..(y *1.25 +4.5 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
"
end
end
fs[#fs +1] = "\
style_type[image_button;noclip=true;bgimg=rgt_button_bg.png;bgimg_middle=8,8]\
"
local i = 0
for _, x in ipairs(e.state.proximate_machines) do
local y = i > 11 and 10.5 or -1
fs[#fs +1] = "image_button["..(i %11 +0.125)..","..y..";0.75,0.75;rgt_stone.png;blah;]"
i = i +1
end
fs[#fs +1] = "\
list[current_player;main;1.125,4.5;8,4;]\
list[current_player;craft;3,0.5;3,3;]\
listring[]\
list[current_player;craftpreview;7,1;1,1;]\
"
e.player:set_inventory_formspec(table.concat(fs))
end,
on_action = function(e, data)
end
}, {
__call = function(_, ...)
return Inventory.new(...)
end
})
local last_time = 0
minetest.register_globalstep(function()
local time = minetest.get_us_time()
-- Scan for machines every second.
if time -last_time > 1000000 then
for name, m in pairs(rgt.players) do
local pm = {}
local machines = minetest.find_nodes_in_area(m.pos:offset(-7, -7, -7), m.pos:offset(7, 7, 7), "group:rgt_machine", true)
for type, positions in pairs(machines) do
pm[#pm +1] = {
type = type,
pos = positions[math.random(1, #positions)]
}
end
if not (#pm <= 0 and #m.inv.state.proximate_machines <= 0) then
-- Give the machines list a predictable order by sorting it alphabetically prior to submission.
table.sort(pm, function(a, b) return a.type < b.type end)
m.inv.proximate_machines = pm
end
end
last_time = time
end
end)
minetest.register_chatcommand("/lua", {
privs = {server = true},
func = function(name, args)
xpcall(function()
loadstring(args)()
end, say)
end
})

2
mods/rgt_inv/mod.conf Normal file
View file

@ -0,0 +1,2 @@
name = rgt_inv
depends = rgt_player

View file

View file

@ -0,0 +1,109 @@
local function update_formspec(pos)
end
local function on_input(pos, m, inv)
end
local function on_add_fuel(pos, m, inv)
end
local function destination_list(s)
return minetest.get_item_group(s:get_name(), "furnace_fuel") > 0 and "fuel" or "input"
end
local function can_run(pos, m, inv)
return false
end
rgt_machines.register_machine("fuel_furnace", {
states = {
idle = {
tiles = {"rgt_stone.png"},
},
active = {
tiles = {"rgt_stone.png"},
}
},
on_construct = function(pos)
local m = minetest.get_meta(pos)
local inv = m:get_inventory()
inv:set_size("input", 1)
inv:set_size("output", 1)
-- Dummy list handle Shift-adding properly.
inv:set_size("sort", 1)
inv:set_size("fuel", 1)
end,
get_gui = function(pos)
local loc = "nodemeta:"..pos.x..","..pos.y..","..pos.z
local fs = "\
formspec_version[10]\
size[12,12]\
\
image["..(2 -0.0625)..","..(2 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
list["..loc..";input;2,2;1,1;]\
\
image["..(5 -0.0625)..","..(3 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
list["..loc..";output;5,3;1,1;]\
\
image["..(2 -0.0625)..","..(4 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
list["..loc..";fuel;2,4;1,1;]\
"
for x = 0, 7 do
for y = 0, 3 do
fs = fs.."\
image["..(x *1.25 +1.125 -0.0625)..","..(y *1.25 +6.5 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
"
end
end
fs = fs.."\
list[current_player;main;1.125,6.5;8,4;]\
\
listring["..loc..";output]\
listring[current_player;main]\
listring["..loc..";sort]\
listring[current_player;main]\
listring["..loc..";input]\
listring[current_player;main]\
listring["..loc..";fuel]\
listring[current_player;main]\
"
end,
allow_metadata_inventory_put = function(pos, list, idx, s, p)
local inv = minetest.get_meta(pos):get_inventory()
if list == "output" then
return 0
elseif list == "input" then
return s:get_count()
elseif list == "fuel" then
return minetest.get_item_group(s:get_name(), "furnace_fuel") > 0 and s:get_count() or 0
elseif list == "sort" then
local dst = destination_list(s)
if dst then
-- Add everything if we can.
if inv:room_for_item(dst, s) then
return s:get_count()
-- If not, and the stacks are compatible, add as much as possible.
elseif inv:room_for_item(dst, s:take_item()) then
return s:get_stack_max() -inv:get_stack(dst, 1):get_count()
end
end
return 0
end
return 0
end,
on_metadata_inventory_put = function(pos, list, idx, s, p)
if list == "sort" then
local inv = minetest.get_meta(pos):get_inventory()
local dst = destination_list(s)
inv:add_item(dst, s)
-- Ensure the sorter list never has anything in it.
inv:set_stack("sort", 1, ItemStack(""))
end
end
})

View file

@ -0,0 +1,2 @@
name = rgt_furnace
depends = rgt_machines_core

View file

@ -0,0 +1,27 @@
rgt_machines = {
registered_machines = {}
}
local ns = rgt_machines
-- This abstracts away the use of multiple nodes for visual state feedback by
-- copying all node callbacks into each node, so that by default the machine
-- behaves exactly the same regardless of the underlying node type.
--[[
{
states = { ... }, -- Alternate visual states for this node. All properties of the resultant node may be overriden.
...
}
--]]
function ns.register_machine(name, def)
if not def.groups then
def.groups = {}
end
def.groups.rgt_machine = 1
def.groups.run_on_activate = 1
def.groups[name] = 1
ns.registered_machines[name] = def
for state, x in pairs(def.states) do
rgt.register_node(name.."_"..state, extend(table.copy(def), x))
end
end

View file

@ -0,0 +1,2 @@
name = rgt_machines_core
depends = rgt_world, rgt_player

View file

@ -1,10 +1,29 @@
--[[
Minimum features needed to make this a playable game:
- Basic towns
- Covered wagon
- Machines
-
- Materials
- Diamond
- Biomes
- Ocean
- Decoration nodes
- Stone bricks
- Stone tile (default stone block)
- Sand
- Gravel
--]]
rgt.register_item("iron_ingot", { rgt.register_item("iron_ingot", {
inventory_image = "rgt_iron_ingot.png", inventory_image = "rgt_iron_ingot.png",
groups = {furnace_fuel = 1}
}) })
rgt.register_item("iron_lump", { rgt.register_item("iron_lump", {
inventory_image = "rgt_iron_lump.png", inventory_image = "rgt_iron_lump.png",
groups = {furnace_smeltable = 1}
}) })
rgt.register_node("iron_block", { rgt.register_node("iron_block", {

View file

@ -45,19 +45,21 @@ Player = {
e.wearing = {} e.wearing = {}
e.logical_height_offset = 0
e:update_hp(p:get_hp()) e:update_hp(p:get_hp())
e:update_inv() e.object:set_formspec_prepend [[
return e
end,
update_inv = function(m)
m.object:set_formspec_prepend [[
bgcolor[#000;true] bgcolor[#000;true]
background9[0,0;0,0;rgt_container_bg.png;true;16,16] background9[0,0;0,0;rgt_container_bg.png;true;16,16]
style_type[button;border=false;bgimg=rgt_button_bg.png;bgimg_middle=8,8] style_type[button;border=false;bgimg=rgt_button_bg.png;bgimg_middle=8,8]
listcolors[#fff0;#fff3;#0000;#444;#aaa] listcolors[#fff0;#fff3;#0000;#444;#aaa]
]] ]]
e.inv = Inventory(p)
return e
end,
update_inv = function(m)
local fs = "\ local fs = "\
formspec_version[10]\ formspec_version[10]\
size[12,10]\ size[12,10]\
@ -65,23 +67,27 @@ Player = {
for x = 0, 7 do for x = 0, 7 do
for y = 0, 3 do for y = 0, 3 do
fs = fs.."\ fs = fs.."\
image["..(x *1.25 +1 -0.0625)..","..(y *1.25 +4.5 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\ image["..(x *1.25 +1.125 -0.0625)..","..(y *1.25 +4.5 -0.0625)..";1.14,1.14;rgt_other_button_bg.png;8,8]\
" "
end end
end end
fs = fs.."\ fs = fs.."\
list[current_player;main;1,4.5;8,4;]\ list[current_player;main;1.125,4.5;8,4;]\
list[current_player;craft;3,0.5;3,3;]\ list[current_player;craft;3,0.5;3,3;]\
listring[]\ listring[]\
list[current_player;craftpreview;7,1;1,1;]\ list[current_player;craftpreview;7,1;1,1;]\
" "
m.object:set_inventory_formspec(fs) m.object:set_inventory_formspec(fs)
end, end,
set_logical_height_offset = function(m, offset)
m.logical_height_offset = offset
m.health_display:set_attach(m.object, nil, vector.new(0, 22 +m.logical_height_offset, 0))
end,
update_hp = function(m, hp) update_hp = function(m, hp)
if not (m.health_display and m.health_display:is_valid()) then if not (m.health_display and m.health_display:is_valid()) then
local pos = m.object:get_pos() or vector.zero() local pos = m.object:get_pos() or vector.zero()
m.health_display = minetest.add_entity(pos, "rgt_player:health_display") m.health_display = minetest.add_entity(pos, "rgt_player:health_display")
m.health_display:set_attach(m.object, nil, vector.new(0, 22, 0)) m.health_display:set_attach(m.object, nil, vector.new(0, 22 +m.logical_height_offset, 0))
m.health_display:get_luaentity().owner = m m.health_display:get_luaentity().owner = m
end end
local tx = "[combine:90x90" local tx = "[combine:90x90"
@ -117,7 +123,7 @@ Player = {
if def._wield_pos then if def._wield_pos then
pos = pos +def._wield_pos pos = pos +def._wield_pos
end end
local rot = def._wield_rot or def.type == "node" and vector.new(45, 40, 30) or (def.type == "tool" and vector.new(90, -45, 90) or vector.new(-90, -100, 90)) local rot = def._wield_rot or def.type == "node" and vector.new(-45, 165, -30) or (def.type == "tool" and vector.new(90, -45, 90) or vector.new(-90, -100, 90))
local scale = def._wield_scale or vector.new(0.2, 0.2, 0.2) local scale = def._wield_scale or vector.new(0.2, 0.2, 0.2)
if type(scale) == "number" then if type(scale) == "number" then
@ -292,8 +298,7 @@ Player = {
m.last_time = time m.last_time = time
end end
-- Custom on-hover effects -- Run on-hover callbacks
m.pointed_node = nil m.pointed_node = nil
local pointed_found = false local pointed_found = false
@ -321,6 +326,7 @@ Player = {
end end
end end
elseif pointed and pointed.type == "node" and not m.pointed_node then elseif pointed and pointed.type == "node" and not m.pointed_node then
pointed.node_under = minetest.get_node(pointed.under)
m.pointed_node = pointed m.pointed_node = pointed
end end
end end
@ -334,6 +340,44 @@ Player = {
m.pos = pos m.pos = pos
-- Hold-to-interact handling
if m.interacting_with and m.ctl.place then
if not m.interaction_start then
m.interaction_start = time
m.interaction_marker = minetest.add_entity(m.pointed_node.under, "display")
m.interaction_marker:set_properties {
visual = "sprite",
textures = {"rgt_interact_progress_0.png"}
}
else
if time -m.interaction_start > 1100000 then
m.interaction_marker:remove()
minetest.registered_nodes[minetest.get_node(m.interacting_with).name].on_interact(m.interacting_with, m)
elseif time -m.interaction_start > 1000000 then
m.interaction_marker:set_properties {
textures = {"rgt_interact_progress_100.png"}
}
elseif time -m.interaction_start > 750000 then
m.interaction_marker:set_properties {
textures = {"rgt_interact_progress_75.png"}
}
elseif time -m.interaction_start > 500000 then
m.interaction_marker:set_properties {
textures = {"rgt_interact_progress_50.png"}
}
elseif time -m.interaction_start > 250000 then
m.interaction_marker:set_properties {
textures = {"rgt_interact_progress_25.png"}
}
end
end
elseif m.interacting_with and not m.ctl.place then
m.interacting_with = nil
m.interaction_start = nil
m.interaction_marker:remove()
m.interaction_marker = nil
end
-- Run on_wield callbacks -- Run on_wield callbacks
local w = p:get_wielded_item() local w = p:get_wielded_item()
local wname = w:get_name() local wname = w:get_name()

View file

@ -1,3 +1,22 @@
--[[
============== Progression =============
* Dig grass to get grass
* Craft grass into (grass) string
* Dig leaves to get sticks
* Craft sticks and (grass) string to get a staff
* Dig loose cobble to get loose cobble
* Craft loose cobble with a staff and (grass) string to get an adze
* Dig a tree with an adze to get a plank (and a surprise)
* Craft a plank with an adze and (grass) string to get a wooden pick head (and one less adze)
* Craft the pick head with a staff and string to get a wooden pick
* Use a wooden pick to dig stone (note: wooden picks only get cobble from ores)
* Use the stone to make a furnace
* Use the furnace to cook loose ore cobble (requires 4x at a time)
*
--]]
rgt.register_tool("iron_sword", { rgt.register_tool("iron_sword", {
inventory_image = "rgt_sword_iron.png", inventory_image = "rgt_sword_iron.png",

View file

@ -224,6 +224,11 @@ function ns.place_plot(grid, pos, plot, owner, rot)
local def = ns.plots[plot] local def = ns.plots[plot]
-- Erase all preexisting node metadata.
for _, pos in ipairs(minetest.find_nodes_with_meta(box.min, box.max)) do
minetest.remove_node(pos)
end
minetest.place_schematic(dst, def.schematic, rot, repl, true, {}) minetest.place_schematic(dst, def.schematic, rot, repl, true, {})
local p, gp, m local p, gp, m

View file

@ -19,6 +19,24 @@ rgt.register_node("stone", {
groups = {stone = 1} groups = {stone = 1}
}) })
rgt.register_node("stone_brick", {
tiles = {{name = "rgt_stone_brick.png", align_style = "world"}},
_variants = "all",
groups = {stone = 1}
})
rgt.register_node("stone_brick_large", {
tiles = {{name = "rgt_stone_brick_large.png", align_style = "world"}},
_variants = "all",
groups = {stone = 1}
})
rgt.register_node("stone_tile", {
tiles = {{name = "rgt_stone_tile.png", align_style = "world"}},
_variants = "all",
groups = {stone = 1}
})
rgt.register_node("cobble", { rgt.register_node("cobble", {
tiles = {"rgt_cobble.png"}, tiles = {"rgt_cobble.png"},
_variants = "all", _variants = "all",
@ -57,6 +75,10 @@ rgt.register_node("path_grass", {
groups = {dig_immediate = 3} groups = {dig_immediate = 3}
}) })
rgt.register_node("sand", {
tiles = {"rgt_sand.png"},
groups = {dig_immediate = 3}
})
rgt.register_node("oak_log", { rgt.register_node("oak_log", {
@ -222,9 +244,24 @@ minetest.register_alias("adrift:water", "red_glazed_terracotta:water")
-- place_offset_y = 1, -- place_offset_y = 1,
--} --}
rgt.register_node("light", {
tiles = {"[fill:1x1:0,0:#fed"},
light_source = 14,
paramtype = "light"
})
minetest.register_ore {
ore_type = "scatter",
ore = "light",
wherein = "stone",
clust_scarcity = 3 * 3 * 3,
clust_num_ores = 5,
clust_size = 1
}
minetest.register_biome{ minetest.register_biome{
name = "!", name = "plains",
node_top = "dirt_grass", node_top = "dirt_grass",
depth_top = 1, depth_top = 1,
@ -239,10 +276,34 @@ minetest.register_biome{
node_dungeon_alt = "stone", node_dungeon_alt = "stone",
y_max = alt_max, y_max = 3000,
y_min = sealevel, y_min = 2,
vertical_blend = 2, vertical_blend = 2,
heat_point = 50, heat_point = 50,
humidity_point = 50, humidity_point = 50,
} }
minetest.register_biome{
name = "beach",
node_top = "sand",
depth_top = 1,
node_filler = "sand",
depth_filler = 2,
node_riverbed = "sand",
depth_riverbed = 3,
node_dungeon = "cobble",
node_dungeon_alt = "stone",
y_max = 1,
y_min = -3,
vertical_blend = 1,
heat_point = 50,
humidity_point = 50,
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B