Charakterbewegungen hinzugefügt, Deko hinzugefügt, Kochrezepte angepasst

This commit is contained in:
N-Nachtigal 2025-05-14 16:36:42 +02:00
parent 95945c0306
commit a0c893ca0b
1124 changed files with 64294 additions and 763 deletions

View file

@ -0,0 +1,657 @@
std = "lua51+luajit+minetest+stairsplus"
unused_args = false
max_line_length = 120
stds.minetest = {
read_globals = {
"DIR_DELIM",
"dump",
"dump2",
math = {
fields = {
abs = {},
acos = {},
asin = {},
atan = {},
atan2 = {},
ceil = {},
cos = {},
cosh = {},
deg = {},
exp = {},
factorial = {},
floor = {},
fmod = {},
frexp = {},
huge = {},
hypot = {},
ldexp = {},
log = {},
log10 = {},
max = {},
min = {},
modf = {},
pi = {},
pow = {},
rad = {},
random = {},
randomseed = {},
round = {},
sign = {},
sin = {},
sinh = {},
sqrt = {},
tan = {},
tanh = {},
},
},
table = {
fields = {
copy = {},
concat = {},
foreach = {},
foreachi = {},
getn = {},
indexof = {},
insert = {},
insert_all = {},
key_value_swap = {},
maxn = {},
move = {},
remove = {},
shuffle = {},
sort = {},
},
},
string = {
fields = {
byte = {},
char = {},
dump = {},
find = {},
format = {},
gmatch = {},
len = {},
lower = {},
match = {},
rep = {},
reverse = {},
split = {},
sub = {},
trim = {},
upper = {},
},
},
vector = {
fields = {
add = {},
angle = {},
apply = {},
check = {},
combine = {},
copy = {},
cross = {},
dir_to_rotation = {},
direction = {},
distance = {},
divide = {},
dot = {},
equals = {},
floor = {},
from_string = {},
length = {},
metatable = {},
multiply = {},
new = {},
normalize = {},
offset = {},
rotate = {},
rotate_around_axis = {},
round = {},
sort = {},
subtract = {},
to_string = {},
zero = {},
},
},
ItemStack = {
fields = {
add_item = {},
add_wear = {},
add_wear_by_uses = {},
clear = {},
get_count = {},
get_definition = {},
get_description = {},
get_free_space = {},
get_meta = {},
get_metadata = {},
get_name = {},
get_short_description = {},
get_stack_max = {},
get_tool_capabilities = {},
get_wear = {},
is_empty = {},
is_known = {},
item_fits = {},
peek_item = {},
replace = {},
set_count = {},
set_metadata = {},
set_name = {},
set_wear = {},
take_item = {},
to_string = {},
to_table = {},
},
},
PerlinNoise = {
fields = {
get_2d = {},
get_3d = {},
},
},
PerlinNoiseMap = {
fields = {
calc_2d_map = {},
calc_3d_map = {},
get_2d_map = {},
get_2d_map_flat = {},
get_3d_map = {},
get_3d_map_flat = {},
get_map_slice = {},
},
},
PseudoRandom = {
fields = {
next = {},
},
},
PcgRandom = {
fields = {
next = {},
rand_normal_dist = {},
},
},
SecureRandom = {
fields = {
next_bytes = {},
},
},
Settings = {
fields = {
get = {},
get_bool = {},
get_flags = {},
get_names = {},
get_np_group = {},
remove = {},
set = {},
set_bool = {},
set_np_group = {},
to_table = {},
write = {},
},
},
VoxelArea = {
fields = {
MaxEdge = {},
MinEdge = {},
contains = {},
containsi = {},
containsp = {},
getExtent = {},
getVolume = {},
index = {},
indexp = {},
iter = {},
iterp = {},
new = {},
position = {},
ystride = {},
zstride = {},
},
},
VoxelManip = {
fields = {
calc_lighting = {},
get_data = {},
get_emerged_area = {},
get_light_data = {},
get_node_at = {},
get_param2_data = {},
read_from_map = {},
set_data = {},
set_light_data = {},
set_lighting = {},
set_node_at = {},
set_param2_data = {},
update_liquids = {},
update_map = {},
was_modified = {},
write_to_map = {},
},
},
minetest = {
fields = {
CONTENT_AIR = {},
CONTENT_IGNORE = {},
CONTENT_UNKNOWN = {},
EMERGE_CANCELLED = {},
EMERGE_ERRORED = {},
EMERGE_FROM_DISK = {},
EMERGE_FROM_MEMORY = {},
EMERGE_GENERATED = {},
LIGHT_MAX = {},
MAP_BLOCKSIZE = {},
PLAYER_MAX_BREATH_DEFAULT = {},
PLAYER_MAX_HP_DEFAULT = {},
add_entity = {},
add_item = {},
add_node = {},
add_node_level = {},
add_particle = {},
add_particlespawner = {},
after = {},
async_event_handler = {},
async_jobs = {},
auth_reload = {},
ban_player = {},
builtin_auth_handler = {},
bulk_set_node = {},
calculate_knockback = {},
callback_origins = {},
cancel_shutdown_requests = {},
chat_send_all = {},
chat_send_player = {},
chatcommands = {},
check_for_falling = {},
check_password_entry = {},
check_player_privs = {},
check_single_for_falling = {},
clear_craft = {},
clear_objects = {},
clear_registered_biomes = {},
clear_registered_decorations = {},
clear_registered_ores = {},
clear_registered_schematics = {},
close_formspec = {},
colorize = {},
colorspec_to_bytes = {},
colorspec_to_colorstring = {},
compare_block_status = {},
compress = {},
cpdir = {},
craft_predict = {},
craftitemdef_default = {},
create_detached_inventory = {},
create_detached_inventory_raw = {},
create_schematic = {},
debug = {},
decode_base64 = {},
decompress = {},
delete_area = {},
delete_particlespawner = {},
deserialize = {},
detached_inventories = {},
dig_node = {},
dir_to_facedir = {},
dir_to_wallmounted = {},
dir_to_yaw = {},
disconnect_player = {},
do_async_callback = {},
do_item_eat = {},
dynamic_add_media = {},
dynamic_media_callbacks = {},
emerge_area = {},
encode_base64 = {},
encode_png = {},
env = {},
explode_scrollbar_event = {},
explode_table_event = {},
explode_textlist_event = {},
facedir_to_dir = {},
features = {},
find_node_near = {},
find_nodes_in_area = {},
find_nodes_in_area_under_air = {},
find_nodes_with_meta = {},
find_path = {},
fix_light = {},
forceload_block = {},
forceload_free_block = {},
format_chat_message = {},
formspec_escape = {},
generate_decorations = {},
generate_ores = {},
get_all_craft_recipes = {},
get_artificial_light = {},
get_auth_handler = {},
get_background_escape_sequence = {},
get_ban_description = {},
get_ban_list = {},
get_biome_data = {},
get_biome_id = {},
get_biome_name = {},
get_builtin_path = {},
get_color_escape_sequence = {},
get_connected_players = {},
get_content_id = {},
get_craft_recipe = {},
get_craft_result = {},
get_current_modname = {},
get_day_count = {},
get_decoration_id = {},
get_dig_params = {},
get_dir_list = {},
get_gametime = {},
get_gen_notify = {},
get_heat = {},
get_hit_params = {},
get_humidity = {},
get_inventory = {},
get_item_group = {},
get_last_run_mod = {},
get_mapgen_object = {},
get_mapgen_params = {},
get_mapgen_setting = {},
get_mapgen_setting_noiseparams = {},
get_meta = {},
get_mod_storage = {},
get_modnames = {},
get_modpath = {},
get_name_from_content_id = {},
get_natural_light = {},
get_node = {},
get_node_drops = {},
get_node_group = {},
get_node_level = {},
get_node_light = {},
get_node_max_level = {},
get_node_or_nil = {},
get_node_timer = {},
get_noiseparams = {},
get_objects_in_area = {},
get_objects_inside_radius = {},
get_password_hash = {},
get_perlin = {},
get_perlin_map = {},
get_player_by_name = {},
get_player_information = {},
get_player_ip = {},
get_player_privs = {},
get_player_radius_area = {},
get_pointed_thing_position = {},
get_position_from_hash = {},
get_server_max_lag = {},
get_server_status = {},
get_server_uptime = {},
get_spawn_level = {},
get_timeofday = {},
get_tool_wear_after_use = {},
get_translated_string = {},
get_translator = {},
get_us_time = {},
get_user_path = {},
get_version = {},
get_voxel_manip = {},
get_worldpath = {},
global_exists = {},
handle_async = {},
handle_node_drops = {},
has_feature = {},
hash_node_position = {},
hud_replace_builtin = {},
inventorycube = {},
is_area_protected = {},
is_colored_paramtype = {},
is_creative_enabled = {},
is_nan = {},
is_player = {},
is_protected = {},
is_singleplayer = {},
is_yes = {},
item_drop = {},
item_eat = {},
item_place = {},
item_place_node = {},
item_place_object = {},
item_secondary_use = {},
itemstring_with_color = {},
itemstring_with_palette = {},
kick_player = {},
line_of_sight = {},
load_area = {},
log = {},
luaentities = {},
mkdir = {},
mod_channel_join = {},
mvdir = {},
node_dig = {},
node_punch = {},
nodedef_default = {},
noneitemdef_default = {},
notify_authentication_modified = {},
object_refs = {},
on_craft = {},
override_chatcommand = {},
override_item = {},
parse_coordinates = {},
parse_json = {},
parse_relative_number = {},
place_node = {},
place_schematic = {},
place_schematic_on_vmanip = {},
player_exists = {},
pointed_thing_to_face_pos = {},
pos_to_string = {},
print = {},
privs_to_string = {},
punch_node = {},
raillike_group = {},
raycast = {},
read_schematic = {},
record_protection_violation = {},
register_abm = {},
register_alias = {},
register_alias_force = {},
register_allow_player_inventory_action = {},
register_async_dofile = {},
register_authentication_handler = {},
register_biome = {},
register_can_bypass_userlimit = {},
register_chatcommand = {},
register_craft = {},
register_craft_predict = {},
register_craftitem = {},
register_decoration = {},
register_entity = {},
register_globalstep = {},
register_item = {},
register_lbm = {},
register_node = {},
register_on_auth_fail = {},
register_on_authplayer = {},
register_on_chat_message = {},
register_on_chatcommand = {},
register_on_cheat = {},
register_on_craft = {},
register_on_dieplayer = {},
register_on_dignode = {},
register_on_generated = {},
register_on_item_eat = {},
register_on_joinplayer = {},
register_on_leaveplayer = {},
register_on_liquid_transformed = {},
register_on_mapgen_init = {},
register_on_modchannel_message = {},
register_on_mods_loaded = {},
register_on_newplayer = {},
register_on_placenode = {},
register_on_player_hpchange = {},
register_on_player_inventory_action = {},
register_on_player_receive_fields = {},
register_on_prejoinplayer = {},
register_on_priv_grant = {},
register_on_priv_revoke = {},
register_on_protection_violation = {},
register_on_punchnode = {},
register_on_punchplayer = {},
register_on_respawnplayer = {},
register_on_rightclickplayer = {},
register_on_shutdown = {},
register_ore = {},
register_playerevent = {},
register_privilege = {},
register_schematic = {},
register_tool = {},
registered_abms = {other_fields = true},
registered_aliases = {other_fields = true},
registered_allow_player_inventory_actions = {other_fields = true},
registered_biomes = {other_fields = true},
registered_can_bypass_userlimit = {other_fields = true},
registered_chatcommands = {other_fields = true},
registered_craft_predicts = {other_fields = true},
registered_craftitems = {other_fields = true},
registered_decorations = {other_fields = true},
registered_entities = {other_fields = true},
registered_globalsteps = {other_fields = true},
registered_items = {other_fields = true},
registered_lbms = {other_fields = true},
registered_nodes = {other_fields = true},
registered_on_authplayers = {other_fields = true},
registered_on_chat_messages = {other_fields = true},
registered_on_chatcommands = {other_fields = true},
registered_on_cheats = {other_fields = true},
registered_on_crafts = {other_fields = true},
registered_on_dieplayers = {other_fields = true},
registered_on_dignodes = {other_fields = true},
registered_on_generateds = {other_fields = true},
registered_on_item_eats = {other_fields = true},
registered_on_joinplayers = {other_fields = true},
registered_on_leaveplayers = {other_fields = true},
registered_on_liquid_transformed = {other_fields = true},
registered_on_modchannel_message = {other_fields = true},
registered_on_mods_loaded = {other_fields = true},
registered_on_newplayers = {other_fields = true},
registered_on_placenodes = {other_fields = true},
registered_on_player_hpchange = {other_fields = true},
registered_on_player_hpchanges = {other_fields = true},
registered_on_player_inventory_actions = {other_fields = true},
registered_on_player_receive_fields = {other_fields = true},
registered_on_prejoinplayers = {other_fields = true},
registered_on_priv_grant = {other_fields = true},
registered_on_priv_revoke = {other_fields = true},
registered_on_protection_violation = {other_fields = true},
registered_on_punchnodes = {other_fields = true},
registered_on_punchplayers = {other_fields = true},
registered_on_respawnplayers = {other_fields = true},
registered_on_rightclickplayers = {other_fields = true},
registered_on_shutdown = {other_fields = true},
registered_ores = {other_fields = true},
registered_playerevents = {other_fields = true},
registered_privileges = {other_fields = true},
registered_tools = {other_fields = true},
remove_detached_inventory = {},
remove_detached_inventory_raw = {},
remove_node = {},
remove_player = {},
remove_player_auth = {},
request_http_api = {},
request_insecure_environment = {},
request_shutdown = {},
rgba = {},
rmdir = {},
rollback_get_last_node_actor = {},
rollback_get_node_actions = {},
rollback_punch_callbacks = {},
rollback_revert_actions_by = {},
rotate_and_place = {},
rotate_node = {},
run_callbacks = {},
run_priv_callbacks = {},
safe_file_write = {},
send_join_message = {},
send_leave_message = {},
serialize = {},
serialize_roundtrip = {},
serialize_schematic = {},
set_gen_notify = {},
set_last_run_mod = {},
set_mapgen_params = {},
set_mapgen_setting = {},
set_mapgen_setting_noiseparams = {},
set_node = {},
set_node_level = {},
set_noiseparams = {},
set_player_password = {},
set_player_privs = {},
set_timeofday = {},
setting_get = {},
setting_get_pos = {},
setting_getbool = {},
setting_save = {},
setting_set = {},
setting_setbool = {},
settings = {
fields = {
get = {},
get_bool = {},
get_np_group = {},
get_flags = {},
set = {},
set_bool = {},
set_np_group = {},
remove = {},
get_names = {},
write = {},
to_table = {},
},
},
sha1 = {},
show_formspec = {},
show_general_help_formspec = {},
show_privs_help_formspec = {},
sound_fade = {},
sound_play = {},
sound_stop = {},
spawn_falling_node = {},
spawn_item = {},
spawn_tree = {},
string_to_area = {},
string_to_pos = {},
string_to_privs = {},
strip_background_colors = {},
strip_colors = {},
strip_foreground_colors = {},
strip_param2_color = {},
swap_node = {},
tooldef_default = {},
transforming_liquid_add = {},
translate = {},
unban_player_or_ip = {},
unregister_biome = {},
unregister_chatcommand = {},
unregister_item = {},
wallmounted_to_dir = {},
wrap_text = {},
write_json = {},
yaw_to_dir = {},
},
},
}
}
stds.stairsplus = {
globals = {
"stairsplus",
},
read_globals = {
"default",
"fmod",
"futil",
},
}

View file

@ -0,0 +1,335 @@
# API documentation for Stairs+
In general, function arguments specified in square brackets ("[ ]") are optional.
## Shape API
For registering new shapes.
* `stairsplus.api.register_shape(name, def)`
An example of a node_box shape:
```lua
stairsplus.api.register_shape("slab_8", {
name_format = "slab_%s_8",
aliases = {"slab_%s"},
description = "@1 1/2 Slab",
shape_groups = {slab = 1, legacy = 1},
eighths = 4,
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
},
})
```
An example of a mesh shape:
```lua
local box_slope = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, -0.25, 0.5},
{-0.5, -0.25, -0.25, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.25, 0.5},
{-0.5, 0.25, 0.25, 0.5, 0.5, 0.5}
}
}
stairsplus.api.register_shape("slope", {
name_format = "slope_%s",
description = "@1 Slope",
shape_groups = {slope = 1, legacy = 1},
eighths = 4,
drawtype = "mesh",
mesh = "stairsplus_slope.obj",
collision_box = box_slope,
selection_box = box_slope,
})
```
Hopefully most of the paramaters should be self explanatory. `eighths` is the number of 1/8th node
microblocks that the shape is "worth", i.e. how much material is used in crafting it. when assigning a value to
`eighths`, generally 1 is the minimum, and 8 is the maximum. often, you should round to the nearest eighth
(`eights = round(volume * 8)`). however, sometimes shapes will form a natural complement w/ another shape to form
a simple volume, and you should instead choose values so that the eighths in the complements match the combined form.
* `stairsplus.api.register_shape_group(shape_group, shapes)`
Register a new shape group.
The following tables are exposed for convenience, but shouldn't be directly modified:
* `stairsplus.api.registered_shapes`
A hash of shape definitions by name.
* `stairsplus.api.shapes_by_group`
A hash of lists of shapes in a group, by the shape group name.
## Node API
These are for registering shapes of a node
* `stairsplus.api.register_on_register_single(function(node, shaped_name))`
Register a callback for when a shaped node is registered. Mostly useful for integration with
inventory managers.
* `stairsplus.api.register_single(node, shape, [overrides], [meta])`
Register a single shape of a node.
If no "micro_8" shape is defined, it will be defined before registering another shape. Attempting to
register an already-registered shape will not override anything.
Many of the node's properties will be inherited from the
node and shape definition, but can be manually changed via `overrides`. `meta` is used to specify
whether special logic should be used while defining the shaped node. The following values are supported:
* `ignore_type = true`
Whether to ignore the base node's `type` - usually, it is an error to try to register a shape for a
non-node.
* `ignore_drawtype = true`
Whether to ignore the base node's `drawtye`. Usually, it is an error to try to register a shape for a
non-node-like drawtype.
* `ignore_paramtype2 = true`
Whether to ignore the base node's `paramtype2`. Usually, it is an error to register a shape for node
which is not compatible with a `facedir` drawtype.
* `align_style = "node" | "user" | "world"`
Whether to override the align_style for the textures used to draw the shaped node. The default behavior
is to use the value defined by the `stairsplus.default_align_style` setting, which defaults to `"user"`.
* `allow_override_groups = true`
Whether to allow groups to be specified in the overrides (otherwise, they are ignored).
* `allow_override_drawtype = true`
Whether to allow a drawtype to be specified in the overrides (otherwise, it is ignored).
* `allow_override_paramtype2 = true`
Whether to allow a paramtype2 to be specified in the overrides (otherwise, it is ignored).
* `stairsplus.api.register_all(node, [overrides], [meta])`
Register all registered shapes for a node. we do not recommend using this, due to the 32767 node limit.
we recommend using
* `stairsplus.api.register_shapes(node, shapes, [overrides], [meta])`
Register variants for a custom list of shapes.
* `stairsplus.api.register_group(node, shape_group, [overrides], [meta])`
Register variants for a defined shape group.
* `stairsplus.api.register_groups(node, shape_groups, [overrides], [meta])`
Register variants for a list of shape groups.
* `stairsplus.api.format_name(node, shape)`
Get the itemstring for a shaped variant. It is not guaranteed that the resulting itemstring actually
exists.
* `stairsplus.api.get_shapes(node)`
Get the defined shapes for a node. Returns `nil` if no shapes are registered for the node.
* `stairsplus.api.get_shapes_hash(node)`
Get a hash where the keys are the shapes supported by the node. Mutating the return value is not
recommended. Will return `nil` if no shapes are registered for the node.
* `stairsplus.api.get_schema_recipe_item(node, shape_or_item)`
If `shape_or_item` is an empty string, return an empty string. If it is an itemstring (contains a ":"),
that itemstring is returned. If it is a shape, and the shape is defined for the given node, returns
the name of the shaped node. If it is a shape, but the shape is not defined for the given node, returns
`nil`. If it is the special string `"node"`, then it returns the node argument. `shape_or_item` may
optionally specify a count value, which will modify the returned value accordingly.
```lua
assert(stairsplus.api.get_schema_recipe_item("default:stone", "node 3") == "default:stone 3")
assert(stairsplus.api.get_schema_recipe_item("default:stone", "slope 3") == "default:slope_stone 3")
```
* `stairsplus.api.get_micronode(node)`
Convenience method for `stairsplus.api.get_schema_recipe_item(node, "micro_8")`.
* `stairsplus.api.get_node_of_shaped_node(shaped_node)`
Given a shaped node, get the "base" node it was made from.
* `stairsplus.api.get_shape_of_shaped_node(shaped_node)`
Given a shaped node, get its shape.
## Alias API
* `stairsplus.api.register_alias_single(old_node, new_node, shape)`
Hopefully this is self-explanatory.
* `stairsplus.api.register_alias_all(old_node, new_node)`
* `stairsplus.api.register_alias_shapes(old_node, new_node, shapes)`
* `stairsplus.api.register_alias_group(old_node, new_node, group)`
* `stairsplus.api.register_alias_groups(old_node, new_node, groups)`
* `stairsplus.api.register_alias_force_single(old_node, new_node, shape)`
`alias_force` implies that the shapes for the "old_node" already exist, so after aliasing them,
we remove them from internal data structures.
* `stairsplus.api.register_alias_force_all(old_node, new_node)`
* `stairsplus.api.register_alias_force_shapes(old_node, new_node, shape)`
* `stairsplus.api.register_alias_force_group(old_node, new_node, group)`
* `stairsplus.api.register_alias_force_groups(old_node, new_node, groups)`
## Group API
Specify how to handle a node's groups when creating a shaped variant. By default, a node's
groups have their name transformed in the same way that the node's name is, e.g. `"wood"` will become e.g.
`"micro_wood_8"`, so that the shaped nodes can't be used in crafting recipes that call for `"group:wood"`.
* `stairsplus.api.register_passthrough_group(group)`
Mark a group as "passthrough", i.e. it will not be transformed when assigned to a shaped node.
This is necessary to e.g. allow nodes to dig-able (e.g. groups like `"cracky"`, `"oddly_breakable_by_hand"`).
* `stairsplus.api.register_passthrough_groups(groups)`
Marks a list of groups as passthrough.
* `stairsplus.api.register_scaling_group(group)`
Like "passthrough", but this scales the group's *value* depending on the # of "eighths" that the shape
is worth. This is useful for groups like `"fall_damage_add_percent"` and `"slippery"`.
* `stairsplus.api.register_scaling_groups(groups)`
Marks a list of groups as scaling.
* `stairsplus.api.register_ignore_group(group)`
Marks a group as something we should just ignore entirely when creating a shaped node. Useful
for things like `"connect_to_raillike"` or `"attached_node"`, which don't makde sense for a shaped
node.
* `stairsplus.api.register_ignore_groups(groups)`
Marks a list of groups to ignore.
* `stairsplus.api.build_groups(node, shape)`
Given a node and a shape, return the groups to assign to a shaped node.
## Legacy API
These are methods that support legacy behavior which can optionally be disabled.
* `stairsplus.api.on_place(itemstack, placer, pointed_thing)`
Handle placing a node. The legacy behavior is to try to rotate the node based on a number of
obscure factors. It can be useful sometimes, but confusing other times.
* `stairsplus.api.scale_light(light_source, shape_def)`
How much light should a shaped glowing node emit? The legacy behavior was to drop the value by `1`.
The non-legacy behavior scales it down more, based on the number of eighths used in the shape.
## Crafting API
A powerful new API for registering crafting recipes involving shaped nodes.
* `stairsplus.api.register_on_register_craft_schema(func)`
A callback for when a schema is registered. Useful for integrating inventory managers.
* `stairsplus.api.register_craft_schema(schema)`
Register a crafting schema, which is like a normal crafting recipe, but it may have bare "shapes" in the
recipe/output. For every node w/ the variants in the recipe, a variant involving those shaped nodes
will be created (when all mods are loaded, so you don't have to worry about the order in which things
are defined).
```lua
stairsplus.api.register_craft_schema({
output = "panel_8 6",
recipe = {{"node", "node", "node"}},
})
stairsplus.api.register_craft_schema({
type = "shapeless",
output = "micro_8 7",
recipe = {"stair_inner"},
})
```
* `stairsplus.api.register_schema_crafts_for_node(node)`
Creates real recipes for all schemas using available variants of the given node. Called automatically after all
mods are loaded for every node which has shaped nodes. Currently only supports "shaped" and "shapeless" recipe
types (these don't make much sense for cooking/fuel). Given the schemas above, if called for `"default:stone"`,
this would result in the following:
```lua
minetest.register_craft({
output = "default:panel_stone_8 6",
recipe = {{"default:stone", "default:stone", "default:stone"}},
})
minetest.register_craft({
type = "shapeless",
output = "default:micro_stone_8 7",
recipe = {"default:stair_stone_inner"},
})
```
* `stairsplus.api.register_crafts_for_shapes(recipe)`
For "cooking" and "fuel" recipe types, register scaled variants for all *currently defined*
shaped variants. It is an error to try to register a cooking recipe for nodes with mis-matched shapes.
```lua
stairsplus.api.register_crafts_for_shapes({
type = "cooking",
output = "default:stone",
recipe = "default:cobblestone",
cooktime = 3,
})
stairsplus.api.register_crafts_for_shapes({
type = "fuel",
recipe = "default:coalblock",
burntime = 370,
})
```
which would result in concrete recipes like
```lua
minetest.register_craft({
type = "cooking",
output = "default:panel_stone_8",
recipe = "default:panel_cobblestone_8",
cooktime = 0.75,
})
minetest.register_craft({
type = "fuel",
recipe = "default:panel_coalblock_8",
burntime = 92.5,
})
```
## Station API
For the creation of crafting stations which can be used to transform nodes into shaped variants, and back.
* `stairsplus.api.register_station(name, def)`
* `name`: an itemstring
* `def`: a regular node definition, with the following parameters:
* `shape_groups`: a list of shape groups that can be created with the station (required).
* `build_formspec = function(meta, inv)`
A function which will build the formspec for the node (not required, but you probably want to use it).
* `update_metadata = function(meta, inv)`
A function which updates the node's metadata (optional)
## Compat API
For compatability w/ other mods
* `function stairsplus.compat.override_stairs(name, node, overrides, meta)`
Override stairs registered w/ the `stairs` mod, and replace them w/ stairsplus-compatible nodes.
# Legacy API
Methods from the previous "moreblocks" version of stairsplus which are still provided so as not to break
compatability with older mods.
* `stairsplus:register_all(modname, subname, recipeitem, fields)`
Example:
```lua
stairsplus:register_all("moreblocks", "wood", "default:wood", {
description = "Wooden",
tiles = {"default_wood.png"},
groups = {oddly_breakabe_by_hand=1},
sounds = moreblocks.node_sound_wood_defaults(),
})
```
* `stairsplus:register_micro(modname, subname, recipeitem, fields)`
* `stairsplus:register_panel(modname, subname, recipeitem, fields)`
* `stairsplus:register_slab(modname, subname, recipeitem, fields)`
* `stairsplus:register_slope(modname, subname, recipeitem, fields)`
* `stairsplus:register_stair(modname, subname, recipeitem, fields)`
* `stairsplus:register_custom_subset(subset, modname, subname, recipeitem, fields)`
* `stairsplus:register_alias_all(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_micro(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_panel(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_slab(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_slope(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_stair(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_custom_subset_alias(subset, modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_force_all(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_force_micro(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_force_panel(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_force_slab(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_force_slope(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_alias_force_stair(modname_old, subname_old, modname_new, subname_new)`
* `stairsplus:register_custom_subset_alias(subset, modname_old, subname_old, modname_new, subname_new)`

View file

@ -0,0 +1,2 @@
minetest.register_alias("circular_saw", "stairsplus:circular_saw")
minetest.register_alias("moreblocks:circular_saw", "stairsplus:circular_saw")

View file

@ -0,0 +1,104 @@
local api = stairsplus.api
local table_is_empty = futil.table.is_empty
function api.register_alias_shape(old_node, new_node, shape)
local old_shaped_node = api.format_name(old_node, shape)
local new_shaped_node = api.format_name(new_node, shape)
minetest.register_alias(old_shaped_node, new_shaped_node)
local shape_def = api.registered_shapes[shape]
if shape_def.aliases then
local old_mod, old_name = old_node:match("^([^:]+):(.*)$")
for _, alias in ipairs(shape_def.aliases) do
old_shaped_node = ("%s:%s"):format(old_mod, alias:format(old_name))
minetest.register_alias(old_shaped_node, new_shaped_node)
end
end
end
function api.register_alias_all(old_node, new_node)
for shape in pairs(api.registered_shapes) do
api.register_alias_shape(old_node, new_node, shape)
end
end
function api.register_alias_shapes(old_node, new_node, shapes)
for _, shape in ipairs(shapes) do
api.register_alias_shape(old_node, new_node, shape)
end
end
function api.register_alias_group(old_node, new_node, group)
for _, shape in ipairs(api.shapes_by_group[group] or {}) do
api.register_alias_shape(old_node, new_node, shape)
end
end
function api.register_alias_groups(old_node, new_node, groups)
for _, group in ipairs(groups) do
api.register_alias_group(old_node, new_node, group)
end
end
function api.register_alias_force_shape(old_node, new_node, shape)
local old_shaped_node = api.format_name(old_node, shape)
local new_shaped_node = api.format_name(new_node, shape)
minetest.register_alias_force(old_shaped_node, new_shaped_node)
local shape_def = api.registered_shapes[shape]
if shape_def.aliases then
local old_mod, old_name = old_node:match("^([^:]+):(.*)$")
for _, alias in ipairs(shape_def.aliases) do
old_shaped_node = ("%s:%s"):format(old_mod, alias:format(old_name))
minetest.register_alias_force(old_shaped_node, new_shaped_node)
end
end
local nodes = api.nodes_by_shape[shape] or {}
if nodes[old_node] then
nodes[old_node] = nil
nodes[new_node] = true
api.nodes_by_shape[shape] = nodes
end
local old_shapes = api.shapes_by_node[old_node] or {}
if old_shapes[shape] then
old_shapes[shape] = nil
if table_is_empty(old_shapes) then
api.shapes_by_node[old_node] = nil
else
api.shapes_by_node[old_node] = old_shapes
end
local new_shapes = api.shapes_by_node[new_node] or {}
new_shapes[new_node] = true
api.shapes_by_node[new_node] = new_shapes
end
end
function api.register_alias_force_all(old_node, new_node)
for shape in pairs(api.registered_shapes) do
api.register_alias_force_shape(old_node, new_node, shape)
end
end
function api.register_alias_force_shapes(old_node, new_node, shapes)
for _, shape in ipairs(shapes) do
api.register_alias_force_shape(old_node, new_node, shape)
end
end
function api.register_alias_force_group(old_node, new_node, group)
for _, shape in ipairs(api.shapes_by_group[group] or {}) do
api.register_alias_force_shape(old_node, new_node, shape)
end
end
function api.register_alias_force_groups(old_node, new_node, groups)
for _, group in ipairs(groups) do
api.register_alias_force_group(old_node, new_node, group)
end
end

View file

@ -0,0 +1,66 @@
local api = stairsplus.api
local in_creative_inventory = stairsplus.settings.in_creative_inventory
local in_craft_guide = stairsplus.settings.in_craft_guide
api.passthrough_groups = {}
api.scaling_groups = {}
api.ignore_groups = {}
function api.register_passthrough_group(group)
api.passthrough_groups[group] = true
end
function api.register_passthrough_groups(groups)
for _, group in ipairs(groups) do
api.register_passthrough_group(group)
end
end
function api.register_scaling_group(group)
api.scaling_groups[group] = true
end
function api.register_scaling_groups(groups)
for _, group in ipairs(groups) do
api.register_scaling_group(group)
end
end
function api.register_ignore_group(group)
api.ignore_groups[group] = true
end
function api.register_ignore_groups(groups)
for _, group in ipairs(groups) do
api.register_ignore_group(group)
end
end
function api.build_groups(node, shape)
local node_def = minetest.registered_nodes[node]
local groups = {
[("shape_%s"):format(shape)] = 1,
not_in_creative_inventory = (not in_creative_inventory) and 1 or nil,
not_in_craft_guide = (not in_craft_guide) and 1 or nil,
}
local shape_def = api.registered_shapes[shape]
for group, value in pairs(shape_def.groups or {}) do
groups[group] = value
end
for group, value in pairs(node_def.groups) do
if api.passthrough_groups[group] then
groups[group] = value
elseif api.scaling_groups[group] then
groups[group] = (shape_def.eighths / 8) * value
elseif not api.ignore_groups[group] then
groups[shape_def.name_format:format(group)] = value
end
end
return groups
end

View file

@ -0,0 +1,10 @@
stairsplus.api = {}
stairsplus.dofile("api", "legacy")
stairsplus.dofile("api", "shape")
stairsplus.dofile("api", "group_filters")
stairsplus.dofile("api", "node")
stairsplus.dofile("api", "alias")
stairsplus.dofile("api", "recipe")
stairsplus.dofile("api", "station")
stairsplus.dofile("api", "whitelist")

View file

@ -0,0 +1,107 @@
local api = stairsplus.api
local legacy_mode = stairsplus.settings.legacy_mode
local legacy_place_mechanic = stairsplus.settings.legacy_place_mechanic
if legacy_place_mechanic then
local wall_right_dirmap = { 9, 18, 7, 12 }
local wall_left_dirmap = { 11, 16, 5, 14 }
local ceil_dirmap = { 20, 23, 22, 21 }
function api.on_place(itemstack, placer, pointed_thing)
if not futil.is_player(placer) then
return minetest.item_place(itemstack, placer, pointed_thing)
end
local controls = placer:get_player_control()
local sneak = controls.sneak
local aux = controls.aux1
local shaped_node_name = itemstack:get_name()
local shape = api.get_shape_of_shaped_node(shaped_node_name)
local under = pointed_thing.under
local under_node = minetest.get_node(under)
local under_shape = api.get_shape_of_shaped_node(under_node.name)
local same_cat = shape == under_shape
-- standard (floor) facedir, also used for sneak placement against the lower half of the wall
local p2 = placer and minetest.dir_to_facedir(placer:get_look_dir()) or 0
-- check which face and which quadrant we are interested in
-- this is used both to check if we're handling parallel placement in the same-category case,
-- and in general for sneak placement
local face_pos = minetest.pointed_thing_to_face_pos(placer, pointed_thing)
local face_off = vector.subtract(face_pos, under)
-- we cannot trust face_off to tell us the correct directionif the
-- under node has a non-standard shape, so use the distance between under and above
local wallmounted = minetest.dir_to_wallmounted(vector.subtract(pointed_thing.above, under))
if same_cat and not aux then
p2 = under_node.param2
-- flip if placing above or below an upright or upside-down node
-- TODO should we also flip when placing next to a side-mounted node?
if wallmounted < 2 then
if p2 < 4 then
p2 = (p2 + 2) % 4
p2 = ceil_dirmap[p2 + 1]
elseif p2 > 19 then
p2 = ceil_dirmap[p2 - 19] - 20
p2 = (p2 + 2) % 4
end
end
else
-- for same-cat placement, aux is used to disable param2 copying
if same_cat then
aux = not aux
end
local remap
-- standard placement against the wall
local use_wallmap = (wallmounted > 1 and not sneak) or (wallmounted < 2 and sneak)
-- standard placement against the ceiling, or sneak placement against the upper half of the wall
local use_ceilmap = wallmounted == 1 and not sneak
use_ceilmap = use_ceilmap or (wallmounted > 1 and sneak and face_off.y > 0)
if use_wallmap then
local left = (p2 == 0 and face_off.x < 0)
or (p2 == 1 and face_off.z > 0)
or (p2 == 2 and face_off.x > 0)
or (p2 == 3 and face_off.z < 0)
if aux then
left = not left
end
remap = left and wall_left_dirmap or wall_right_dirmap
elseif use_ceilmap then
remap = ceil_dirmap
end
if aux then
p2 = (p2 + 2) % 4
end
if remap then
p2 = remap[p2 + 1]
end
end
return minetest.item_place(itemstack, placer, pointed_thing, p2)
end
else
api.on_place = minetest.item_place
end
function api.scale_light(light_source, shape_def)
if not light_source or light_source == 0 then
return 0
elseif legacy_mode then
return light_source - 1
end
return math.max(1, math.min(math.round(light_source * shape_def.eighths / 4), light_source))
end

View file

@ -0,0 +1,341 @@
-- for registering variants of a specific node
local api = stairsplus.api
local table_set_all = futil.table.set_all
local table_sort_keys = futil.table.sort_keys
local S = stairsplus.S
local default_align_style = stairsplus.settings.default_align_style
local silence_group_overrides = stairsplus.settings.silence_group_overrides
api.nodes_by_shape = {}
api.shapes_by_node = {}
api.node_by_shaped_node = {}
api.shape_by_shaped_node = {}
api.registered_singles = {}
api.registered_on_register_singles = {}
function api.register_on_register_single(func)
table.insert(api.registered_on_register_singles, func)
end
local function check_node_validity(node_def, meta)
local type_ = node_def.type
if not meta.ignore_type and type_ ~= "node" then
error(("cannot register non-node %q w/ stairsplus"):format(node_def.name))
end
local drawtype = node_def.drawtype
if
not meta.ignore_drawtype
and (
drawtype == "airlike"
or drawtype == "liquid"
or drawtype == "flowingliquid"
or drawtype == "torchlike"
or drawtype == "signlike"
or drawtype == "plantlike"
or drawtype == "firelike"
or drawtype == "fencelike"
or drawtype == "raillike"
or drawtype == "nodebox"
or drawtype == "mesh"
or drawtype == "plantlike_rooted"
)
then
error(("cannot register %q w/ drawtype %q w/ stairsplus"):format(node_def.name, drawtype))
end
local paramtype2 = node_def.paramtype2
if
not meta.ignore_paramtype2
and (
paramtype2 == "flowingliquid"
or paramtype2 == "wallmounted"
or paramtype2 == "leveled"
or paramtype2 == "degrotate"
or paramtype2 == "meshoptions"
or paramtype2 == "color"
or paramtype2 == "colorwallmounted"
or paramtype2 == "glasslikeliquidlevel"
or paramtype2 == "colordegrotate"
)
then
error(("cannot register %q w/ paramtype2 %q w/ stairsplus"):format(node_def.name, paramtype2))
end
end
function api.format_name(node, shape)
local mod, name = node:match("^([^:]+):(.*)$")
local shape_def = api.registered_shapes[shape]
return ("%s:%s"):format(mod, shape_def.name_format:format(name))
end
function api.register_single(node, shape, overrides, meta)
if not minetest.registered_nodes[node] then
error(("node %q is not defined"):format(node))
end
if shape ~= "micro_8" and not (api.nodes_by_shape.micro_8 or {})[node] then
-- always make sure a microblock exists
api.register_single(node, "micro_8", overrides, meta)
end
local shaped_name = api.format_name(node, shape)
if shape ~= "micro_8" and not api.is_whitelisted(shaped_name) then
return false
end
stairsplus.log("info", "registering %s", shaped_name)
meta = meta or {}
overrides = table.copy(overrides or {})
local node_def = table.copy(minetest.registered_nodes[node])
check_node_validity(node_def, meta)
if (api.nodes_by_shape[shape] or {})[node] then
return -- already registered
end
local shape_def = api.registered_shapes[shape]
local paramtype2
if node_def.paramtype2 == "colorfacedir" then
paramtype2 = "colorfacedir"
else
paramtype2 = shape_def.paramtype2 or "facedir"
end
-- shaped_node definition
local def = {
description = S(shape_def.description, node_def.description or node),
drawtype = shape_def.drawtype,
mesh = shape_def.mesh,
node_box = shape_def.node_box,
collision_box = shape_def.collision_box,
selection_box = shape_def.selection_box,
paramtype = shape_def.paramtype or "light",
paramtype2 = paramtype2,
light_source = api.scale_light(node_def.light_source, shape_def),
groups = api.build_groups(node, shape),
tiles = node_def.tiles,
overlay_tiles = node_def.overlay_tiles,
use_texture_alpha = node_def.use_texture_alpha,
color = node_def.color,
palette = node_def.palette, -- for coloredfacedir
stack_max = node_def.stack_max,
sounds = node_def.sounds,
is_ground_content = node_def.is_ground_content,
walkable = node_def.walkable,
pointable = node_def.pointable,
diggable = node_def.diggable,
climbable = node_def.climbable,
move_resistance = node_def.move_resistance,
on_place = function(...)
return api.on_place(...)
end,
}
-- see-through nodes tend to look better if we just use the first tile
if (node_def.drawtype or ""):match("glass") then
if #def.tiles > 1 then
def.tiles = { def.tiles[1] }
end
if def.overlay_tiles and #def.overlay_tiles > 1 then
def.overlay_tiles = { def.overlay_tiles[1] }
end
end
if node_def.short_description then
def.short_description = S(shape_def.description, node_def.short_description)
end
-- if there's a drop defined, and we can drop a shaped version, do so
if meta.legacy_drop then
if type(meta.legacy_drop) == "string" then
local item = api.get_schema_recipe_item(meta.legacy_drop, shape)
if item then
def.drop = item
end
end
elseif node_def.drop and type(node_def.drop) == "string" then
local item = api.get_schema_recipe_item(node_def.drop, shape)
if item then
def.drop = item
end
end
if not silence_group_overrides and not meta.allow_override_groups and overrides.groups then
stairsplus.log(
"warning",
"removing group overrides from %s (was %s, will be %s)",
shaped_name,
minetest.write_json(overrides.groups),
minetest.write_json(def.groups)
)
overrides.groups = nil
end
if not meta.allow_override_drawtype and overrides.drawtype then
stairsplus.log("warning", "removing drawtype override %s from %s", overrides.drawtype, shaped_name)
overrides.drawtype = nil
end
if not meta.allow_override_paramtype2 and overrides.paramtype2 then
stairsplus.log("warning", "removing paramtype2 override %s from %s", overrides.paramtype2, shaped_name)
overrides.paramtype2 = nil
end
table_set_all(def, overrides)
-- set backface_culling and align_style
local align_style = meta.align_style or default_align_style
for i, tile in ipairs(def.tiles) do
if type(tile) == "string" then
def.tiles[i] = {
name = tile,
backface_culling = true,
align_style = align_style,
}
elseif not (tile.animation or tile.color) then
tile.backface_culling = true
tile.align_style = align_style
end
end
if def.overlay_tiles then
for i, tile in ipairs(def.overlay_tiles) do
if type(tile) == "string" then
def.overlay_tiles[i] = {
name = tile,
backface_culling = true,
align_style = align_style,
}
elseif not (tile.animation or tile.color) then
tile.backface_culling = true
tile.align_style = align_style
end
def.overlay_tiles[i] = tile
end
end
-- register node
minetest.register_node(":" .. shaped_name, def)
-- alias old name formats
if shape_def.aliases then
local mod, name = node:match("^([^:]+):(.*)$")
for _, alias in ipairs(shape_def.aliases) do
minetest.register_alias(("%s:%s"):format(mod, alias:format(name)), shaped_name)
end
end
local nodes = api.nodes_by_shape[shape] or {}
nodes[node] = true
api.nodes_by_shape[shape] = nodes
local shapes = api.shapes_by_node[node] or {}
shapes[shape] = true
api.shapes_by_node[node] = shapes
api.node_by_shaped_node[shaped_name] = node
api.shape_by_shaped_node[shaped_name] = shape
api.node_by_shaped_node[node] = node
api.shape_by_shaped_node[node] = "node"
table.insert(api.registered_singles, { node, shaped_name })
for _, func in ipairs(api.registered_on_register_singles) do
func(node, shaped_name)
end
return shaped_name
end
function api.register_all(node, overrides, meta)
for shape in pairs(api.registered_shapes) do
api.register_single(node, shape, overrides, meta)
end
end
function api.register_singles(node, shapes, overrides, meta)
for _, shape in ipairs(shapes) do
api.register_single(node, shape, overrides, meta)
end
end
function api.register_group(node, shape_group, overrides, meta)
for _, shape in ipairs(api.shapes_by_group[shape_group] or {}) do
api.register_single(node, shape, overrides, meta)
end
end
function api.register_groups(node, shape_groups, overrides, meta)
for _, group in ipairs(shape_groups) do
api.register_group(node, group, overrides, meta)
end
end
function api.get_shapes(node)
if api.shapes_by_node[node] then
return table_sort_keys(api.shapes_by_node[node])
end
end
-- warning: don't mutate the return value
function api.get_shapes_hash(node)
return api.shapes_by_node[node]
end
-- turn a recipe item into a shape if possible
function api.get_schema_recipe_item(node, shape_or_item)
if shape_or_item == "" then
return ""
end
local name, count = shape_or_item:match("^([^ ]+) (%d+)")
if not name then
name = shape_or_item
end
count = tonumber(count)
if api.registered_shapes[name] then
name = api.format_name(node, name)
elseif name == "node" then
name = node
elseif not name:match(":") then
return
end
if count then
return ("%s %s"):format(name, count)
else
return name
end
end
function api.get_micronode(node)
return api.get_schema_recipe_item(node, "micro_8")
end
function api.get_node_of_shaped_node(shaped_node)
return api.node_by_shaped_node[shaped_node]
end
function api.get_shape_of_shaped_node(shaped_node)
return api.shape_by_shaped_node[shaped_node]
end

View file

@ -0,0 +1,257 @@
-- for registering recipe schemas
local api = stairsplus.api
api.registered_recipe_schemas = {}
api.registered_on_register_craft_schemas = {}
local registered_schemas_by_node = {}
local function is_valid_output(item, shapes)
local item_name = item:match("^([^ ]+)")
return item_name and (shapes[item_name] or item_name == "node" or item_name:match(":"))
end
local function is_valid_item(item, shapes)
return is_valid_output(item, shapes) or item == ""
end
local function verify_schema(schema)
local problems = {}
if not (schema.type == "shaped" or schema.type == "shapeless" or schema.type == nil) then
table.insert(problems, ("unimplemented schema type %q"):format(schema.type))
end
if not is_valid_output(schema.output, api.registered_shapes) then
table.insert(problems, ("don't know how to handle output %q"):format(schema.output))
end
if schema.replacements then
for _, replacement in ipairs(schema.replacements) do
for _, item in ipairs(replacement) do
if not is_valid_item(item, api.registered_shapes) then
table.insert(problems, ("don't know how to handle replacement item %q"):format(item))
end
end
end
end
if schema.type == "shapeless" then
for _, item in ipairs(schema.recipe) do
if not is_valid_item(item, api.registered_shapes) then
table.insert(problems, ("don't know how to handle craft item %q"):format(item))
end
end
else
for _, row in ipairs(schema.recipe) do
for _, item in ipairs(row) do
if not is_valid_item(item, api.registered_shapes) then
table.insert(problems, ("don't know how to handle craft item %q"):format(item))
end
end
end
end
if #problems > 0 then
return table.concat(problems, ", ")
end
end
local function has_the_right_shapes(schema, shapes)
if not is_valid_output(schema.output, shapes) then
return false
end
if schema.replacements then
for _, replacement in ipairs(schema.replacements) do
for _, item in ipairs(replacement) do
if not is_valid_item(item, shapes) then
return false
end
end
end
end
if schema.type == "shapeless" then
for _, item in ipairs(schema.recipe) do
if not is_valid_item(item, shapes) then
return false
end
end
elseif schema.type == "shaped" or schema.type == nil then
for _, row in ipairs(schema.recipe) do
for _, item in ipairs(row) do
if not is_valid_item(item, shapes) then
return false
end
end
end
end
return true
end
local function register_for_schema(node, schema)
stairsplus.log("verbose", "using schema %s w/ node %s", minetest.write_json(schema), node)
local recipe = table.copy(schema)
recipe.output = api.get_schema_recipe_item(node, recipe.output)
if recipe.replacements then
for _, replacement in ipairs(recipe.replacements) do
for i, item in ipairs(replacement) do
replacement[i] = api.get_schema_recipe_item(node, item)
end
end
end
if recipe.type == "shapeless" then
for i, item in ipairs(recipe.recipe) do
recipe.recipe[i] = api.get_schema_recipe_item(node, item)
end
elseif recipe.type == "shaped" or recipe.type == nil then
for _, row in ipairs(recipe.recipe) do
for i, item in ipairs(row) do
row[i] = api.get_schema_recipe_item(node, item)
end
end
end
stairsplus.log("info", "registering recipe %s", minetest.write_json(recipe))
minetest.register_craft(recipe)
end
function api.register_on_register_craft_schema(func)
table.insert(api.registered_on_register_craft_schemas, func)
end
function api.register_craft_schema(schema)
local problems = verify_schema(schema)
if problems then
error(problems)
end
stairsplus.log("info", "registering craft schema %s", minetest.write_json(schema))
table.insert(api.registered_recipe_schemas, schema)
for node, shapes in pairs(api.shapes_by_node) do
local registered_schemas = registered_schemas_by_node[node] or {}
if has_the_right_shapes(schema, shapes) and not registered_schemas[schema] then
register_for_schema(node, schema)
registered_schemas[schema] = true
end
registered_schemas_by_node[node] = registered_schemas
end
for _, func in ipairs(api.registered_on_register_craft_schemas) do
func(schema)
end
end
function api.register_schema_crafts_for_node(node)
local registered_schemas = registered_schemas_by_node[node] or {}
local shapes = api.get_shapes_hash(node)
for _, schema in ipairs(api.registered_recipe_schemas) do
if has_the_right_shapes(schema, shapes) and not registered_schemas[schema] then
register_for_schema(node, schema)
registered_schemas[schema] = true
end
end
registered_schemas_by_node[node] = registered_schemas
end
api.register_on_register_single(api.register_schema_crafts_for_node)
local function get_shape_intersection(a, b)
local a_shapes = api.get_shapes(a)
local b_shapes = api.get_shapes(b)
local filter = {}
for i = 1, #b_shapes do
filter[b_shapes[i]] = true
end
local intersection = {}
for i = 1, #a_shapes do
local shape = a_shapes[i]
if filter[shape] then
intersection[#intersection + 1] = shape
end
end
return intersection
end
local function register_cooking_for_shapes(recipe)
local mod, name = recipe.recipe:match("^([^:]+):(.*)$")
local shapes
if mod == "group" then
shapes = api.get_shapes(recipe.output)
else
shapes = get_shape_intersection(recipe.output, recipe.recipe)
end
for _, shape in ipairs(shapes) do
local shape_def = api.registered_shapes[shape]
local input
if mod == "group" then
input = "group:" .. shape_def.name_format:format(name)
else
input = api.get_schema_recipe_item(recipe.recipe, shape)
end
minetest.register_craft({
type = "cooking",
output = api.get_schema_recipe_item(recipe.output, shape),
recipe = input,
cooktime = math.max(1.0, (recipe.cooktime or 3) * (shape_def.eighths / 8)),
})
end
end
local function register_fuel_for_shapes(recipe)
local mod, name = recipe.recipe:match("^([^:]+):(.*)$")
local shapes
if mod == "group" then
shapes = api.registered_shapes
else
shapes = api.get_shapes_hash(recipe.recipe)
end
if not shapes then
error(("don't know how to handle fuel %s"):format(recipe.recipe))
end
for shape in pairs(shapes) do
local shape_def = api.registered_shapes[shape]
local input
if mod == "group" then
input = "group:" .. shape_def.name_format:format(name)
else
input = api.get_schema_recipe_item(recipe.recipe, shape)
end
minetest.register_craft({
type = "fuel",
recipe = input,
burntime = math.max(1.0, (recipe.burntime or 1) * (shape_def.eighths / 8)),
})
end
end
function api.register_crafts_for_shapes(recipe)
if recipe.type == "cooking" then
register_cooking_for_shapes(recipe)
elseif recipe.type == "fuel" then
register_fuel_for_shapes(recipe)
else
error(("unsupported recipe type %s"):format(recipe.type))
end
end

View file

@ -0,0 +1,48 @@
local f = string.format
local api = stairsplus.api
api.registered_on_register_shapes = {}
api.registered_shapes = {}
api.shapes_by_group = {}
function api.register_on_register_shape(func)
table.insert(api.registered_on_register_shapes, func)
end
function api.register_shape(name, def)
stairsplus.log("info", "registering shape %q", name)
def.shape_groups = def.shape_groups or {}
api.registered_shapes[name] = def
for group in pairs(def.shape_groups) do
local shapes = api.shapes_by_group[group] or {}
table.insert(shapes, name)
api.shapes_by_group[group] = shapes
end
for _, func in ipairs(api.registered_on_register_shapes) do
func(name, def)
end
end
function api.register_shape_group(shape_group, shapes)
for _, shape in ipairs(shapes) do
api.registered_shapes[shape].shape_groups[shape_group] = 1
end
api.shapes_by_group[shape_group] = shapes
end
function api.guess_shape(node_name)
local mod, namepart = node_name:match("^([^:]+):([^:]+)$")
if not (mod and namepart) then
return
end
for name, def in pairs(api.registered_shapes) do
local pattern = def.name_format:gsub("%%s", "(.*)")
local matched = namepart:match(pattern)
if matched and minetest.registered_nodes[f("%s:%s", mod, matched)] then
return name
end
end
end

View file

@ -0,0 +1,357 @@
-- for creating the circular saw and similar nodes
local api = stairsplus.api
local item_has_metadata = stairsplus.util.item_has_metadata
local resolve_itemstack = futil.resolve_itemstack
local default_stack_max = tonumber(minetest.settings:get("default_stack_max")) or 99
local station = {}
function station.get_cost(shaped_node)
if shaped_node == "" then
return 0
end
local shape = api.get_shape_of_shaped_node(shaped_node)
if shape == "node" then
return 8
end
local shape_def = api.registered_shapes[shape]
return shape_def and shape_def.eighths
end
function station.get_current_node(inv)
local input_stack = inv:get_stack("stairsplus:input", 1)
if not input_stack:is_empty() then
return api.get_node_of_shaped_node(input_stack:get_name())
end
local micro_stack = inv:get_stack("stairsplus:micro", 1)
if not micro_stack:is_empty() then
return api.get_node_of_shaped_node(micro_stack:get_name())
end
local recycle_stack = inv:get_stack("stairsplus:recycle", 1)
if not recycle_stack:is_empty() then
return api.get_node_of_shaped_node(recycle_stack:get_name())
end
end
function station.can_dig(meta, inv)
return inv:is_empty("stairsplus:input") and inv:is_empty("stairsplus:micro")
end
function station.on_receive_fields(meta, inv, formname, fields, sender, build_formspec, update_metadata)
local max = tonumber(fields.max_offered)
if max and max > 0 then
meta:set_int("stairsplus:max_offered", max)
-- Update to show the correct number of items:
station.update_inventory(meta, inv)
if update_metadata then
update_metadata(meta, inv)
end
if build_formspec then
meta:set_string("formspec", build_formspec(meta, inv))
end
end
return not not fields.max_offered
end
local function fix_aliases(inv)
local input = inv:get_stack("stairsplus:input", 1)
input:set_name(resolve_itemstack(input):get_name())
inv:set_stack("stairsplus:input", 1, input)
local micro = inv:get_stack("stairsplus:micro", 1)
micro:set_name(resolve_itemstack(micro):get_name())
inv:set_stack("stairsplus:micro", 1, micro)
local recycle = inv:get_stack("stairsplus:recycle", 1)
recycle:set_name(resolve_itemstack(recycle):get_name())
inv:set_stack("stairsplus:recycle", 1, recycle)
for i = 1, inv:get_size("stairsplus:output") do
local output = inv:get_stack("stairsplus:output", i)
output:set_name(resolve_itemstack(output):get_name())
inv:set_stack("stairsplus:output", i, output)
end
end
function station.update_inventory(meta, inv, taken_stack)
fix_aliases(inv)
local node = station.get_current_node(inv)
local valid_shapes = api.shapes_by_node[node]
if not (node and valid_shapes) then
inv:set_stack("stairsplus:input", 1, ItemStack())
inv:set_stack("stairsplus:micro", 1, ItemStack())
inv:set_stack("stairsplus:recycle", 1, ItemStack())
for i = 1, inv:get_size("stairsplus:output") do
inv:set_stack("stairsplus:output", i, ItemStack())
end
return
end
local input_stack = inv:get_stack("stairsplus:input", 1)
local micro_stack = inv:get_stack("stairsplus:micro", 1)
local recycle_stack = inv:get_stack("stairsplus:recycle", 1)
local input_cost = station.get_cost(input_stack:get_name())
local micro_cost = station.get_cost(micro_stack:get_name())
local recycle_cost = station.get_cost(recycle_stack:get_name())
local total_value = (
(input_stack:get_count() * input_cost)
+ (micro_stack:get_count() * micro_cost)
+ (recycle_stack:get_count() * recycle_cost)
)
if taken_stack then
total_value = total_value - station.get_cost(taken_stack:get_name()) * taken_stack:get_count()
end
local new_micros = total_value % 8
local new_blocks = math.floor(total_value / 8)
local micronode = api.get_micronode(node)
inv:set_stack("stairsplus:input", 1, ItemStack({ name = node, count = new_blocks }))
inv:set_stack("stairsplus:micro", 1, ItemStack({ name = micronode, count = new_micros }))
inv:set_stack("stairsplus:recycle", 1, ItemStack())
if total_value == 0 then
for i = 1, inv:get_size("stairsplus:output") do
inv:set_stack("stairsplus:output", i, "")
end
return
end
local max_offered = meta:get_int("stairsplus:max_offered")
local shape_groups = minetest.parse_json(meta:get_string("stairsplus:shape_groups"))
local i = 1
for _, group in ipairs(shape_groups) do
for _, shape in ipairs(api.shapes_by_group[group]) do
if valid_shapes[shape] then
local shape_def = api.registered_shapes[shape]
local shaped_node = api.format_name(node, shape)
local stack_max = math.min(max_offered, ItemStack(shaped_node):get_stack_max())
local count = math.min(stack_max, math.floor(total_value / shape_def.eighths))
local stack
if count > 0 then
stack = ItemStack({ name = shaped_node, count = count })
else
stack = ""
end
inv:set_stack("stairsplus:output", i, stack)
i = i + 1
end
end
end
for j = i, inv:get_size("stairsplus:output") do
inv:set_stack("stairsplus:output", j, "")
end
end
-- Moving the inventory of the station around is not allowed because it
-- is a fictional inventory. Moving inventory around would be rather
-- impractical and make things more difficult to calculate:
function station.allow_inventory_move(meta, inv, from_list, from_index, to_list, to_index, count, player)
return 0
end
function station.allow_inventory_put(meta, inv, listname, index, stack, player)
if listname == "stairsplus:output" then
return 0
end
if item_has_metadata(stack) then
return 0
end
local to_put_name = resolve_itemstack(stack):get_name()
local node = api.get_node_of_shaped_node(to_put_name)
local shape = api.get_shape_of_shaped_node(to_put_name)
if not (node and shape) then
return 0
end
local current_node = station.get_current_node(inv)
local input_stack = inv:get_stack("stairsplus:input", 1)
local micro_stack = inv:get_stack("stairsplus:micro", 1)
if current_node and node ~= current_node then
if
(input_stack:is_empty() and listname == "stairsplus:micro")
or (micro_stack:is_empty() and listname == "stairsplus:input")
then
return stack:get_count()
else
return 0
end
end
local count = stack:get_count()
local cost = station.get_cost(to_put_name)
local current_value = 8 * input_stack:get_count() + micro_stack:get_count()
local max_value = 8 * ItemStack(node):get_stack_max() + 7
local available_value = max_value - current_value
local available_count = math.floor(available_value / cost)
return math.min(count, available_count)
end
function station.on_inventory_put(meta, inv, listname, index, stack, player, update_metadata)
station.update_inventory(meta, inv)
if update_metadata then
update_metadata(meta, inv)
end
end
function station.on_inventory_take(meta, inv, listname, index, stack, player, update_metadata)
if listname == "stairsplus:output" then
station.update_inventory(meta, inv, stack)
else
station.update_inventory(meta, inv)
end
if update_metadata then
update_metadata(meta, inv)
end
end
function station.initialize_metadata(meta, inv, shape_groups, build_formspec, update_metadata)
meta:set_string("stairsplus:shape_groups", minetest.write_json(shape_groups))
if meta:get_int("max_offered") ~= 0 then
meta:set_int("stairsplus:max_offered", meta:get_int("max_offered"))
elseif meta:get_int("stairsplus:max_offered") == 0 then
meta:set_int("stairsplus:max_offered", default_stack_max)
end
if build_formspec then
meta:set_string("formspec", build_formspec(meta, inv))
end
if update_metadata then
update_metadata(meta, inv)
end
end
function station.initialize_inventory(inv, shape_groups)
local output_size = 0
for _, group in ipairs(shape_groups) do
output_size = output_size + #api.shapes_by_group[group]
end
inv:set_size("stairsplus:input", 1)
inv:set_size("stairsplus:micro", 1)
inv:set_size("stairsplus:recycle", 1)
inv:set_size("stairsplus:output", output_size)
-- get rid of old lists
for _, listname in ipairs({ "input", "micro", "recycle", "output" }) do
if inv:get_size(listname) > 0 then
inv:set_list(("stairsplus:%s"):format(listname), inv:get_list(listname))
inv:set_size(listname, 0)
end
end
end
function station.on_construct(pos, shape_groups, build_formspec, update_metadata)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
station.initialize_inventory(inv, shape_groups)
station.initialize_metadata(meta, inv, shape_groups, build_formspec, update_metadata)
station.update_inventory(meta, inv)
end
function station.after_place_node(pos, placer)
local meta = minetest.get_meta(pos)
if futil.is_player(placer) then
meta:set_string("owner", placer:get_player_name())
end
end
function api.register_station(name, def)
local shape_groups = def.shape_groups
local build_formspec = def.build_formspec
local update_metadata = def.update_metadata
if not shape_groups then
error("station requires shape_groups defined")
end
def.shape_groups = nil
def.build_formspec = nil
def.update_metadata = nil
def.after_place_node = def.after_place_node or station.after_place_node
def.on_construct = def.on_construct
or function(pos)
return station.on_construct(pos, shape_groups, build_formspec, update_metadata)
end
def.can_dig = def.can_dig
or function(pos, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return station.can_dig(meta, inv, player)
end
def.on_receive_fields = def.on_receive_fields
or function(pos, formname, fields, sender)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return station.on_receive_fields(meta, inv, formname, fields, sender, build_formspec, update_metadata)
end
def.allow_metadata_inventory_move = def.allow_metadata_inventory_move
or function(pos, from_list, from_index, to_list, to_index, count, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return station.allow_inventory_move(meta, inv, from_list, from_index, to_list, to_index, count, player)
end
def.allow_metadata_inventory_put = def.allow_metadata_inventory_put
or function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return station.allow_inventory_put(meta, inv, listname, index, stack, player)
end
def.on_metadata_inventory_put = def.on_metadata_inventory_put
or function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return station.on_inventory_put(meta, inv, listname, index, stack, player, update_metadata)
end
def.on_metadata_inventory_take = def.on_metadata_inventory_take
or function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return station.on_inventory_take(meta, inv, listname, index, stack, player, update_metadata)
end
def._stairsplus_shape_groups = shape_groups
def.groups = table.copy(def.groups or {})
def.groups.stairsplus_station = 1
minetest.register_node(name, def)
end
api.station = station

View file

@ -0,0 +1,25 @@
local f = string.format
local s = stairsplus.settings
local api = stairsplus.api
if s.whitelist_mode then
api.whitelisted = {}
local filename = futil.path_concat(minetest.get_worldpath(), "stairsplus.whitelist")
local contents = futil.load_file(filename)
if not contents then
error(f("error initializing stairsplus whitelist: %s does not exist", filename))
end
local items = contents:split("\n")
for i = 1, #items do
api.whitelisted[items[i]] = true
end
stairsplus.log("action", f("%s nodes whitelisted", #api.whitelisted))
end
function api.is_whitelisted(name)
if not s.whitelist_mode then
return true
end
return api.whitelisted[name]
end

View file

@ -0,0 +1,132 @@
stairsplus.api.circular_saw = {}
local circular_saw = stairsplus.api.circular_saw
local api = stairsplus.api
local station = api.station
local get_location_string = futil.get_location_string
local S = stairsplus.S
local F = minetest.formspec_escape
local formspec_style = stairsplus.resources.formspec_style
function circular_saw.build_formspec(meta, inv)
local inv_location = get_location_string(inv)
return ([[
size[10,11]
%s
label[0,0;]
label[0,0;%s]
list[%s;stairsplus:input;1.7,0;1,1;]
label[0,1;%s]
list[%s;stairsplus:micro;1.7,1;1,1;]
label[0,2;%s]
list[%s;stairsplus:recycle;1.7,2;1,1;]
field[0.3,3.5;1,1;max_offered;%s:;%i]
button[1,3.2;1.7,1;Set;%s]
list[%s;stairsplus:output;2.8,0;7,7;]
list[current_player;main;1.5,7.25;8,4;]
listring[%s;stairsplus:output]
listring[current_player;main]
listring[%s;stairsplus:recycle]
listring[%s;stairsplus:micro]
listring[current_player;main]
listring[%s;stairsplus:input]
listring[current_player;main]
]]):format(
formspec_style,
F(S("Nodes")),
inv_location,
F(S("Microblocks")),
inv_location,
F(S("Input")),
inv_location,
F(S("Max")),
meta:get_int("stairsplus:max_offered"),
F(S("Set")),
inv_location,
inv_location,
inv_location,
inv_location,
inv_location
)
end
function circular_saw.update_metadata(meta, inv)
local parts = { S("Circular Saw") }
local owner = meta:get_string("owner")
if owner ~= "" then
table.insert(parts, S("(owned by @1)", owner))
end
local working_on = station.get_current_node(inv)
if working_on then
local stack = ItemStack(working_on)
table.insert(parts, S("Working on @1", stack:get_short_description() or stack:get_description()))
else
table.insert(parts, S("Empty"))
end
meta:set_string("infotext", table.concat(parts, " "))
end
api.register_station("stairsplus:circular_saw", {
description = S("Circular Saw"),
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.4, -0.5, -0.4, -0.25, 0.25, -0.25 }, -- Leg
{ 0.25, -0.5, 0.25, 0.4, 0.25, 0.4 }, -- Leg
{ -0.4, -0.5, 0.25, -0.25, 0.25, 0.4 }, -- Leg
{ 0.25, -0.5, -0.4, 0.4, 0.25, -0.25 }, -- Leg
{ -0.5, 0.25, -0.5, 0.5, 0.375, 0.5 }, -- Tabletop
{ -0.01, 0.4375, -0.125, 0.01, 0.5, 0.125 }, -- Saw blade (top)
{ -0.01, 0.375, -0.1875, 0.01, 0.4375, 0.1875 }, -- Saw blade (bottom)
{ -0.25, -0.0625, -0.25, 0.25, 0.25, 0.25 }, -- Motor case
},
},
tiles = {
"stairsplus_circular_saw_top.png",
"stairsplus_circular_saw_bottom.png",
"stairsplus_circular_saw_side.png",
},
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
groups = { choppy = 2, oddly_breakable_by_hand = 2 },
sounds = stairsplus.resources.sounds.wood,
shape_groups = { "legacy" },
build_formspec = circular_saw.build_formspec,
update_metadata = circular_saw.update_metadata,
})
local cm = stairsplus.resources.craft_materials
if stairsplus.settings.circular_saw_crafting and cm.steel_ingot then
minetest.register_craft({
output = "stairsplus:circular_saw",
recipe = {
{ "", cm.steel_ingot, "" },
{ "group:wood", "group:wood", "group:wood" },
{ "group:wood", "", "group:wood" },
},
})
end
minetest.register_lbm({
label = "Upgrade legacy saws",
name = "stairsplus:replace_legacy_saws",
nodenames = { "stairsplus:circular_saw" },
run_at_every_load = false,
action = function(pos, node)
local def = minetest.registered_nodes[node.name]
def.on_construct(pos, { "legacy" }, circular_saw.build_formspec, circular_saw.update_metadata)
end,
})

View file

@ -0,0 +1,43 @@
-- luacheck: globals i3
if not stairsplus.has.i3 then
return
end
-- https://github.com/fluxionary/minetest-moreblocks/issues/13
-- remove i3's assumption that it controls our compression groups
for node in pairs(i3.compress_groups) do
if node:match("^moreblocks:slope_") or node:match("^wool:slope_") then
i3.compress_groups[node] = nil
end
end
local api = stairsplus.api
i3.register_craft_type("stairsplus:circular_saw", {
description = "Stairs+ circular saw",
icon = "stairsplus_saw_button.png",
})
local function on_register_single(node, shaped_name)
i3.register_craft({
type = "stairsplus:circular_saw",
result = shaped_name,
items = { node },
})
local micronode = api.get_micronode(node)
if shaped_name ~= micronode then
local compress_groups = i3.compress_groups[micronode] or {}
table.insert(compress_groups, shaped_name)
i3.compress_groups[micronode] = compress_groups
i3.compressed[shaped_name] = true
end
end
for _, single in ipairs(api.registered_singles) do
local node, shaped_name = unpack(single)
on_register_single(node, shaped_name)
end
api.register_on_register_single(on_register_single)

View file

@ -0,0 +1,19 @@
stairsplus.compat = {
is_legacy_drawtype = function(node)
local def = minetest.registered_nodes[node]
return (def.drawtype == "mesh" or def.drawtype == "plantlike" or def.drawtype == "nodebox")
end,
is_legacy_paramtype2 = function(node)
local def = minetest.registered_nodes[node]
return (
def.paramtype2 == "color"
or def.paramtype2 == "colorwallmounted"
or def.paramtype2 == "glasslikeliquidlevel"
)
end,
}
stairsplus.dofile("compat", "i3")
stairsplus.dofile("compat", "unified_inventory")
stairsplus.dofile("compat", "old_moreblocks")

View file

@ -0,0 +1,251 @@
-- legacy: export old API for mods which depend on it
-- provide a configuration option to *disable* legacy. it must be enabled by default, to prevent breaking
-- existing servers
local api = stairsplus.api
local is_legacy_drawtype = stairsplus.compat.is_legacy_drawtype
local is_legacy_paramtype2 = stairsplus.compat.is_legacy_paramtype2
local legacy_mode = stairsplus.settings.legacy_mode
local function clean_legacy_fields(fields)
fields = table.copy(fields) or {}
fields.drawtype = nil
fields.light_source = nil
fields.inventory_image = nil
fields.inventory_overlay = nil
fields.wield_image = nil
fields.wield_overlay = nil
fields.wield_scale = nil
fields.tool_capabilities = nil
fields.node_placement_prediction = nil
fields.node_dig_prediction = nil
fields.on_place = nil
fields.on_secondary_use = nil
fields.on_drop = nil
fields.on_use = nil
fields.after_use = nil
fields.paramtype2 = nil
fields.node_box = nil
fields.mesh = nil
fields.connects_to = nil
fields.connect_sides = nil
fields.selection_box = nil
fields.collision_box = nil
fields.legacy_facedir_simple = nil
fields.legacy_wallmounted = nil
fields.drop = nil
fields.on_construct = nil
fields.on_destruct = nil
fields.after_destruct = nil
fields.after_place_node = nil
fields.after_dig_node = nil
fields.can_dig = nil
fields.on_punch = nil
fields.on_rightclick = nil
fields.on_dig = nil
fields.on_timer = nil
fields.on_receive_fields = nil
return fields
end
local function handle_legacy_drop(modname, drop)
if not drop then
return
end
if type(drop) == "table" then
return drop
else
return ("%s:%s"):format(modname, drop)
end
end
local function register_group(modname, subname, recipeitem, fields, group)
if not minetest.registered_nodes[recipeitem] then
error(("cannot register stairs for %s before the node is defined"):format(recipeitem))
end
local meta = {}
meta.legacy_drop = handle_legacy_drop(modname, fields.drop)
if is_legacy_drawtype(recipeitem) then
meta.ignore_drawtype = true
end
if is_legacy_paramtype2(recipeitem) then
meta.ignore_paramtype2 = true
end
fields = clean_legacy_fields(fields)
api.register_group(recipeitem, group, fields, meta)
local old_name = ("%s:%s"):format(modname, subname)
if old_name ~= recipeitem then
api.register_alias_group(old_name, recipeitem, group)
end
end
function stairsplus:register_all(modname, subname, recipeitem, fields)
if legacy_mode then
register_group(modname, subname, recipeitem, fields, "legacy")
else
register_group(modname, subname, recipeitem, fields, "common")
end
end
function stairsplus:register_micro(modname, subname, recipeitem, fields)
register_group(modname, subname, recipeitem, fields, "micro")
end
function stairsplus:register_panel(modname, subname, recipeitem, fields)
register_group(modname, subname, recipeitem, fields, "panel")
end
function stairsplus:register_slab(modname, subname, recipeitem, fields)
register_group(modname, subname, recipeitem, fields, "slab")
end
function stairsplus:register_slope(modname, subname, recipeitem, fields)
register_group(modname, subname, recipeitem, fields, "slope")
end
function stairsplus:register_stair(modname, subname, recipeitem, fields)
register_group(modname, subname, recipeitem, fields, "stair")
end
local fix_shape_table = {
micro = "micro_8",
panel = "panel_8",
slab = "slab_8",
slab_quarter = "slab_4",
slab_three_quarter = "slab_12",
stair_alt = "stair_alt_8",
}
local function interpret_subset(subset)
local shapes = {}
for _, v in ipairs(subset) do
local shape = table.concat(v, "")
shape = fix_shape_table[shape] or shape
table.insert(shapes, shape)
end
return shapes
end
function stairsplus:register_custom_subset(subset, modname, subname, recipeitem, fields)
local shapes = interpret_subset(subset)
fields = clean_legacy_fields(fields)
local meta = {}
if is_legacy_drawtype(recipeitem) then
meta.ignore_drawtype = true
end
api.register_singles(recipeitem, shapes, fields, meta)
local old_name = ("%s:%s"):format(modname, subname)
if old_name ~= recipeitem then
api.register_alias_shapes(old_name, recipeitem, shapes)
end
end
function stairsplus:register_alias_all(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
if legacy_mode then
api.register_alias_group(old_node, new_node, "legacy")
else
api.register_alias_group(old_node, new_node, "common")
end
end
function stairsplus:register_alias_micro(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_group(old_node, new_node, "micro")
end
function stairsplus:register_alias_panel(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_group(old_node, new_node, "panel")
end
function stairsplus:register_alias_slab(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_group(old_node, new_node, "slab")
end
function stairsplus:register_alias_slope(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_group(old_node, new_node, "slope")
end
function stairsplus:register_alias_stair(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_group(old_node, new_node, "stair")
end
function stairsplus:register_custom_subset_alias(subset, modname_old, subname_old, modname_new, subname_new)
local shapes = interpret_subset(subset)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_shapes(old_node, new_node, shapes)
end
function stairsplus:register_alias_force_all(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
if legacy_mode then
api.register_alias_force_group(old_node, new_node, "legacy")
else
api.register_alias_force_group(old_node, new_node, "common")
end
end
function stairsplus:register_alias_force_micro(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_force_group(old_node, new_node, "micro")
end
function stairsplus:register_alias_force_panel(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_force_group(old_node, new_node, "panel")
end
function stairsplus:register_alias_force_slab(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_force_group(old_node, new_node, "slab")
end
function stairsplus:register_alias_force_slope(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_force_group(old_node, new_node, "slope")
end
function stairsplus:register_alias_force_stair(modname_old, subname_old, modname_new, subname_new)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_force_group(old_node, new_node, "stair")
end
function stairsplus:register_custom_subset_alias(subset, modname_old, subname_old, modname_new, subname_new)
local shapes = interpret_subset(subset)
local old_node = ("%s:%s"):format(modname_old, subname_old)
local new_node = ("%s:%s"):format(modname_new, subname_new)
api.register_alias_force_shapes(old_node, new_node, shapes)
end

View file

@ -0,0 +1,54 @@
-- luacheck: read globals unified_inventory
if not stairsplus.has.unified_inventory then
return
end
local in_creative_inventory = stairsplus.settings.in_creative_inventory
local api = stairsplus.api
unified_inventory.register_craft_type("stairsplus:circular_saw", {
description = "Stairs+ circular saw",
icon = "stairsplus_saw_button.png",
width = 1,
height = 1,
uses_crafting_grid = false,
})
unified_inventory.register_category("stairsplus:cuttable", {
symbol = "stairsplus:circular_saw",
label = "Cuttable in the circular saw",
index = 0,
items = {},
})
if in_creative_inventory then
unified_inventory.register_category("stairsplus:cut_node", {
symbol = "stairsplus:circular_saw",
label = "Nodes cut in the circular saw",
index = 0,
items = {},
})
end
local function on_register_single(node, shaped_name)
unified_inventory.register_craft({
output = shaped_name,
type = "stairsplus:circular_saw",
items = { node },
width = 1,
})
unified_inventory.add_category_item("stairsplus:cuttable", node)
if in_creative_inventory then
unified_inventory.add_category_item("stairsplus:cut_node", shaped_name)
end
end
for _, single in ipairs(api.registered_singles) do
local node, shaped_name = unpack(single)
on_register_single(node, shaped_name)
end
api.register_on_register_single(on_register_single)

View file

@ -0,0 +1,5 @@
stairsplus.dofile("craft_schemas", "micro_8_all")
stairsplus.dofile("craft_schemas", "other")
stairsplus.dofile("craft_schemas", "slopes")
stairsplus.dofile("craft_schemas", "stairs")
stairsplus.dofile("craft_schemas", "standard_composition")

View file

@ -0,0 +1,12 @@
local api = stairsplus.api
local register_craft_schema = api.register_craft_schema
for shape, shape_def in pairs(api.registered_shapes) do
if shape ~= "micro_8" and math.floor(shape_def.eighths) > 0 then
register_craft_schema({
type = "shapeless",
output = ("micro_8 %i"):format(shape_def.eighths),
recipe = { shape },
})
end
end

View file

@ -0,0 +1,84 @@
local api = stairsplus.api
local register_craft_schema = api.register_craft_schema
---- panel_8
register_craft_schema({
output = "panel_8 12",
recipe = {
{ "node", "" },
{ "node", "node" },
},
})
register_craft_schema({
output = "panel_8 12",
recipe = {
{ "", "node" },
{ "node", "node" },
},
})
-- slabs
register_craft_schema({
type = "shapeless",
output = "slab_8",
recipe = { "micro_8", "micro_8", "micro_8", "micro_8" },
})
register_craft_schema({
type = "shapeless",
output = "slab_8",
recipe = { "slab_2", "slab_2", "slab_2", "slab_2" },
})
register_craft_schema({
type = "shapeless",
output = "slab_8 2",
recipe = { "slab_1", "slab_1", "slab_1", "slab_1", "slab_1", "slab_1", "slab_1", "slab_1" },
})
register_craft_schema({
type = "shapeless",
output = "slab_4 2",
recipe = { "slab_1", "slab_1", "slab_1", "slab_1" },
})
register_craft_schema({
type = "shapeless",
output = "slab_12",
recipe = { "slab_2", "slab_2", "slab_2", "slab_2", "slab_2", "slab_2" },
})
register_craft_schema({
type = "shapeless",
output = "slab_14",
recipe = { "slab_2", "slab_2", "slab_2", "slab_2", "slab_2", "slab_2", "slab_2" },
})
-- node
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "micro_8", "micro_8", "micro_8", "micro_8", "micro_8", "micro_8", "micro_8", "micro_8" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "panel_8", "panel_8", "panel_8", "panel_8" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slab_4", "slab_4", "slab_4", "slab_4" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slab_2", "slab_2", "slab_2", "slab_2", "slab_2", "slab_2", "slab_2", "slab_2" },
})

View file

@ -0,0 +1,106 @@
local api = stairsplus.api
local register_craft_schema = api.register_craft_schema
-- slopes
register_craft_schema({
type = "shapeless",
output = "slab_8",
recipe = { "slope_half", "slope_half" },
})
register_craft_schema({
type = "shapeless",
output = "slab_8",
recipe = { "slope_outer_half", "slope_inner_half" },
})
register_craft_schema({
type = "shapeless",
output = "slab_8",
recipe = { "slope_outer_cut_half", "slope_inner_cut_half" },
})
register_craft_schema({
type = "shapeless",
output = "slope_half_raised",
recipe = { "slope_half", "slope_half", "slope_half" },
})
register_craft_schema({
type = "shapeless",
output = "slope_half_raised",
recipe = { "slab_8", "slope_half" },
})
register_craft_schema({
type = "shapeless",
output = "slope_inner_half_raised",
recipe = { "slab_8", "slope_inner_half" },
})
register_craft_schema({
type = "shapeless",
output = "slope_outer_half_raised",
recipe = { "slab_8", "slope_outer_half" },
})
register_craft_schema({
type = "shapeless",
output = "slope_inner_cut_half_raised",
recipe = { "slab_8", "slope_inner_cut_half" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope", "slope" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_half", "slope_half_raised" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_half", "slope_half", "slope_half", "slope_half" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_outer", "slope_inner" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_outer_half", "slope_inner_half_raised" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_outer_half_raised", "slope_inner_half" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_outer_cut", "slope_inner_cut" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_outer_cut_half", "slope_inner_cut_half_raised" },
})
register_craft_schema({
type = "shapeless",
output = "node",
recipe = { "slope_cut", "slope_cut" },
})

View file

@ -0,0 +1,150 @@
local api = stairsplus.api
local register_craft_schema = api.register_craft_schema
register_craft_schema({
type = "shapeless",
output = "slab_8 3",
recipe = { "stair", "stair" },
})
register_craft_schema({
type = "shapeless",
output = "node 3",
recipe = { "stair", "stair", "stair", "stair" },
})
register_craft_schema({
output = "stair 8",
recipe = {
{ "node", "", "" },
{ "node", "node", "" },
{ "node", "node", "node" },
},
})
register_craft_schema({
output = "stair 8",
recipe = {
{ "", "", "node" },
{ "", "node", "node" },
{ "node", "node", "node" },
},
})
register_craft_schema({
type = "shapeless",
output = "stair",
recipe = { "panel_8", "slab_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair",
recipe = { "panel_8", "panel_8", "panel_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair",
recipe = { "micro_8", "micro_8", "micro_8", "micro_8", "micro_8", "micro_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair",
recipe = { "panel_8", "panel_8", "panel_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair_inner",
recipe = { "micro_8", "micro_8", "micro_8", "micro_8", "micro_8", "micro_8", "micro_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair_outer",
recipe = { "micro_8", "slab_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair_outer",
recipe = { "micro_8", "micro_8", "micro_8", "micro_8", "micro_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair_half",
recipe = { "micro_8", "micro_8", "micro_8" },
})
register_craft_schema({
type = "shapeless",
output = "stair_half",
recipe = { "panel_8", "micro_8" },
})
register_craft_schema({
output = "stair_alt_1",
recipe = {
{ "panel_1", "" },
{ "", "panel_1" },
},
})
register_craft_schema({
output = "stair_alt_1",
recipe = {
{ "", "panel_1" },
{ "panel_1", "" },
},
})
register_craft_schema({
output = "stair_alt_2",
recipe = {
{ "panel_2", "" },
{ "", "panel_2" },
},
})
register_craft_schema({
output = "stair_alt_2",
recipe = {
{ "", "panel_2" },
{ "panel_2", "" },
},
})
register_craft_schema({
output = "stair_alt_4",
recipe = {
{ "panel_4", "" },
{ "", "panel_4" },
},
})
register_craft_schema({
output = "stair_alt_4",
recipe = {
{ "", "panel_4" },
{ "panel_4", "" },
},
})
register_craft_schema({
output = "stair_alt_8",
recipe = {
{ "panel_8", "" },
{ "", "panel_8" },
},
})
register_craft_schema({
output = "stair_alt_8",
recipe = {
{ "", "panel_8" },
{ "panel_8", "" },
},
})

View file

@ -0,0 +1,132 @@
local api = stairsplus.api
local register_craft_schema = api.register_craft_schema
local slices = { 1, 2, 4, 8, 12, 14, 15 }
local valid_slice = {
[1] = true,
[2] = true,
[4] = true,
[8] = true,
[12] = true,
[14] = true,
[15] = true,
}
local full_promotion = {
micro = "panel_8",
panel = "slab_8",
slab = "node",
}
local half_promotion = {
micro = "panel",
panel = "slab",
}
local demotion = {
node = "slab",
slab = "panel",
panel = "micro",
}
-- stack things on top of each other
for _, shape in ipairs({ "micro", "panel", "slab" }) do
for _, slice1 in ipairs(slices) do
local shape1 = ("%s_%s"):format(shape, slice1)
local def1 = api.registered_shapes[shape1]
for _, slice2 in ipairs(slices) do
local shape2 = ("%s_%s"):format(shape, slice2)
local def2 = api.registered_shapes[shape2]
local slice3 = slice1 + slice2
if valid_slice[slice3] then
local shape3 = ("%s_%s"):format(shape, slice3)
local def3 = api.registered_shapes[shape3]
local n = math.floor((def1.eighths + def2.eighths) / def3.eighths)
register_craft_schema({
output = ("%s %s"):format(shape3, n),
recipe = {
{ shape1 },
{ shape2 },
},
})
elseif slice3 == 16 then
register_craft_schema({
output = ("%s"):format(full_promotion[shape]),
recipe = {
{ shape1 },
{ shape2 },
},
})
end
end
end
end
-- stack things next to each other
for _, shape in ipairs({ "micro", "panel" }) do
for _, slice in ipairs(slices) do
local shape1 = ("%s_%s"):format(shape, slice)
local def1 = api.registered_shapes[shape1]
local shape2 = ("%s_%s"):format(half_promotion[shape], slice)
local def2 = api.registered_shapes[shape2]
local n = math.floor(2 * def1.eighths / def2.eighths)
register_craft_schema({
output = ("%s %s"):format(shape2, n),
recipe = {
{ shape1, shape1 },
},
})
end
end
-- split in half horizontally
register_craft_schema({
output = "slab_8 6",
recipe = { { "node", "node", "node" } },
})
for _, shape in ipairs({ "micro", "panel", "slab" }) do
for _, slice1 in ipairs({ 2, 4, 8 }) do
local slice2 = slice1 / 2
local shape1 = ("%s_%s"):format(shape, slice1)
local shape2 = ("%s_%s"):format(shape, slice2)
local def1 = api.registered_shapes[shape1]
local def2 = api.registered_shapes[shape2]
local n = math.floor(3 * def1.eighths / def2.eighths)
register_craft_schema({
output = ("%s %s"):format(shape2, n),
recipe = {
{ shape1, shape1, shape1 },
},
})
end
end
-- split in half vertically
for _, shape in ipairs({ "panel", "slab" }) do
for _, slice in ipairs(slices) do
local shape1 = ("%s_%s"):format(shape, slice)
local shape2 = ("%s_%s"):format(demotion[shape], slice)
local def1 = api.registered_shapes[shape1]
local def2 = api.registered_shapes[shape2]
local n = math.floor(3 * def1.eighths / def2.eighths)
register_craft_schema({
output = ("%s %s"):format(shape2, n),
recipe = {
{ shape1 },
{ shape1 },
{ shape1 },
},
})
end
end

View file

@ -0,0 +1,21 @@
stairsplus.api.register_passthrough_groups({
"not_in_creative_inventory",
"dig_immediate",
"float",
"level",
"slippery",
"falling_node",
"disable_jump",
})
stairsplus.api.register_scaling_groups({
"bouncy",
"fall_damage_add_percent",
"slippery",
})
stairsplus.api.register_ignore_groups({
"attached_node",
"connect_to_raillike",
"tool",
})

View file

@ -0,0 +1,15 @@
if not stairsplus.has.default then
return
end
stairsplus.api.register_passthrough_groups({
"crumbly",
"cracky",
"snappy",
"choppy",
"fleshy",
"flammable",
"explody",
"oddly_breakable_by_hand",
"snowy", -- https://github.com/fluxionary/minetest-moreblocks/issues/20 might revisit this later
})

View file

@ -0,0 +1,3 @@
stairsplus.dofile("groups", "builtin")
stairsplus.dofile("groups", "default")
stairsplus.dofile("groups", "other")

View file

@ -0,0 +1,61 @@
-- group overrides for various other mods
-- various things found in the wild
stairsplus.api.register_passthrough_groups({
"bendy",
"dig_generic",
"dig_sand",
"dig_stone",
"dig_tree",
"fast_travel",
"melty",
"unbreakable",
})
stairsplus.api.register_ignore_groups({
"type_node",
"ud_param2_colorable",
})
-- mineclone
stairsplus.api.register_passthrough_groups({
"pickaxey",
"axey",
"shovely",
"swordy",
"swordy_cobweb",
"shearsy",
"shearsy_wool",
"handy",
"creative_breakable",
"dig_by_water",
"destroy_by_lava_flow",
"dig_by_piston",
"non_mycelium_plant",
"enderman_takable",
"disable_suffocation",
"no_rename",
})
stairsplus.api.register_scaling_groups({
"falling_node_damage",
"fire_encouragement",
"fire_flammability",
"comparator_signal",
"set_on_fire",
"compostability",
})
stairsplus.api.register_ignore_groups({
"crush_after_fall",
"cultivatable",
"path_creation_possible",
"spreading_dirt_type",
"dirtifies_below_solid",
"dirtifier",
"destroys_items",
"no_eat_delay",
"can_eat_when_full",
"attached_node_facedir",
"supported_node",
})

View file

@ -0,0 +1,25 @@
fmod.check_version({ year = 2023, month = 2, day = 1 })
futil.check_version({ year = 2023, month = 11, day = 1 }) -- is_player
stairsplus = fmod.create()
-- please don't change the order in which things are loaded, without understanding why they're ordered like this
stairsplus.dofile("util")
stairsplus.dofile("api", "init")
stairsplus.dofile("shapes", "init")
stairsplus.dofile("groups", "init")
if stairsplus.settings.crafting_schemata_enabled then
stairsplus.dofile("craft_schemas", "init")
end
stairsplus.dofile("resources", "init")
stairsplus.dofile("circular_saw")
stairsplus.dofile("compat", "init")
stairsplus.dofile("aliases")
stairsplus.dofile("scripts", "init")

View file

@ -0,0 +1,51 @@
# textdomain: stairsplus
@1 Microblock=
@1 1/16 Microblock=
@1 1/8 Microblock=
@1 1/4 Microblock=
@1 3/4 Microblock=
@1 7/8 Microblock=
@1 15/16 Microblock=
@1 1/16 Panel=
@1 1/8 Panel=
@1 1/4 Panel=
@1 1/2 Panel=
@1 3/4 Panel=
@1 7/8 Panel=
@1 15/16 Panel=
@1 1/16 Slab=
@1 1/8 Slab=
@1 1/4 Slab=
@1 1/2 Slab=
@1 3/4 Slab=
@1 7/8 Slab=
@1 15/16 Slab=
@1 1/16 Slab Two Sides=
@1 1/16 Slab Three Sides=
@1 1/16 Slab Three Sides U=
@1 Slope=
@1 1/2 Slope=
@1 1/2 Slope Raised=
@1 Slope Inner=
@1 Slope Outer=
@1 Slope Inner Cut=
@1 Slope Outer Cut=
@1 Slope Inner Half=
@1 Slope Outer Half=
@1 Slope Inner Cut Half=
@1 Slope Outer Cut Half=
@1 Slope Inner Half Raised=
@1 Slope Inner Cut Half Raised=
@1 Slope Outer Half Raised=
@1 Slope Outer Cut Half Raised=
@1 Slope Cut=
@1 Half Stair=
@1 Right Half Stair=
@1 Stair=
@1 Inner Stair=
@1 Outer Stair=
@1 1/16 Alt Stair=
@1 1/8 Alt Stair=
@1 1/4 Alt Stair=
@1 1/2 Alt Stair=

View file

@ -0,0 +1,51 @@
# textdomain: stairsplus
@1 Microblock=@1 (микроблок)
@1 1/16 Microblock=@1 (1/16 микроблок)
@1 1/8 Microblock=@1 (1/8 микроблок)
@1 1/4 Microblock=@1 (1/4 микроблок)
@1 3/4 Microblock=@1 (3/4 микроблок)
@1 7/8 Microblock=@1 (7/8 микроблок)
@1 15/16 Microblock=@1 (15/16 микроблок)
@1 1/16 Panel=@1 (1/16 панель)
@1 1/8 Panel=@1 (1/8 панель)
@1 1/4 Panel=@1 (1/4 панель)
@1 1/2 Panel=@1 (1/2 панель)
@1 3/4 Panel=@1 (3/4 панель)
@1 7/8 Panel=@1 (7/8 панель)
@1 15/16 Panel=@1 (15/16 панель)
@1 1/16 Slab=@1 (1/16 плита)
@1 1/8 Slab=@1 (1/8 плита)
@1 1/4 Slab=@1 (1/4 плита)
@1 1/2 Slab=@1 (1/2 плита)
@1 3/4 Slab=@1 (3/4 плита)
@1 7/8 Slab=@1 (7/8 плита)
@1 15/16 Slab=@1 (15/16 плита)
@1 1/16 Slab Two Sides=@1 (1/16 двухсторонняя плита)
@1 1/16 Slab Three Sides=@1 (1/16 трехсторонняя плита)
@1 1/16 Slab Three Sides U=@1 (1/16 U-трехсторонняя плита)
@1 Slope=@1 (скос)
@1 1/2 Slope=@1 (1/2 скос)
@1 1/2 Slope Raised=@1 (1/2 приподнятый скос)
@1 Slope Inner=@1 (внутренний скос)
@1 Slope Outer=@1 (внешний скос)
@1 Slope Inner Cut=@1 (скос, внутренний срез)
@1 Slope Outer Cut=@1 (скос, внешний срез)
@1 Slope Inner Half=@1 (скос, внутренняя половина)
@1 Slope Outer Half=@1 (скос, внешняя половина)
@1 Slope Inner Cut Half=@1 (скос, внутренний срез, половина)
@1 Slope Outer Cut Half=@1 (скос, внешний срез, половина)
@1 Slope Inner Half Raised=@1 (скос, внутренняя половина, приподнятый)
@1 Slope Inner Cut Half Raised=@1 (скос, внутренний срез, половина, приподнятый)
@1 Slope Outer Half Raised=@1 (скос, наружная половина, приподнятый)
@1 Slope Outer Cut Half Raised=@1 (скос, наружный срез, половина, приподнятый)
@1 Slope Cut=@1 (скос, срез)
@1 Half Stair=@1 (полуступени)
@1 Right Half Stair=@1 (правая половина ступеней)
@1 Stair=@1 (ступени)
@1 Inner Stair=@1 (внутренние ступени)
@1 Outer Stair=@1 (внешние ступени)
@1 1/16 Alt Stair=@1 (1/16 альт. ступени)
@1 1/8 Alt Stair=@1 (1/8 альт. ступени)
@1 1/4 Alt Stair=@1 (1/4 альт. ступени)
@1 1/2 Alt Stair=@1 (1/2 альт. ступени)

View file

@ -0,0 +1,10 @@
name = stairsplus
title = stairs+
description = microblock API
website = https://content.minetest.net/packages/rheo/moreblocks/.
author = Hugo Locurcio, fluxionary, others (see commit log)
license = LGPL-3.0-or-later
media_license = CC-BY-SA-4.0
version = 2024-12-23
depends = fmod, futil
optional_depends = default, i3, unified_inventory

View file

@ -0,0 +1,56 @@
g top
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 0.7071 -0.7071
s off
f 2/1/1 1/2/1 4/3/1 3/4/1
g bottom
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s off
f 6/5/2 5/6/2 7/7/2 8/8/2
g right
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -1.0000 0.0000 0.0000
s off
f 9/9/3 11/10/3 10/11/3
g left
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 12/12/4 13/13/4 14/14/4
g back
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -0.0000 1.0000
s off
f 15/15/5 16/16/5 17/17/5 18/18/5

View file

@ -0,0 +1,68 @@
g top
v 0.500000 0.500000 0.500000
v -0.500000 -0.000000 0.500000
v 0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.5000 0.0000
vt 1.0000 1.0000
vt 0.5000 2.0000
vt 0.0000 1.0000
vn -0.4082 0.8165 -0.4082
s 1
f 4/1/1 2/2/1 1/3/1 3/4/1
g bottom
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -1.0000 -0.0000
s 1
f 6/5/2 5/6/2 8/7/2 7/8/2
g right
v -0.500000 -0.500000 0.500000
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vn -1.0000 0.0000 0.0000
s 1
f 11/9/3 9/10/3 10/11/3
g left
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vt 0.0000 1.0000
vn 1.0000 0.0000 0.0000
s 1
f 12/12/4 14/13/4 15/14/4 13/15/4
g back
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 -0.000000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 0.5000
vn -0.0000 -0.0000 1.0000
s 1
f 16/16/5 17/17/5 18/18/5 19/19/5
g front
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 0.0000 0.5000
vn 0.0000 0.0000 -1.0000
s 1
f 20/20/6 22/21/6 21/22/6

View file

@ -0,0 +1,56 @@
g top
v 0.500000 -0.000000 0.500000
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 0.8944 -0.4472
s off
f 2/1/1 1/2/1 4/3/1 3/4/1
g bottom
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s off
f 6/5/2 5/6/2 7/7/2 8/8/2
g right
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -1.0000 0.0000 0.0000
s off
f 9/9/3 11/10/3 10/11/3
g left
v 0.500000 -0.000000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 12/12/4 13/13/4 14/14/4
g back
v 0.500000 -0.000000 0.500000
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 0.5000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -0.0000 1.0000
s off
f 15/15/5 16/16/5 17/17/5 18/18/5

View file

@ -0,0 +1,72 @@
g top
v -0.500000 0.500000 0.500000
v -0.500000 0.000000 -0.500000
v 0.500000 0.000000 -0.500000
v 0.500000 0.500000 0.500000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vn 0.0000 0.8944 -0.4472
s off
f 2/1/1 1/2/1 4/3/1 3/4/1
g bottom
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vn 0.0000 -1.0000 -0.0000
s off
f 6/5/2 5/6/2 7/7/2 8/8/2
g right
v -0.500000 0.500000 0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -1.0000 0.0000 0.0000
s off
f 9/9/3 10/10/3 11/11/3 12/12/3
g left
v 0.500000 0.000000 -0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 0.5000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 13/13/4 15/14/4 16/15/4 14/16/4
g back
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -0.0000 1.0000
s off
f 19/17/5 17/18/5 18/19/5 20/20/5
g front
v -0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 0.5000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 0.0000 -1.0000
s off
f 21/21/6 23/22/6 24/23/6 22/24/6

View file

@ -0,0 +1,81 @@
g top
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vn 0.0000 0.7071 -0.7071
vn -0.7071 0.7071 0.0000
s 1
f 3/1/1 2/2/1 4/3/1
f 2/4/2 1/5/2 5/6/2
g bottom
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -1.0000 -0.0000
s 1
f 9/7/3 7/8/3 6/9/3 8/10/3
l 8 10
g right
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -1.0000 0.0000 0.0000
s 1
f 11/11/4 12/12/4 13/13/4
g left
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 1.0000 0.0000 0.0000
s 1
f 14/14/5 15/15/5 16/16/5 17/17/5
l 15 18
g back
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -0.0000 1.0000
s 1
f 22/18/6 20/19/6 19/20/6 21/21/6
l 22 23
l 19 23
g front
v 0.500000 0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 0.0000 -1.0000
s 1
f 24/22/7 25/23/7 27/24/7

View file

@ -0,0 +1,72 @@
g top
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.5000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 1.0000 0.0000
vn -0.5774 0.5774 -0.5774
s 1
f 3/1/1 2/2/1 1/3/1
f 4/4/2 3/5/2 1/6/2
g bottom
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s 1
f 6/7/3 5/8/3 7/9/3 8/10/3
g right
v -0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vn -1.0000 0.0000 0.0000
s 1
f 11/11/4 9/12/4 10/13/4
g left
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 1.0000 0.0000 0.0000
s 1
f 12/14/5 13/15/5 14/16/5 15/17/5
g back
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -0.0000 1.0000
s 1
f 18/18/6 16/19/6 17/20/6 19/21/6
g front
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 0.0000 1.0000
vn 0.0000 0.0000 -1.0000
s 1
f 20/22/7 22/23/7 21/24/7

View file

@ -0,0 +1,72 @@
g top
v 0.500000 0.000000 -0.500000
v 0.500000 -0.000000 0.500000
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.5000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 1.0000 0.0000
vn -0.4082 0.8165 -0.4082
s 1
f 3/1/1 2/2/1 1/3/1
f 4/4/2 3/5/2 1/6/2
g bottom
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s 1
f 6/7/3 5/8/3 7/9/3 8/10/3
g right
v -0.500000 -0.500000 0.500000
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vn -1.0000 0.0000 0.0000
s 1
f 11/11/4 9/12/4 10/13/4
g left
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
v 0.500000 -0.000000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vt 0.0000 0.5000
vn 1.0000 0.0000 0.0000
s 1
f 12/14/5 13/15/5 14/16/5 15/17/5
g back
v 0.500000 -0.500000 0.500000
v 0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.000000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vt 0.0000 0.5000
vn 0.0000 -0.0000 1.0000
s 1
f 18/18/6 16/19/6 17/20/6 19/21/6
g front
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 0.0000 0.5000
vn 0.0000 0.0000 -1.0000
s 1
f 20/22/7 22/23/7 21/24/7

View file

@ -0,0 +1,76 @@
g top
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 0.000000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.5000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 1.0000 0.0000
vn -0.4082 0.8165 -0.4082
s 1
f 3/1/1 2/2/1 1/3/1
f 4/4/2 3/5/2 1/6/2
g bottom
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s 1
f 6/7/3 5/8/3 7/9/3 8/10/3
g right
v -0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 0.000000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 0.5000
vn -1.0000 0.0000 0.0000
s 1
f 11/11/4 9/12/4 10/13/4 12/14/4
g left
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 1.0000 0.0000 0.0000
s 1
f 13/15/5 14/16/5 15/17/5 16/18/5
g back
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -0.0000 1.0000
s 1
f 19/19/6 17/20/6 18/21/6 20/22/6
g front
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 0.000000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vt 0.0000 1.0000
vn -0.0000 0.0000 -1.0000
s 1
f 21/23/7 23/24/7 24/25/7 22/26/7

View file

@ -0,0 +1,85 @@
g top
v 0.500000 0.000000 -0.500000
v 0.500000 -0.000000 0.500000
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vn 0.0000 0.8944 -0.4472
vn -0.4472 0.8944 0.0000
s off
f 3/1/1 2/2/1 4/3/1
f 2/4/2 1/5/2 5/6/2
g bottom
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -1.0000 -0.0000
s off
f 10/7/3 7/8/3 6/9/3 9/10/3
l 9 8
l 11 9
g right
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -1.0000 0.0000 0.0000
s off
f 12/11/4 13/12/4 14/13/4
l 15 14
g left
v 0.500000 0.000000 -0.500000
v 0.500000 -0.000000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 16/14/5 17/15/5 18/16/5 19/17/5
l 20 17
g back
v 0.500000 -0.000000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -0.0000 1.0000
s off
f 24/18/6 22/19/6 21/20/6 23/21/6
l 25 24
l 25 21
g front
v 0.500000 0.000000 -0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 0.0000 -1.0000
s off
f 26/22/7 27/23/7 29/24/7

View file

@ -0,0 +1,86 @@
g top
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 0.000000 -0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 1.0000 0.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 0.8944 -0.4472
vn -0.4472 0.8944 0.0000
s off
f 3/1/1 2/2/1 4/3/1
f 2/2/2 1/4/2 5/5/2
g bottom
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -1.0000 -0.0000
s off
f 8/6/3 9/7/3 7/8/3 6/9/3
l 10 8
g right
v -0.500000 0.500000 0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 0.5000
vt 0.0000 0.0000
vn -1.0000 0.0000 0.0000
s off
f 13/10/4 11/11/4 12/12/4 15/13/4
g left
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 16/14/5 17/15/5 18/16/5 19/17/5
l 20 17
g back
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 1.0000
vn 0.0000 -0.0000 1.0000
s off
f 24/18/6 22/19/6 21/20/6 23/21/6
l 25 21
l 25 24
g front
v 0.500000 0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 0.5000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -0.0000 0.0000 -1.0000
s off
f 28/22/7 26/23/7 27/24/7 30/25/7
l 29 26

View file

@ -0,0 +1,48 @@
g top
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -0.7071 0.7071 0.0000
vn 0.0000 0.7071 -0.7071
s off
f 4/1/1 2/2/1 1/3/1
f 4/4/2 3/5/2 2/6/2
g bottom
v 0.500000 -0.500000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -1.0000 -0.0000
s off
f 5/7/3 6/8/3 7/9/3 8/10/3
g right
v 0.500000 -0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vn 0.0000 -0.0000 1.0000
s off
f 10/11/4 9/12/4 11/13/4
g left
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 0.500000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 14/14/5 12/15/5 13/16/5

View file

@ -0,0 +1,40 @@
g top
v 0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 0.0000
vt 0.5000 1.0000
vt 0.0000 0.0000
vn -0.5774 0.5774 -0.5774
s off
f 2/1/1 1/2/1 3/3/1
g bottom
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 0.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s off
f 5/4/2 4/5/2 6/6/2
g right
v 0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -0.0000 1.0000
s off
f 7/7/3 8/8/3 9/9/3
g left
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 0.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 12/10/4 10/11/4 11/12/4

View file

@ -0,0 +1,40 @@
g top
v 0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 0.0000
vt 0.5000 1.0000
vt 0.0000 0.0000
vn -0.4082 0.8165 -0.4082
s off
f 2/1/1 1/2/1 3/3/1
g bottom
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 0.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s off
f 5/4/2 4/5/2 6/6/2
g right
v 0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -0.0000 1.0000
s off
f 7/7/3 8/8/3 9/9/3
g left
v 0.500000 -0.000000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 1.0000 0.0000
vt 0.0000 0.5000
vt 0.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 12/10/4 10/11/4 11/12/4

View file

@ -0,0 +1,56 @@
g top
v -0.500000 -0.000000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 0.000000 -0.500000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vn -0.4082 0.8165 -0.4082
s off
f 1/1/1 2/2/1 3/3/1
g bottom
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
vt 0.0000 1.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s off
f 4/4/2 6/5/2 5/6/2
g right
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
vt 1.0000 0.0000
vt 1.0000 0.5000
vt 0.0000 0.5000
vt 0.0000 0.0000
vn -0.7071 0.0000 -0.7071
s off
f 8/7/3 7/8/3 10/9/3 9/10/3
g left
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vn 1.0000 0.0000 0.0000
s off
f 12/11/4 11/12/4 13/13/4 14/14/4
g back
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vn -0.0000 -0.0000 1.0000
s off
f 15/15/5 16/16/5 17/17/5 18/18/5

View file

@ -0,0 +1,48 @@
g top
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 -0.000000 0.500000
vt 1.0000 0.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vt 0.0000 0.0000
vn 0.0000 0.8944 -0.4472
vn -0.4472 0.8944 0.0000
s off
f 2/1/1 4/2/1 3/3/1
f 1/4/2 4/5/2 2/6/2
g bottom
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
vt 1.0000 1.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -1.0000 -0.0000
s off
f 5/7/3 6/8/3 7/9/3 8/10/3
g right
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.000000 0.500000
vt 1.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn 0.0000 -0.0000 1.0000
s off
f 11/11/4 9/12/4 10/13/4
g left
v 0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.000000 0.500000
vt 1.0000 0.0000
vt 0.0000 0.5000
vt 0.0000 0.0000
vn 1.0000 0.0000 0.0000
s off
f 12/14/5 14/15/5 13/16/5

View file

@ -0,0 +1,74 @@
g top
v -0.500000 -0.000000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 0.000000 -0.500000
v -0.500000 0.000000 -0.500000
vt 1.0000 0.0000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 1.0000
vn 0.0000 0.8944 -0.4472
vn -0.4472 0.8944 0.0000
s off
f 4/1/1 2/2/1 3/3/1
f 4/1/2 1/4/2 2/2/2
g bottom
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vn 0.0000 -1.0000 -0.0000
s off
f 7/5/3 6/6/3 5/7/3 8/8/3
g right
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 1.0000 0.5000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vn -1.0000 0.0000 0.0000
s off
f 9/9/4 11/10/4 12/11/4 10/12/4
g left
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
vt 0.0000 1.0000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vn 1.0000 0.0000 0.0000
s off
f 14/13/5 13/14/5 15/15/5 16/16/5
g back
v -0.500000 -0.000000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 1.0000
vn -0.0000 -0.0000 1.0000
s off
f 17/17/6 18/18/6 19/19/6 20/20/6
g front
v 0.500000 -0.500000 -0.500000
v 0.500000 0.000000 -0.500000
v -0.500000 0.000000 -0.500000
v -0.500000 -0.500000 -0.500000
vt 0.0000 0.5000
vt 0.0000 0.0000
vt 1.0000 0.0000
vt 1.0000 0.5000
vn 0.0000 0.0000 -1.0000
s off
f 22/21/7 21/22/7 24/23/7 23/24/7

View file

@ -0,0 +1,9 @@
local table_set_all = futil.table.set_all
stairsplus.resources.craft_materials = {}
if stairsplus.has.default then
table_set_all(stairsplus.resources.craft_materials, {
steel_ingot = "default:steel_ingot",
})
end

View file

@ -0,0 +1,6 @@
stairsplus.resources.formspec_style = ""
if stairsplus.has.default then
-- prepend background and slot styles from default if available
stairsplus.resources.formspec_style = default.gui_bg .. default.gui_bg_img .. default.gui_slots
end

View file

@ -0,0 +1,4 @@
stairsplus.resources = {}
stairsplus.dofile("resources", "craft_materials")
stairsplus.dofile("resources", "sounds")

View file

@ -0,0 +1,9 @@
local table_set_all = futil.table.set_all
stairsplus.resources.sounds = {}
if stairsplus.has.default then
table_set_all(stairsplus.resources.sounds, {
wood = default.node_sound_wood_defaults(),
})
end

View file

@ -0,0 +1,83 @@
import argparse
import math
import multiprocessing
import pathlib
import time
import progressbar
import pymtdb
from whitelist_helpers import create_whitelist, write_whitelist, create_filter, count_blocks, existing_file, get_cursor, \
get_all_nodes
def process_chunk(args, offset, limit, completed, results):
cursor = get_cursor(args)
cursor.execute(f'SELECT data FROM blocks LIMIT {limit} OFFSET {offset}')
node_names = set()
i = 0
for i, row in enumerate(cursor, 1):
node_names.update(pymtdb.MapBlockSimple.import_from_serialized(row[0]).node_names)
if i % args.chunk_size == 0:
completed.value = i
completed.value = i
results.put(node_names, False)
def main(args):
num_blocks, count_blocks_elapsed = count_blocks(args) # 345104538, 13*60
work_size = math.ceil(num_blocks / args.workers)
offsets = range(0, num_blocks, work_size)
completeds = tuple(multiprocessing.Value('Q', 0, lock=False) for _ in range(args.workers))
# because we want to terminate the processes before we remove the results from the queue, use a manager
# see warnings in https://docs.python.org/3/library/multiprocessing.html#pipes-and-queues
results = multiprocessing.Manager().Queue()
processes = tuple(
multiprocessing.Process(target=process_chunk, name=f'processor {i}',
args=(args, offsets[i], work_size, completeds[i], results))
for i in range(args.workers)
)
for process in processes:
process.start()
print(f'NOTICE: not all jobs will start at the same time due to the nature of ranged queries. actual runtime will '
f'be closer to 1/{min(args.workers, multiprocessing.cpu_count())}th the early estimate, plus '
f'{count_blocks_elapsed}s.')
# TODO: if we know how long it takes to count the blocks, and how many workers there are, we can estimate how long
# before a process starts producing results, and resize the jobs to maximize processor usage.
# proper estimation requires differential equations, ugh.
with progressbar.ProgressBar(max_value=num_blocks) as bar:
while True:
time.sleep(1)
total_completed = sum(completed.value for completed in completeds)
bar.update(total_completed)
if total_completed == num_blocks:
break
print('joining...')
for process in processes:
process.join()
print('compiling results...')
all_nodes = get_all_nodes(results)
filter_ = create_filter(args.stairsplus_dump)
whitelist = create_whitelist(filter_, all_nodes)
write_whitelist(args, whitelist)
def parse_args(args=None, namespace=None):
p = argparse.ArgumentParser()
g = p.add_mutually_exclusive_group(required=True)
g.add_argument('--pg_connection', '-c')
g.add_argument('--sqlite_file', '-s', type=existing_file)
p.add_argument('--chunk_size', type=int, default=64)
p.add_argument('--workers', type=int, default=multiprocessing.cpu_count())
p.add_argument('--output', '-o', type=pathlib.Path)
p.add_argument('stairsplus_dump', type=existing_file)
return p.parse_args(args=args, namespace=namespace)
if __name__ == "__main__":
main(parse_args())

View file

@ -0,0 +1,33 @@
local f = string.format
local S = stairsplus.S
minetest.register_chatcommand("dump_stairsplus_registered_nodes", {
description = S("create a list of stairsplus nodes, including aliases, to use as a filter in creating a whitelist"),
privs = { server = true },
func = function()
local shaped_nodes = {}
for shaped_node, shape in pairs(stairsplus.api.shape_by_shaped_node) do
if shape ~= "node" then
shaped_nodes[shaped_node] = true
end
end
local aliases = {}
for original in pairs(minetest.registered_aliases) do
local resolved = futil.resolve_item(original)
if resolved and shaped_nodes[resolved] then
aliases[original] = resolved
end
end
local filename = futil.path_concat(minetest.get_worldpath(), "stairsplus_dump.json")
local contents = minetest.write_json({
aliases = aliases,
shaped_nodes = shaped_nodes,
}, true)
if not futil.write_file(filename, contents) then
return false, f("error writing file @ %s", filename)
end
return true, f("dump created @ %s.", filename)
end,
})

View file

@ -0,0 +1,239 @@
# https://github.com/minetest/minetest/blob/master/doc/world_format.txt#L301
# https://docs.python.org/3/library/struct.html
import collections
import pyzstd
from stream import StreamReader
MAP_BLOCKSIZE = 16
vector = collections.namedtuple('vector', ('x', 'y', 'z'))
def unpack_pos(packed_pos):
# 16*(16*z + y) + x
zy, x = divmod(packed_pos, 16)
z, y = divmod(zy, 16)
return vector(x, y, z)
class Inventory:
def __init__(self):
pass
@staticmethod
def from_bytes(data: bytes):
inv = Inventory()
return inv
class MetaData:
def __init__(self):
self._meta = {}
self._private = set()
def __getitem__(self, key: bytes):
return self._meta[key]
def __setitem__(self, key: bytes, value: bytes):
self._meta[key] = value
def mark_as_private(self, key: bytes, private: bool):
if private:
self._private.add(key)
else:
self._private.discard(key)
class StaticObject:
def __init__(self, type_, pos, data):
self._type = type_
self._pos = pos
self._data = data
class Timer:
def __init__(self, timeout: int, elapsed: int):
self._timeout = timeout
self._elapsed = elapsed
class MapBlock:
def __init__(self):
self._flags = 0
self._lighting_complete = 0
self._timestamp = 0
self._nodes = tuple(
tuple(
["ignore" for _ in range(MAP_BLOCKSIZE)]
for _ in range(MAP_BLOCKSIZE)
) for _ in range(MAP_BLOCKSIZE)
)
self._param1 = tuple(
tuple(
[0 for _ in range(MAP_BLOCKSIZE)]
for _ in range(MAP_BLOCKSIZE)
) for _ in range(MAP_BLOCKSIZE)
)
self._param2 = tuple(
tuple(
[0 for _ in range(MAP_BLOCKSIZE)]
for _ in range(MAP_BLOCKSIZE)
) for _ in range(MAP_BLOCKSIZE)
)
self._metadata = tuple(
tuple(
[None for _ in range(MAP_BLOCKSIZE)]
for _ in range(MAP_BLOCKSIZE)
) for _ in range(MAP_BLOCKSIZE)
)
self._inventory = tuple(
tuple(
[None for _ in range(MAP_BLOCKSIZE)]
for _ in range(MAP_BLOCKSIZE)
) for _ in range(MAP_BLOCKSIZE)
)
self._timer = tuple(
tuple(
[None for _ in range(MAP_BLOCKSIZE)]
for _ in range(MAP_BLOCKSIZE)
) for _ in range(MAP_BLOCKSIZE)
)
def iter_nodes(self):
for plane in self._nodes:
for row in plane:
yield from row
@staticmethod
def import_from_serialized(serialized_data: bytes):
mapblock = MapBlock()
version = serialized_data[0] # struct.unpack('>b', serialized_data)
if version != 29:
raise RuntimeError(f'can\'t parse version {version}')
stream = StreamReader(pyzstd.decompress(serialized_data[1:]))
mapblock._flags = stream.u8()
mapblock._lighting_complete = stream.u16()
mapblock._timestamp = stream.u32()
name_id_mapping_version = stream.u8()
num_name_id_mappings = stream.u16()
if name_id_mapping_version != 0:
raise RuntimeError(f'can\'t grok name_id_mapping_version {name_id_mapping_version}')
name_by_id = {}
for _ in range(num_name_id_mappings):
id_ = stream.u16()
name_len = stream.u16()
name_by_id[id_] = stream.bytes(name_len)
content_width = stream.u8()
if content_width != 2:
raise RuntimeError(f'invalid content_width {content_width}')
params_width = stream.u8()
if params_width != 2:
raise RuntimeError(f'invalid params_width {params_width}')
for z in range(MAP_BLOCKSIZE):
for y in range(MAP_BLOCKSIZE):
for x in range(MAP_BLOCKSIZE):
mapblock._nodes[z][y][x] = name_by_id[stream.u16()]
for z in range(MAP_BLOCKSIZE):
for y in range(MAP_BLOCKSIZE):
for x in range(MAP_BLOCKSIZE):
mapblock._param1[z][y][x] = stream.u8()
for z in range(MAP_BLOCKSIZE):
for y in range(MAP_BLOCKSIZE):
for x in range(MAP_BLOCKSIZE):
mapblock._param2[z][y][x] = stream.u8()
ib = ''
node_metadata_version = stream.u8()
if node_metadata_version > 0:
if node_metadata_version != 2:
raise RuntimeError(f'unexpected node_metadata_version {node_metadata_version}')
node_metadata_count = stream.u16()
for _ in range(node_metadata_count):
pos = unpack_pos(stream.u16())
meta = MetaData()
num_vars = stream.u32()
for _ in range(num_vars):
key_len = stream.u16()
key = stream.bytes(key_len)
val_len = stream.u32()
meta[key] = stream.bytes(val_len)
meta.mark_as_private(key, stream.u8() == 1)
mapblock._metadata[pos.z][pos.y][pos.x] = meta
mapblock._inventory[pos.z][pos.y][pos.x] = Inventory.from_bytes(stream.inventory_bytes())
static_object_version = stream.u8()
if static_object_version != 0:
raise RuntimeError(f'unexpected static_object_version {static_object_version} {ib} {stream._data}')
static_object_count = stream.u16()
static_objects = []
for _ in range(static_object_count):
type_ = stream.u8()
pos_x_nodes = stream.s32() / 1e5
pos_y_nodes = stream.s32() / 1e5
pos_z_nodes = stream.s32() / 1e5
data_size = stream.u16()
data = stream.bytes(data_size)
static_objects.append(StaticObject(type_, vector(pos_x_nodes, pos_y_nodes, pos_z_nodes), data))
timers_length = stream.u8()
if timers_length != 10:
raise RuntimeError(f'unexpected timers_length {timers_length}')
num_of_timers = stream.u16()
for _ in range(num_of_timers):
pos = unpack_pos(stream.u16())
timeout = stream.s32()
elapsed = stream.s32()
mapblock._timer[pos.z][pos.y][pos.x] = Timer(timeout, elapsed)
return mapblock
class MapBlockSimple:
def __init__(self):
self.node_names = []
@staticmethod
def import_from_serialized(serialized_data: bytes):
mapblock = MapBlockSimple()
version = serialized_data[0]
if type(version) is bytes:
version = ord(version)
if version != 29:
raise RuntimeError(f'can\'t parse version {version}')
stream = StreamReader(pyzstd.decompress(serialized_data[1:]))
stream.u8() # flags
stream.u16() # lighting_complete
stream.u32() # timestamp
name_id_mapping_version = stream.u8()
num_name_id_mappings = stream.u16()
if name_id_mapping_version != 0:
raise RuntimeError(f'can\'t grok name_id_mapping_version {name_id_mapping_version}')
for _ in range(num_name_id_mappings):
stream.u16() # id
name_len = stream.u16()
mapblock.node_names.append(stream.bytes(name_len))
return mapblock

View file

@ -0,0 +1,3 @@
progressbar2
psycopg2
pyzstd

View file

@ -0,0 +1,72 @@
import struct
class StreamReader:
def __init__(self, data: bytes):
self._data = data
self._start = 0
def u8(self) -> int:
sformat = '>B'
ssize = struct.calcsize(sformat)
rv = struct.unpack(sformat, self._data[self._start:self._start + ssize])
self._start = self._start + ssize
return rv[0]
def u16(self) -> int:
sformat = '>H'
ssize = struct.calcsize(sformat)
rv = struct.unpack(sformat, self._data[self._start:self._start + ssize])
self._start = self._start + ssize
return rv[0]
def s32(self) -> int:
sformat = '>i'
ssize = struct.calcsize(sformat)
rv = struct.unpack(sformat, self._data[self._start:self._start + ssize])
self._start = self._start + ssize
return rv[0]
def u32(self) -> int:
sformat = '>I'
ssize = struct.calcsize(sformat)
rv = struct.unpack(sformat, self._data[self._start:self._start + ssize])
self._start = self._start + ssize
return rv[0]
def bytes(self, count: int) -> bytes:
rv = self._data[self._start:self._start + count]
self._start = self._start + count
return rv
def inventory_bytes(self) -> bytes:
start_of_end = self._data.find(b'EndInventory\n', self._start)
if start_of_end == -1:
return
actual_end = start_of_end + len(b'EndInventory\n')
rv = self._data[self._start:actual_end]
self._start = actual_end
return rv
def rest(self) -> bytes:
return self._data[self._start:]
class StreamWriter:
def __init__(self, fh):
self._fh = fh
def u8(self, value):
sformat = '>B'
self._fh.write(struct.pack(sformat, value))
def u16(self, value):
sformat = '>H'
self._fh.write(struct.pack(sformat, value))
def u32(self, value):
sformat = '>I'
self._fh.write(struct.pack(sformat, value))
def bytes(self, value: bytes):
self._fh.write(value)

View file

@ -0,0 +1,162 @@
# https://gitlab.com/bztsrc/mtsedit/blob/master/docs/mts_format.md
import argparse
import json
import pathlib
import lupa
from stream import StreamReader, StreamWriter
lua = lupa.LuaRuntime(unpack_returned_tuples=True)
def is_schem(file: pathlib.Path):
return file.suffix == '.mts'
def convert_schem(child, alias_map):
print(f'processing {child}')
with child.open('rb') as fh:
contents = fh.read()
reader = StreamReader(contents)
magic = reader.bytes(4)
if magic != b'MTSM':
raise RuntimeError(f'invalid magic number {magic}')
version = reader.u16()
if version != 4:
raise RuntimeError(f'unexpected version {version}')
x = reader.u16()
y = reader.u16()
z = reader.u16()
layer_probability_values = reader.bytes(y)
name_id_length = reader.u16()
names = []
any_changed = False
for _ in range(name_id_length):
name = reader.bytes(reader.u16())
alias = alias_map.get(name.decode())
if alias:
any_changed = True
names.append(alias.encode())
else:
names.append(name)
if any_changed:
print('writing changes...')
rest = reader.rest()
with child.open('wb') as fh:
writer = StreamWriter(fh)
writer.bytes(b'MTSM')
writer.u16(4)
writer.u16(x)
writer.u16(y)
writer.u16(z)
writer.bytes(layer_probability_values)
writer.u16(name_id_length)
for name in names:
writer.u16(len(name))
writer.bytes(name)
writer.bytes(rest)
def is_we(file: pathlib.Path):
return file.suffix == '.we'
def lua_dump(value):
if type(value) is str:
return repr(value)
elif type(value) in {int, float}:
return str(value)
elif type(value) in {list, tuple}:
return f'{{{", ".join(map(lua_dump, value))}}}'
elif type(value) is dict:
return '{' + ', '.join(f'[{lua_dump(k)}] = {lua_dump(v)}' for k, v in value.items()) + '}'
elif value is None:
return 'nil'
elif value is True:
return 'true'
elif value is False:
return 'false'
elif lupa.lua_type(value) == 'table':
return lua_dump(dict(value.items()))
else:
raise RuntimeError(f'value {value!r} w/ unexpected type {type(value)}')
def convert_we(child, alias_map):
print(f'processing {child}')
with child.open('r') as fh:
contents = fh.read()
assert(contents[:9] == '5:return ')
table = lua.eval(contents[9:])
data = tuple(map(dict, table.values()))
any_changed = False
for point in data:
alias = alias_map.get(point['name'])
if alias:
point['name'] = alias
any_changed = True
if any_changed:
print('writing changes...')
output = f'5:return {lua_dump(data)}'
with child.open('w') as fh:
fh.write(output)
def create_alias_map(stairsplus_dump: pathlib.Path):
print('reading aliases from dump')
aliases = {}
with stairsplus_dump.open() as fh:
data = json.load(fh)
for alias, shaped_node in data['aliases'].items():
aliases[alias] = shaped_node
return aliases
def main(args):
alias_map = create_alias_map(args.stairsplus_dump)
for child in args.schems.iterdir():
if child.is_file():
if is_schem(child):
convert_schem(child, alias_map)
elif is_we(child):
convert_we(child, alias_map)
else:
print(f'unknown file type {child.suffix}')
def existing_file(path: str) -> pathlib.Path:
file_path = pathlib.Path(path)
if not file_path.exists():
raise argparse.ArgumentTypeError(f'{path!r} does not exist.')
if not file_path.is_file():
raise argparse.ArgumentTypeError(f'{path!r} is not a file.')
return file_path
def existing_directory(path: str) -> pathlib.Path:
file_path = pathlib.Path(path)
if not file_path.exists():
raise argparse.ArgumentTypeError(f'{path!r} does not exist.')
if not file_path.is_dir():
raise argparse.ArgumentTypeError(f'{path!r} is not a directory.')
return file_path
def parse_args(args=None, namespace=None):
p = argparse.ArgumentParser()
p.add_argument('stairsplus_dump', type=existing_file)
p.add_argument('schems', type=existing_directory)
return p.parse_args(args=args, namespace=namespace)
if __name__ == "__main__":
main(parse_args())

View file

@ -0,0 +1,87 @@
import argparse
import json
import multiprocessing
import pathlib
import queue
import time
def existing_file(path: str) -> pathlib.Path:
file_path = pathlib.Path(path)
if not file_path.exists():
raise argparse.ArgumentTypeError(f'{path!r} does not exist.')
if not file_path.is_file():
raise argparse.ArgumentTypeError(f'{path!r} is not a file.')
return file_path
def get_cursor(args):
if args.pg_connection:
import psycopg2
conn = psycopg2.connect(args.pg_connection)
cursor = conn.cursor(name='blocks')
cursor.itersize = args.chunk_size
else:
import sqlite3
conn = sqlite3.connect(args.sqlite_file)
cursor = conn.cursor()
return cursor
def create_filter(stairsplus_dump: pathlib.Path):
print('creating filter from dump...')
start = time.time()
f = {}
with stairsplus_dump.open() as fh:
data = json.load(fh)
for shaped_node in data['shaped_nodes'].keys():
f[shaped_node.encode()] = shaped_node.encode()
for alias, shaped_node in data['aliases'].items():
f[alias.encode()] = shaped_node.encode()
print(f'created in {time.time() - start}')
return f
def count_blocks(args):
cursor = get_cursor(args)
# just shy of 12 minutes for postgres w/ a 150GiB map dump, an opteron 6376, and 4 encrypted raid6 5400 RPM disks
print('counting mapblocks - this can take a while...')
start = time.time()
cursor.execute('SELECT COUNT(data) FROM blocks')
num_blocks = cursor.fetchone()[0]
elapsed = time.time() - start
print(f'num_blocks: {num_blocks} (fetched in {elapsed}s)')
return num_blocks, elapsed
def create_whitelist(filter_, all_nodes):
print('creating whitelist')
return set(
shaped_node for shaped_node in map(filter_.get, all_nodes) if shaped_node
)
def write_whitelist(args, whitelist):
if args.output:
output = args.output
else:
output = args.stairsplus_dump.parent / 'stairsplus.whitelist'
with output.open('wb') as fh:
print(f'writing whitelist to {output!r}')
fh.write(b'\n'.join(sorted(whitelist)))
def get_all_nodes(results: multiprocessing.Queue):
all_nodes = set()
try:
while True:
all_nodes.update(results.get(False))
except queue.Empty:
return all_nodes

View file

@ -0,0 +1,40 @@
# I guess if you want the saw to be creative-only?
stairsplus.circular_saw_crafting (Allow crafting the circular saw) bool true
# Defaults to true if creative_mode is enabled.
stairsplus.ex_nihilo (Saw gives infinite nodes) bool false
# If enabled, Stairs+ nodes will be displayed in the inventory when playing in creative mode.
# Disabling this can speed up loading times for clients as fewer nodes need to be registered in the creative inventory.
stairsplus.in_creative_inventory (Display Stairs+ nodes in creative inventory) bool true
# Whether to show crafting recipes involving Stairs+ nodes (in compatible inventory managers)
stairsplus.in_craft_guide (Show crafting recipes) bool true
# "user" or "world" cause things to look nicer in general, but some nodes look better w/ the "node" style
stairsplus.default_align_style (World align style) enum user node,user,world
# regular stairs shapes. if you can't view all of these in the minetest settings GUI, please edit your minetest.conf by hand.
stairsplus.basic_shapes (basic shapes) flags micro_8,slab_8,stair,stair_inner,stair_outer panel_1,slope,slope_half,slope_half_raised,panel_2,slope_inner_cut,slope_inner_half,panel_4,slope_inner_half_raised,slope_inner_cut_half_raised,slope_outer,slope_outer_cut,slope_cut,slope_outer_half,panel_12,slope_outer_half_raised,slope_outer_cut_half_raised,panel_14,panel_15,micro_1,slab_15,slab_two_sides,stair_half,slab_three_sides,stair_right_half,slab_three_sides_u,micro_12,micro_14,slab_2,micro_15,slab_4,micro_2,stair_alt_4,stair_alt_2,stair_alt_1,stair_alt_8,slab_1,slab_12,stair,slope_outer_cut_half,slope_inner_cut_half,slab_14,micro_4,micro_8,slab_8,stair_inner,stair_outer,slope_inner,panel_8
# the most commonly used variants. if you can't view all of these in the minetest settings GUI, please edit your minetest.conf by hand.
stairsplus.common_shapes (common shapes) flags micro_8,panel_8,slab_1,slab_8,stair,stair_inner,stair_outer,slope,slope_half,slope_half_raised,slope_inner,slope_inner_cut,slope_inner_half,slope_inner_cut_half,slope_inner_half_raised,slope_inner_cut_half_raised,slope_outer,slope_outer_cut,slope_cut,slope_outer_half,slope_outer_cut_half,slope_outer_half_raised,slope_outer_cut_half_raised panel_1,slope,slope_half,slope_half_raised,panel_2,slope_inner_cut,slope_inner_half,panel_4,slope_inner_half_raised,slope_inner_cut_half_raised,slope_outer,slope_outer_cut,slope_cut,slope_outer_half,panel_12,slope_outer_half_raised,slope_outer_cut_half_raised,panel_14,panel_15,micro_1,slab_15,slab_two_sides,stair_half,slab_three_sides,stair_right_half,slab_three_sides_u,micro_12,micro_14,slab_2,micro_15,slab_4,micro_2,stair_alt_4,stair_alt_2,stair_alt_1,stair_alt_8,slab_1,slab_12,stair,slope_outer_cut_half,slope_inner_cut_half,slab_14,micro_4,micro_8,slab_8,stair_inner,stair_outer,slope_inner,panel_8
# default to registering *all* variants, and some other stuff.
# it is only "safe" to disable this on new servers.
stairsplus.legacy_mode (legacy mode) bool true
# automatically try to rotate nodes when placed. the mechanic is sometimes confusing.
stairsplus.legacy_place_mechanic (use legacy place mecahnic) bool true
# allows crafting shaped nodes in addition to cutting them
stairsplus.crafting_schemata_enabled (enable crafting schemata) bool true
# when set to true, will only register nodes specified in $WORLD_PATH/stairsplus.whitelist
stairsplus.whitelist_mode (whitelist mode) bool false
# this fork changes how groups for stairsplus derivative nodes are computed. normally this is logged, because i don't
# have a better way to communicate this. however, these changes don't cause any actual incompatibility that i'm aware
# of. if you don't want to fix the mods that use moreblocks to be aware of this fork, you can enable this setting
# to remove those log messages.
stairsplus.silence_group_overrides (silence group override log messages) bool false

View file

@ -0,0 +1,8 @@
stairsplus.dofile("shapes", "micros")
stairsplus.dofile("shapes", "panels")
stairsplus.dofile("shapes", "slabs")
stairsplus.dofile("shapes", "slopes")
stairsplus.dofile("shapes", "stairs")
stairsplus.api.register_shape_group("basic", stairsplus.settings.basic_shapes)
stairsplus.api.register_shape_group("common", stairsplus.settings.common_shapes)

View file

@ -0,0 +1,85 @@
stairsplus.api.register_shape("micro_1", {
name_format = "micro_%s_1",
description = "@1 1/16 Microblock",
shape_groups = { micro = 1, legacy = 1 },
eighths = 1, -- 1/64 nodes, complement of micro_15 to make panel_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0, -0.4375, 0.5 },
},
})
stairsplus.api.register_shape("micro_2", {
name_format = "micro_%s_2",
description = "@1 1/8 Microblock",
shape_groups = { micro = 1, legacy = 1 },
eighths = 1, -- 1/32 nodes, complement of micro_14 to make panel_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0, -0.375, 0.5 },
},
})
stairsplus.api.register_shape("micro_4", {
name_format = "micro_%s_4",
description = "@1 1/4 Microblock",
shape_groups = { micro = 1, legacy = 1 },
eighths = 1, -- 1/16 nodes, complement of micro_12 to make panel_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0, -0.25, 0.5 },
},
})
stairsplus.api.register_shape("micro_8", {
name_format = "micro_%s_8",
aliases = { "micro_%s", "micro_%s_bottom" },
description = "@1 Microblock", -- leave out the 1/2 to not confuse people too much...
shape_groups = { micro = 1, obligatory = 1, legacy = 1 },
eighths = 1, -- 1/8 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0, 0, 0.5 },
},
})
stairsplus.api.register_shape("micro_12", {
name_format = "micro_%s_12",
description = "@1 3/4 Microblock",
shape_groups = { micro = 1, legacy = 1 },
eighths = 1, -- 3/16 nodes, complement of micro_4 to make panel_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0, 0.25, 0.5 },
},
})
stairsplus.api.register_shape("micro_14", {
name_format = "micro_%s_14",
description = "@1 7/8 Microblock",
shape_groups = { micro = 1, legacy = 1 },
eighths = 1, -- 7/32 nodes, complement of micro_2 to make panel_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0, 0.375, 0.5 },
},
})
stairsplus.api.register_shape("micro_15", {
name_format = "micro_%s_15",
aliases = { "micro_%s_5" },
description = "@1 15/16 Microblock",
shape_groups = { micro = 1, legacy = 1 },
eighths = 1, -- 15/64 nodes, complement of micro_1 to make panel_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0, 0.4375, 0.5 },
},
})

View file

@ -0,0 +1,84 @@
stairsplus.api.register_shape("panel_1", {
name_format = "panel_%s_1",
description = "@1 1/16 Panel",
shape_groups = { panel = 1, legacy = 1 },
eighths = 1, -- 1/32 nodes, complement of panel_15 to make slab_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0.5, -0.4375, 0.5 },
},
})
stairsplus.api.register_shape("panel_2", {
name_format = "panel_%s_2",
description = "@1 1/8 Panel",
shape_groups = { panel = 1, legacy = 1 },
eighths = 1, -- 1/16 nodes, complement of panel_14 to make slab_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0.5, -0.375, 0.5 },
},
})
stairsplus.api.register_shape("panel_4", {
name_format = "panel_%s_4",
description = "@1 1/4 Panel",
shape_groups = { panel = 1, legacy = 1 },
eighths = 1, -- 1/8 nodes, complement of panel_12 to make slab_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0.5, -0.25, 0.5 },
},
})
stairsplus.api.register_shape("panel_8", {
name_format = "panel_%s_8",
aliases = { "panel_%s", "panel_bottom_%s" },
description = "@1 1/2 Panel",
shape_groups = { panel = 1, legacy = 1 },
eighths = 2, -- 1/4 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0.5, 0, 0.5 },
},
})
stairsplus.api.register_shape("panel_12", {
name_format = "panel_%s_12",
description = "@1 3/4 Panel",
shape_groups = { panel = 1, legacy = 1 },
eighths = 3, -- 3/8 nodes, complement of panel_4 to make slab_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0.5, 0.25, 0.5 },
},
})
stairsplus.api.register_shape("panel_14", {
name_format = "panel_%s_14",
description = "@1 7/8 Panel",
shape_groups = { panel = 1, legacy = 1 },
eighths = 3, -- 7/16 nodes, complement of panel_2 to make slab_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0.5, 0.375, 0.5 },
},
})
stairsplus.api.register_shape("panel_15", {
name_format = "panel_%s_15",
description = "@1 15/16 Panel",
shape_groups = { panel = 1, legacy = 1 },
eighths = 3, -- 15/32 nodes, complement of panel_1 to make slab_8
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, 0, 0.5, 0.4375, 0.5 },
},
})

View file

@ -0,0 +1,135 @@
stairsplus.api.register_shape("slab_1", {
name_format = "slab_%s_1",
description = "@1 1/16 Slab",
shape_groups = { slab = 1, legacy = 1 },
eighths = 1, -- 1/16 nodes, complement of slab_15 to make node
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, -0.4375, 0.5 },
},
})
stairsplus.api.register_shape("slab_2", {
name_format = "slab_%s_2",
description = "@1 1/8 Slab",
shape_groups = { slab = 1, legacy = 1 },
eighths = 1, -- 1/8 nodes, complement of slab_14 to make node
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, -0.375, 0.5 },
},
})
stairsplus.api.register_shape("slab_4", {
name_format = "slab_%s_4",
aliases = { "slab_%s_quarter" },
description = "@1 1/4 Slab",
shape_groups = { slab = 1, legacy = 1 },
eighths = 2, -- 1/4 nodes, complement of slab_12 to make node
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, -0.25, 0.5 },
},
})
stairsplus.api.register_shape("slab_8", {
name_format = "slab_%s_8",
aliases = { "slab_%s" },
description = "@1 1/2 Slab",
shape_groups = { slab = 1, legacy = 1, stairs_legacy = 1 },
eighths = 4, -- 1/2 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, 0, 0.5 },
},
})
stairsplus.api.register_shape("slab_12", {
name_format = "slab_%s_12",
aliases = { "slab_%s_three_quarter" },
description = "@1 3/4 Slab",
shape_groups = { slab = 1, legacy = 1 },
eighths = 6, -- 3/4 nodes, complement of slab_4 to make node
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, 0.25, 0.5 },
},
})
stairsplus.api.register_shape("slab_14", {
name_format = "slab_%s_14",
description = "@1 7/8 Slab",
shape_groups = { slab = 1, legacy = 1 },
eighths = 7, -- 7/8 nodes, complement of slab_2 to make node
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, 0.375, 0.5 },
},
})
stairsplus.api.register_shape("slab_15", {
name_format = "slab_%s_15",
description = "@1 15/16 Slab",
shape_groups = { slab = 1, legacy = 1 },
eighths = 7, -- 15/16 nodes, complement of slab_1 to make node
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, 0.4375, 0.5 },
},
})
--- these terrible things
stairsplus.api.register_shape("slab_two_sides", {
name_format = "slab_%s_two_sides",
description = "@1 1/16 Slab Two Sides",
shape_groups = { slab = 1, legacy = 1 },
eighths = 1, -- 31/256 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, -7 / 16, 7 / 16 },
{ -0.5, -0.5, 7 / 16, 0.5, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("slab_three_sides", {
name_format = "slab_%s_three_sides",
description = "@1 1/16 Slab Three Sides",
shape_groups = { slab = 1, legacy = 1 },
eighths = 1, -- 361/2048 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -7 / 16, -0.5, -0.5, 0.5, -7 / 16, 7 / 16 },
{ -7 / 16, -0.5, 7 / 16, 0.5, 0.5, 0.5 },
{ -0.5, -0.5, -0.5, -7 / 16, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("slab_three_sides_u", {
name_format = "slab_%s_three_sides_u",
description = "@1 1/16 Slab Three Sides U",
shape_groups = { slab = 1, legacy = 1 },
eighths = 1, -- 23/128 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.5, -7 / 16 },
{ -0.5, -0.5, -7 / 16, 0.5, -7 / 16, 7 / 16 },
{ -0.5, -0.5, 7 / 16, 0.5, 0.5, 0.5 },
},
},
})

View file

@ -0,0 +1,274 @@
local box_slope = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, -0.25, 0.5 },
{ -0.5, -0.25, -0.25, 0.5, 0, 0.5 },
{ -0.5, 0, 0, 0.5, 0.25, 0.5 },
{ -0.5, 0.25, 0.25, 0.5, 0.5, 0.5 },
},
}
local box_slope_half = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, -0.375, 0.5 },
{ -0.5, -0.375, -0.25, 0.5, -0.25, 0.5 },
{ -0.5, -0.25, 0, 0.5, -0.125, 0.5 },
{ -0.5, -0.125, 0.25, 0.5, 0, 0.5 },
},
}
local box_slope_half_raised = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.125, 0.5 },
{ -0.5, 0.125, -0.25, 0.5, 0.25, 0.5 },
{ -0.5, 0.25, 0, 0.5, 0.375, 0.5 },
{ -0.5, 0.375, 0.25, 0.5, 0.5, 0.5 },
},
}
local box_slope_inner = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, -0.25, 0.5 },
{ -0.5, -0.5, -0.25, 0.5, 0, 0.5 },
{ -0.5, -0.5, -0.5, 0.25, 0, 0.5 },
{ -0.5, 0, -0.5, 0, 0.25, 0.5 },
{ -0.5, 0, 0, 0.5, 0.25, 0.5 },
{ -0.5, 0.25, 0.25, 0.5, 0.5, 0.5 },
{ -0.5, 0.25, -0.5, -0.25, 0.5, 0.5 },
},
}
local box_slope_inner_half = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, -0.375, 0.5 },
{ -0.5, -0.375, -0.25, 0.5, -0.25, 0.5 },
{ -0.5, -0.375, -0.5, 0.25, -0.25, 0.5 },
{ -0.5, -0.25, -0.5, 0, -0.125, 0.5 },
{ -0.5, -0.25, 0, 0.5, -0.125, 0.5 },
{ -0.5, -0.125, 0.25, 0.5, 0, 0.5 },
{ -0.5, -0.125, -0.5, -0.25, 0, 0.5 },
},
}
local box_slope_inner_half_raised = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.125, 0.5 },
{ -0.5, 0.125, -0.25, 0.5, 0.25, 0.5 },
{ -0.5, 0.125, -0.5, 0.25, 0.25, 0.5 },
{ -0.5, 0.25, -0.5, 0, 0.375, 0.5 },
{ -0.5, 0.25, 0, 0.5, 0.375, 0.5 },
{ -0.5, 0.375, 0.25, 0.5, 0.5, 0.5 },
{ -0.5, 0.375, -0.5, -0.25, 0.5, 0.5 },
},
}
local box_slope_outer = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, -0.25, 0.5 },
{ -0.5, -0.25, -0.25, 0.25, 0, 0.5 },
{ -0.5, 0, 0, 0, 0.25, 0.5 },
{ -0.5, 0.25, 0.25, -0.25, 0.5, 0.5 },
},
}
local box_slope_outer_half = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, -0.375, 0.5 },
{ -0.5, -0.375, -0.25, 0.25, -0.25, 0.5 },
{ -0.5, -0.25, 0, 0, -0.125, 0.5 },
{ -0.5, -0.125, 0.25, -0.25, 0, 0.5 },
},
}
local box_slope_outer_half_raised = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.125, 0.5 },
{ -0.5, 0.125, -0.25, 0.25, 0.25, 0.5 },
{ -0.5, 0.25, 0, 0, 0.375, 0.5 },
{ -0.5, 0.375, 0.25, -0.25, 0.5, 0.5 },
},
}
stairsplus.api.register_shape("slope", {
name_format = "slope_%s",
description = "@1 Slope",
shape_groups = { slope = 1, legacy = 1 },
eighths = 4, -- 1/2 node
drawtype = "mesh",
mesh = "stairsplus_slope.obj",
collision_box = box_slope,
selection_box = box_slope,
})
stairsplus.api.register_shape("slope_half", {
name_format = "slope_%s_half",
description = "@1 1/2 Slope",
shape_groups = { slope = 1, legacy = 1 },
eighths = 2, -- 1/4 node, complement of slope_half_raised
drawtype = "mesh",
mesh = "stairsplus_slope_half.obj",
collision_box = box_slope_half,
selection_box = box_slope_half,
})
stairsplus.api.register_shape("slope_half_raised", {
name_format = "slope_%s_half_raised",
description = "@1 1/2 Slope Raised",
shape_groups = { slope = 1, legacy = 1 },
eighths = 6, -- 3/4 node, complement of slope_half
drawtype = "mesh",
mesh = "stairsplus_slope_half_raised.obj",
collision_box = box_slope_half_raised,
selection_box = box_slope_half_raised,
})
stairsplus.api.register_shape("slope_inner", {
name_format = "slope_%s_inner",
description = "@1 Slope Inner",
shape_groups = { slope = 1, legacy = 1 },
eighths = 5, -- 2/3 nodes, complement of slope_outer
drawtype = "mesh",
mesh = "stairsplus_slope_inner.obj",
collision_box = box_slope_inner,
selection_box = box_slope_inner,
})
stairsplus.api.register_shape("slope_outer", {
name_format = "slope_%s_outer",
description = "@1 Slope Outer",
shape_groups = { slope = 1, legacy = 1 },
eighths = 3, -- 1/3 nodes, complement of slope_inner
drawtype = "mesh",
mesh = "stairsplus_slope_outer.obj",
collision_box = box_slope_outer,
selection_box = box_slope_outer,
})
stairsplus.api.register_shape("slope_inner_cut", {
name_format = "slope_%s_inner_cut",
description = "@1 Slope Inner Cut",
shape_groups = { slope = 1, legacy = 1 },
eighths = 7, -- 5/6 nodes, complement of slope_outer_cut
drawtype = "mesh",
mesh = "stairsplus_slope_inner_cut.obj",
collision_box = box_slope_inner,
selection_box = box_slope_inner,
})
stairsplus.api.register_shape("slope_outer_cut", {
name_format = "slope_%s_outer_cut",
description = "@1 Slope Outer Cut",
shape_groups = { slope = 1, legacy = 1 },
eighths = 1, -- 1/6 nodes, complement of slope_inner_cut
drawtype = "mesh",
mesh = "stairsplus_slope_outer_cut.obj",
collision_box = box_slope_outer,
selection_box = box_slope_outer,
})
stairsplus.api.register_shape("slope_inner_half", {
name_format = "slope_%s_inner_half",
description = "@1 Slope Inner Half",
shape_groups = { slope = 1, legacy = 1 },
eighths = 3, -- 1/3 nodes, complement of slope_outer_half to make slab_8
drawtype = "mesh",
mesh = "stairsplus_slope_inner_half.obj",
collision_box = box_slope_inner_half,
selection_box = box_slope_inner_half,
})
stairsplus.api.register_shape("slope_outer_half", {
name_format = "slope_%s_outer_half",
description = "@1 Slope Outer Half",
shape_groups = { slope = 1, legacy = 1 },
eighths = 1, -- 1/6 nodes, complement of slope_inner_half to make slab_8
drawtype = "mesh",
mesh = "stairsplus_slope_outer_half.obj",
collision_box = box_slope_outer_half,
selection_box = box_slope_outer_half,
})
stairsplus.api.register_shape("slope_inner_cut_half", {
name_format = "slope_%s_inner_cut_half",
description = "@1 Slope Inner Cut Half",
shape_groups = { slope = 1, legacy = 1 },
eighths = 3, -- 11/12 nodes, complement of slope_outer_cut_half to make slab_8
drawtype = "mesh",
mesh = "stairsplus_slope_inner_cut_half.obj",
collision_box = box_slope_inner_half,
selection_box = box_slope_inner_half,
})
stairsplus.api.register_shape("slope_outer_cut_half", {
name_format = "slope_%s_outer_cut_half",
description = "@1 Slope Outer Cut Half",
shape_groups = { slope = 1, legacy = 1 },
eighths = 1, -- 1/12 nodes, complement of slope_inner_cut_half to make slab_8
drawtype = "mesh",
mesh = "stairsplus_slope_outer_cut_half.obj",
collision_box = box_slope_outer_half,
selection_box = box_slope_outer_half,
})
stairsplus.api.register_shape("slope_inner_half_raised", {
name_format = "slope_%s_inner_half_raised",
description = "@1 Slope Inner Half Raised",
shape_groups = { slope = 1, legacy = 1 },
eighths = 7, -- 5/6 nodes, complement of slope_outer_half to make node
drawtype = "mesh",
mesh = "stairsplus_slope_inner_half_raised.obj",
collision_box = box_slope_inner_half_raised,
selection_box = box_slope_inner_half_raised,
})
stairsplus.api.register_shape("slope_inner_cut_half_raised", {
name_format = "slope_%s_inner_cut_half_raised",
description = "@1 Slope Inner Cut Half Raised",
shape_groups = { slope = 1, legacy = 1 },
eighths = 7, -- 11/12 nodes, complement of slope_outer_cut_half to make node
drawtype = "mesh",
mesh = "stairsplus_slope_inner_cut_half_raised.obj",
collision_box = box_slope_inner_half_raised,
selection_box = box_slope_inner_half_raised,
})
stairsplus.api.register_shape("slope_outer_half_raised", {
name_format = "slope_%s_outer_half_raised",
description = "@1 Slope Outer Half Raised",
shape_groups = { slope = 1, legacy = 1 },
eighths = 5, -- 2/3 nodes, BUT slab_8 + slope_outer_half, complement of slope_inner_half to make node
drawtype = "mesh",
mesh = "stairsplus_slope_outer_half_raised.obj",
collision_box = box_slope_outer_half_raised,
selection_box = box_slope_outer_half_raised,
})
stairsplus.api.register_shape("slope_outer_cut_half_raised", {
name_format = "slope_%s_outer_cut_half_raised",
description = "@1 Slope Outer Cut Half Raised",
shape_groups = { slope = 1, legacy = 1 },
eighths = 3, -- 1/3 nodes, complement of slope_outer_cut_half to make slope
drawtype = "mesh",
mesh = "stairsplus_slope_outer_cut_half_raised.obj",
collision_box = box_slope_outer_half_raised,
selection_box = box_slope_outer_half_raised,
})
stairsplus.api.register_shape("slope_cut", {
name_format = "slope_%s_cut",
description = "@1 Slope Cut",
shape_groups = { slope = 1, legacy = 1 },
eighths = 4, -- 1/2 nodes, self complement
drawtype = "mesh",
mesh = "stairsplus_slope_cut.obj",
collision_box = box_slope_outer,
selection_box = box_slope_outer,
})

View file

@ -0,0 +1,141 @@
stairsplus.api.register_shape("stair_half", {
name_format = "stair_%s_half",
description = "@1 Half Stair",
shape_groups = { stair = 1, legacy = 1 },
eighths = 3, -- 3/8 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0, 0, 0.5 },
{ -0.5, 0, 0, 0, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("stair_right_half", {
name_format = "stair_%s_right_half",
description = "@1 Right Half Stair",
shape_groups = { stair = 1, legacy = 1 },
eighths = 3, -- 3/8 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ 0, -0.5, -0.5, 0.5, 0, 0.5 },
{ 0, 0, 0, 0.5, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("stair", {
name_format = "stair_%s",
description = "@1 Stair",
groups = { morelights_supports_stairlight = 1 },
shape_groups = { stair = 1, legacy = 1, stairs_legacy = 1 },
eighths = 6, -- 3/4 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0, 0.5 },
{ -0.5, 0, 0, 0.5, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("stair_inner", {
name_format = "stair_%s_inner",
description = "@1 Inner Stair",
shape_groups = { stair = 1, legacy = 1, stairs_legacy = 1 },
eighths = 7, -- 7/8 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0, 0.5 },
{ -0.5, 0, 0, 0.5, 0.5, 0.5 },
{ -0.5, 0, -0.5, 0, 0.5, 0 },
},
},
})
stairsplus.api.register_shape("stair_outer", {
name_format = "stair_%s_outer",
description = "@1 Outer Stair",
shape_groups = { stair = 1, legacy = 1, stairs_legacy = 1 },
eighths = 5, -- 5/8 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0, 0.5 },
{ -0.5, 0, 0, 0, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("stair_alt_1", {
name_format = "stair_%s_alt_1",
description = "@1 1/16 Alt Stair",
groups = { morelights_supports_stairlight = 1 },
shape_groups = { stair = 1, legacy = 1 },
eighths = 1, -- 1/16 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.0625, -0.5, 0.5, 0, 0 },
{ -0.5, 0.4375, 0, 0.5, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("stair_alt_2", {
name_format = "stair_%s_alt_2",
description = "@1 1/8 Alt Stair",
groups = { morelights_supports_stairlight = 1 },
shape_groups = { stair = 1, legacy = 1 },
eighths = 1, -- 1/8 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.125, -0.5, 0.5, 0, 0 },
{ -0.5, 0.375, 0, 0.5, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("stair_alt_4", {
name_format = "stair_%s_alt_4",
description = "@1 1/4 Alt Stair",
groups = { morelights_supports_stairlight = 1 },
shape_groups = { stair = 1, legacy = 1 },
eighths = 2, -- 1/4 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.25, -0.5, 0.5, 0, 0 },
{ -0.5, 0.25, 0, 0.5, 0.5, 0.5 },
},
},
})
stairsplus.api.register_shape("stair_alt_8", {
name_format = "stair_%s_alt_8",
aliases = { "stair_%s_alt" },
description = "@1 1/2 Alt Stair",
groups = { morelights_supports_stairlight = 1 },
shape_groups = { stair = 1, legacy = 1 },
eighths = 4, -- 1/2 nodes
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0, 0 },
{ -0.5, 0, 0, 0.5, 0.5, 0.5 },
},
},
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,12 @@
local util = {}
local table_is_empty = futil.table.is_empty
function util.item_has_metadata(item)
item = type(item) == "userdata" and item or ItemStack(item)
local meta = item:get_meta()
return not table_is_empty(meta:to_table().fields)
end
stairsplus.util = util