diff --git a/mods/3d_armor_flyswim/.gitattributes b/mods/3d_armor_flyswim/.gitattributes
new file mode 100644
index 00000000..dfe07704
--- /dev/null
+++ b/mods/3d_armor_flyswim/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/mods/3d_armor_flyswim/LICENSE.txt b/mods/3d_armor_flyswim/LICENSE.txt
new file mode 100644
index 00000000..a5d55867
--- /dev/null
+++ b/mods/3d_armor_flyswim/LICENSE.txt
@@ -0,0 +1,9 @@
+[mod] 3d_armor_flyswim
+=======================
+Licence code LGPL v2.1
+"Headanim" code by LoneWolfHT MIT Licence
+Cape Textures - CC0
+Blender Model/B3Ds as per base MTG - CC BY-SA 3.0
+"3d_armor_trans.png" CC-BY-SA 3.0
+
+
diff --git a/mods/3d_armor_flyswim/README.MD b/mods/3d_armor_flyswim/README.MD
new file mode 100644
index 00000000..ccd1d585
--- /dev/null
+++ b/mods/3d_armor_flyswim/README.MD
@@ -0,0 +1,147 @@
+ _, __, _, __, _, _ _, __,
+ ~_) | \ / \ |_) |\/| / \ |_)
+ _) |_/ |~| | \ | | \ / | \
+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
+ __, _, , _ _ _, _ _, _, _ _ _ _, _ _, _ _ _, _ _,
+ |_ | \ | | |\ | / _ (_ | | | |\/| |\/| | |\ | / _
+ | |_, \| | | \| \ / , ) |/\| | | | | | | | \| \ /
+ ~ ~ ~ ) ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
+ ~'
+
+---------------------------
+## Information
+---------------------------
+This is a small utility mod which adds some new animations to the default animations found in player_api animations, the new animations are:
+
+ |Animation| Start | End | FPS |
+ |---------|-------|-----|-----|
+ |Swim | 246 | 279 | 30 |
+ |Swim Atk | 285 | 318 | 30 |
+ |Fly | 325 | 334 | 30 |
+ |Fly Atk | 340 | 349 | 30 |
+ |Fall | 355 | 364 | 30 |
+ |Fall Atk | 365 | 374 | 30 |
+ |Duck Std | 380 | 380 | 30 |
+ |Duck | 381 | 399 | 30 |
+ |Climb | 410 | 429 | 30 |
+
+
+
+I have done my best to trigger the animations in the correct circumstances when viewing the player model in 3rd person.
+
+I have only tested this against minetest versions 5.0 to 5.6
+
+Mod now works with just player_api, 3d_armor is an optional depends.
+
+Works with simple_skins - https://forum.minetest.net/viewtopic.php?f=11&t=9100
+
+Works with skinsdb - https://forum.minetest.net/viewtopic.php?t=17899
+
+Works with clothing 2 - https://forum.minetest.net/viewtopic.php?p=395157
+
+When using skinsdb capes must be supplied by clothing 2.
+
+
+
+---------------------------
+## Mod explanations and interactions
+---------------------------
+**player_api**
+fly,swim,crouch,climb animations work, enabling capes will do nothing as you cant wear them
+
+Below here assumed you stil have player_api enabled/installed:
+
+**simple skins**
+fly,swim,crouch,climb animations work, enabling capes will do nothing as you cant wear them
+
+**3d_armor**
+You have the option to have capes on or off upto you, capes are considered armor
+
+**3d_armor and simple_skins**
+You have the option to have capes on or off upto you, capes are considered armor
+
+**skinsdb**
+Fly,swim,crouch,climb animations work, enabling capes will do nothing as you cant wear them
+
+**3d_armor and skinsdb**
+Fly,swim,crouch,climb animations work you can enable capes but they wont be visible on your character but will still provide any bonuses
+
+**3d_armor, skinsdb and clothing 2**
+Fly,swim,crouch,climb animations work, visual cape provided by clothing_2 however can also enable capes but they wont be visible and it's a bit strange wearing two capes. So recommened left disabled.
+
+
+---------------------------
+## Animation rules/triggers
+---------------------------
+Swimming - You must be in at least 2 nodes depth of liquid/flowing liquid and moving otherwise your character will simply wade through the liquid or float (stand). If you sink close to the bottom while submerged in the liquid your character will automatically stand on the bottom.
+
+Swim through - Hold shift down while swimming, you will sink but will also now be able to swim through 1x1 tunnels.
+
+Flying - Character/player must have fly_privs you must have at least 2 nodes of airlike nodes between you and the ground otherwise your character will simply/stand or walk ready to land. You also need some type of movement horizontally to trigger the animation otherwise your character will simply stand in the air to emulate hovering.
+
+Falling - Character/player must have 4 nodes of airlike nodes between them and the ground. Easiest to trigger if fly_privs have been removed but you can fall with fly_privs. Max falling speed is set by terminal velocity, however holding shift down will let you exceed terminal velocity.
+
+Crouching - Press shift while standing in the open and then walk forward.
+
+Crouching under - Simply walk through a gap 1.5 nodes tall. Player/character must be facing the space and walking forwards.
+
+
+---------------------------
+## Turn off Animation or Crouch Rule
+---------------------------
+From MT Main Menu go to "Settings" then "All Settings" then "Mods" then "3d_armor_flyswim"
+
+**capes_add_to_3darmor** - Default is enabled - Makes capes an armor item avaliable via 3d_armor (1 test cape included)
+
+**example_cape** - Default is enabled - The example cape "Someones Cape" is avaliable which grants fly_privs when worn and a 100% speed increase
+
+**fly_anim** - Default is enabled - Will show the flying animation
+
+**fall_anim** - Default is enabled - Will show the falling animation
+
+**fall_tv** - Default is 100 - This is terminal velocity, 100 represents approximatly 100kp/h however player/character speed will oscilate around this speed. Without this limit when falling characters simply accelerate endlessly (until they hit chunk load edge).
+
+**swim_anim** - Default is enabled - Will show swim animation
+
+**swim_sneak** - Default is enabled - Will allow character/player to swim through a hole 1x1 in size when underwater.
+
+**climb_anim** - Default is enabled - Will show climb animation
+
+**crouch_anim** - Default is enabled - Will show crouch/duck animation
+
+**crouch_sneak** - Default is enabled - Will allow character/player to walk through a gap 1.5 nodes high when on land.
+
+---------------------------
+## Why are Capes Included?
+---------------------------
+I found it best to keep capes included in this mod with the option to enable or disable. This is because capes needs the new b3d player model so they
+are displayed as part of armor instead of part of the player. However as I didn't want to force anyone into using capes as armor items I created a second
+optional player model which keeps capes with the player textures.
+
+The above would create a circular dependancy if capes was it's own mod. Capes would have a dependency on Fly/Swim but Fly/Swim needs to know if Capes mod
+is present so as to load the correct b3d player model.
+
+Given the above I have kept capes inside this mod with an option to enable/disable it under Settings>>All Settings>>Mods>>3d_armor_flyswim by default capes are set to Enabled/true
+
+Capes provide minimal additional armor, about half as much as wooden boots by default.
+
+--------------------------------------------
+## What nodes are set as Flyable/Swimmable?
+--------------------------------------------
+Any node which has the drawtype set as "airlike", "liquid" and "flowingliquid" will automatically be flyable or swimmable.
+
+Big thanks to Gundul for pointing out a better way to do this.
+
+--------------------------------------------
+## Headanimation is incorporated
+--------------------------------------------
+I have incorporated "headanim" mod content by LoneWolfHT as it was easier to include and then customise for this mod than try to interface
+with "headanim". Full credit to LoneWolfHT for the functionality. I did modify the functionality a little so visually when in 3rd person view
+Sams head will:
+
+~ Regular animations when looking down Sams chin now rests on his chest (about a 60 degree angle).
+~ Regular animations when looking up Sam only bends head back to the same 60 degrees.
+~ Swimming and flying you can look down full 90 degrees, straight ahead 0 degrees, however head motion is restricted to 30 degrees back.
+
+Make sure you disable "headanim" if you have it installed, although I found no serious issues with both enabled the mods
+could fight one another for control of head position.
\ No newline at end of file
diff --git a/mods/3d_armor_flyswim/description.txt b/mods/3d_armor_flyswim/description.txt
new file mode 100644
index 00000000..b033c5be
--- /dev/null
+++ b/mods/3d_armor_flyswim/description.txt
@@ -0,0 +1 @@
+Addition of Fly and Swimming animations to 3d_armour base character model, used when swimming and flyinf or falling avaliable for other mods to make use of.
diff --git a/mods/3d_armor_flyswim/i_example_cape.lua b/mods/3d_armor_flyswim/i_example_cape.lua
new file mode 100644
index 00000000..c33c6daa
--- /dev/null
+++ b/mods/3d_armor_flyswim/i_example_cape.lua
@@ -0,0 +1,28 @@
+------------------------------------------------------------
+-- ___ _ __ ___ _ --
+-- | __| |_ _ / _|___ / __|_ __ _(_)_ __ --
+-- | _|| | || | > _|_ _| \__ \ V V / | ' \ --
+-- |_| |_|\_, | \_____| |___/\_/\_/|_|_|_|_| --
+-- |__/ --
+-- Crouch and Climb --
+------------------------------------------------------------
+-- Example Cape --
+------------------------------------------------------------
+ armor:register_armor("3d_armor_flyswim:demo_cape", {
+ description = "Someones Cape",
+ inventory_image = "3d_armor_flyswim_demo_cape_inv.png",
+ groups = {armor_capes=1, physics_speed=1, armor_use=1000},
+ armor_groups = {fleshy=5},
+ damage_groups = {cracky=3, snappy=3, choppy=2, crumbly=2, level=1},
+ on_equip = function(player)
+ local privs = minetest.get_player_privs(player:get_player_name())
+ privs.fly = true
+ minetest.set_player_privs(player:get_player_name(), privs)
+ end,
+
+ on_unequip = function(player)
+ local privs = minetest.get_player_privs(player:get_player_name())
+ privs.fly = nil
+ minetest.set_player_privs(player:get_player_name(), privs)
+ end,
+ })
diff --git a/mods/3d_armor_flyswim/i_functions.lua b/mods/3d_armor_flyswim/i_functions.lua
new file mode 100644
index 00000000..48185fcf
--- /dev/null
+++ b/mods/3d_armor_flyswim/i_functions.lua
@@ -0,0 +1,186 @@
+------------------------------------------------------------
+-- ___ _ __ ___ _ --
+-- | __| |_ _ / _|___ / __|_ __ _(_)_ __ --
+-- | _|| | || | > _|_ _| \__ \ V V / | ' \ --
+-- |_| |_|\_, | \_____| |___/\_/\_/|_|_|_|_| --
+-- |__/ --
+-- Crouch and Climb --
+------------------------------------------------------------
+-- Functions --
+------------------------------------------------------------
+
+----------------------------------------
+-- Get Player model and textures
+
+function armor_fly_swim.get_player_model()
+
+ -- player_api only (simple_skins uses)
+ local player_mod = "character_sf.b3d"
+ local texture = {"character.png",
+ "3d_armor_trans.png"}
+
+ -- 3d_armor only nil capes (simple_skins uses)
+ if armor_fly_swim.is_3d_armor and
+ not armor_fly_swim.add_capes and
+ not armor_fly_swim.is_skinsdb then
+
+ player_mod = "3d_armor_character_sf.b3d"
+ texture = {armor.default_skin..".png",
+ "3d_armor_trans.png",
+ "3d_armor_trans.png"}
+ end
+
+ -- 3d_armor only with capes (simple_skins uses)
+ if armor_fly_swim.is_3d_armor and
+ armor_fly_swim.add_capes and
+ not armor_fly_swim.is_skinsdb then
+
+ player_mod = "3d_armor_character_sfc.b3d"
+ texture = {armor.default_skin..".png",
+ "3d_armor_trans.png",
+ "3d_armor_trans.png"}
+ end
+
+ -- skins_db with 3d_armor or without
+ if armor_fly_swim.is_skinsdb then
+ player_mod = "skinsdb_3d_armor_character_5.b3d"
+ texture = {"blank.png",
+ "blank.png",
+ "blank.png",
+ "blank.png"}
+ end
+
+ return player_mod,texture
+end
+
+----------------------------------------
+-- Get WASD, pressed = true
+
+function armor_fly_swim.get_wasd_state(controls)
+
+ local rtn = false
+
+ if controls.up == true or
+ controls.down == true or
+ controls.left == true or
+ controls.right == true then
+
+ rtn = true
+ end
+
+ return rtn
+end
+
+----------------------------------------
+-- Check specific node fly/swim
+-- 1=player feet, 2=one below feet,
+-- Thanks Gundul
+
+function node_fsable(pos,num,type)
+
+ local draw_ta = {"airlike"}
+ local draw_tl = {"liquid","flowingliquid"}
+ local compare = draw_ta
+ local node = minetest.get_node({x=pos.x,y=pos.y-(num-1),z=pos.z})
+ local n_draw
+
+ if minetest.registered_nodes[node.name] then
+ n_draw = minetest.registered_nodes[node.name].drawtype
+ else
+ n_draw = "normal"
+ end
+
+ if type == "s" then
+ compare = draw_tl
+ end
+
+ for k,v in ipairs(compare) do
+ if n_draw == v then
+ return true
+ end
+ end
+ return false
+end
+
+-----------------------------------------------
+-- Check X number nodes down fly/Swimmable
+
+function node_down_fsable(pos,num,type)
+
+local draw_ta = {"airlike"}
+local draw_tl = {"liquid","flowingliquid"}
+local i = 0
+local nodes = {}
+local result ={}
+local compare = draw_ta
+ while (i < num ) do
+ table.insert(nodes, minetest.get_node({x=pos.x,y=pos.y-i,z=pos.z}))
+ i=i+1
+ end
+
+ if type == "s" then
+ compare = draw_tl
+ end
+
+ local n_draw
+ for k,v in pairs(nodes) do
+ local n_draw
+
+ if minetest.registered_nodes[v.name] then
+ n_draw = minetest.registered_nodes[v.name].drawtype
+ else
+ n_draw = "normal"
+ end
+
+ for k2,v2 in ipairs(compare) do
+ if n_draw == v2 then
+ table.insert(result,"t")
+ end
+ end
+ end
+
+ if #result == num then
+ return true
+ else
+ return false
+ end
+end
+
+------------------------------------------
+-- Workaround for slab edge crouch
+
+function crouch_wa(player,pos)
+ local is_slab = 0
+ local pos_w = {}
+ local angle = (player:get_look_horizontal())*180/math.pi -- Convert Look direction to angles
+
+ -- +Z North
+ if angle <= 45 or angle >= 315 then
+ pos_w={x=pos.x,y=pos.y+1,z=pos.z+1}
+
+ -- -X West
+ elseif angle > 45 and angle < 135 then
+ pos_w={x=pos.x-1,y=pos.y+1,z=pos.z}
+
+ -- -Z South
+ elseif angle >= 135 and angle <= 225 then
+ pos_w={x=pos.x,y=pos.y+1,z=pos.z-1}
+
+ -- +X East
+ elseif angle > 225 and angle < 315 then
+ pos_w={x=pos.x+1,y=pos.y+1,z=pos.z}
+ end
+
+ local check = minetest.get_node(pos_w)
+
+ if minetest.registered_nodes[check.name] then
+ local check_g = minetest.get_item_group(check.name, "slab")
+
+ if check_g ~= 0 then
+ is_slab = 1
+ end
+ end
+
+ -- return 1 or 0, need to update to bool
+ return is_slab
+end
diff --git a/mods/3d_armor_flyswim/init.lua b/mods/3d_armor_flyswim/init.lua
new file mode 100644
index 00000000..9ca9a49f
--- /dev/null
+++ b/mods/3d_armor_flyswim/init.lua
@@ -0,0 +1,386 @@
+------------------------------------------------------------
+-- ___ _ __ ___ _ --
+-- | __| |_ _ / _|___ / __|_ __ _(_)_ __ --
+-- | _|| | || | > _|_ _| \__ \ V V / | ' \ --
+-- |_| |_|\_, | \_____| |___/\_/\_/|_|_|_|_| --
+-- |__/ --
+-- Crouch and Climb --
+------------------------------------------------------------
+-- Sirrobzeroone --
+-- Licence code LGPL v2.1 --
+-- Cape Textures - CC0 --
+-- Blender Model/B3Ds as per base MTG - CC BY-SA 3.0 --
+-- except "3d_armor_trans.png" CC-BY-SA 3.0 --
+------------------------------------------------------------
+
+----------------------------
+-- Settings
+
+armor_fly_swim = {}
+
+local modname = minetest.get_current_modname()
+local modpath = minetest.get_modpath(modname)
+
+armor_fly_swim.add_capes = minetest.settings:get_bool("capes_add_to_3darmor" ,false)
+armor_fly_swim.example_cape = minetest.settings:get_bool("example_cape" ,false)
+
+local fly_anim = minetest.settings:get_bool("fly_anim" ,true)
+local fall_anim = minetest.settings:get_bool("fall_anim" ,true)
+local fall_tv = tonumber(minetest.settings:get("fall_tv" ,true)) or 100
+ -- Convert kp/h back to number of -y blocks per 0.05 of a second.
+ fall_tv = -1*(fall_tv/3.7)
+local swim_anim = minetest.settings:get_bool("swim_anim" ,true)
+local swim_sneak = minetest.settings:get_bool("swim_sneak" ,true)
+local climb_anim = minetest.settings:get_bool("climb_anim" ,true)
+local crouch_anim = minetest.settings:get_bool("crouch_anim" ,true)
+local crouch_sneak = minetest.settings:get_bool("crouch_sneak" ,true)
+
+-----------------------
+-- Conditional mods
+
+armor_fly_swim.is_3d_armor = minetest.get_modpath("3d_armor")
+armor_fly_swim.is_skinsdb = minetest.get_modpath("skinsdb")
+
+-------------------------------------
+-- Adding new armor item for Capes
+
+if armor_fly_swim.add_capes == true then
+ if minetest.global_exists("armor") and armor.elements then
+ table.insert(armor.elements, "capes")
+ end
+end
+
+----------------------------
+-- Initiate files
+
+dofile(modpath .. "/i_functions.lua")
+
+if armor_fly_swim.example_cape and
+ armor_fly_swim.add_capes and
+ armor_fly_swim.is_3d_armor then
+
+ dofile(modpath .. "/i_example_cape.lua")
+end
+-------------------------------------
+-- Get Player model to use
+
+local player_mod, texture = armor_fly_swim.get_player_model()
+
+--------------------------------------
+-- Player model with Swim/Fly/Capes
+
+player_api.register_model(player_mod, {
+ animation_speed = 30,
+ textures = texture,
+ animations = {
+ stand = {x=0, y=79},
+ lay = {x=162, y=166},
+ walk = {x=168, y=187},
+ mine = {x=189, y=198},
+ walk_mine = {x=200, y=219},
+ sit = {x=81, y=160},
+ swim = {x=246, y=279},
+ swim_atk = {x=285, y=318},
+ fly = {x=325, y=334},
+ fly_atk = {x=340, y=349},
+ fall = {x=355, y=364},
+ fall_atk = {x=365, y=374},
+ duck_std = {x=380, y=380},
+ duck = {x=381, y=399},
+ climb = {x=410, y=429},
+ },
+})
+----------------------------------------
+-- Setting model on join and clearing
+-- local_animations
+
+minetest.register_on_joinplayer(function(player)
+ player_api.set_model(player,player_mod)
+ player_api.player_attached[player:get_player_name()] = false
+ player:set_local_animation({},{},{},{},30)
+
+end)
+
+------------------------------------------------
+-- Global step to check if player meets --
+-- Conditions for Swimming, Flying(falling) --
+-- Crouching or Climbing --
+------------------------------------------------
+minetest.register_globalstep(function()
+ for _, player in pairs(minetest.get_connected_players()) do
+ local controls = player:get_player_control()
+ local controls_wasd = armor_fly_swim.get_wasd_state(controls)
+ local attached_to = player:get_attach()
+ local privs = minetest.get_player_privs(player:get_player_name())
+ local pos = player:get_pos()
+ local pmeta = player:get_meta()
+ local cur_anim = player_api.get_animation(player)
+ local ladder = {}
+ ladder.n = {is = false, pos = pos}
+ ladder.n_a = {is = false, pos = {x=pos.x,y=pos.y +1,z=pos.z}}
+ ladder.n_b = {is = false, pos = {x=pos.x,y=pos.y -1,z=pos.z}}
+ local is_slab = crouch_wa(player,pos)
+ local attack = ""
+ local ani_spd = 30
+ local offset = 0
+ local tdebug = false
+
+ -- reset player collisionbox, eye height, speed override
+ player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}})
+ player:set_properties({eye_height = 1.47})
+
+ -- used to store and reset the players physics.speed settings
+ -- back to what they were before fly_swim adjusted them
+ if pmeta:get_int("flyswim_std_under_slab") == 1 then
+ player:set_physics_override({speed = pmeta:get_float("flyswim_org_phy_or")})
+ pmeta:set_int("flyswim_std_under_slab", 0)
+ end
+
+ local vel = player:get_velocity()
+
+ -- basically 3D Pythagorean Theorem km/h
+ local play_s = (math.sqrt(math.pow(math.abs(vel.x),2) +
+ math.pow(math.abs(vel.y),2) +
+ math.pow(math.abs(vel.z),2) ))*3.6
+
+ -- Sets terminal velocity to about 100Km/hr beyond
+ -- this speed chunk load issues become more noticable
+ --(-1*(vel.y+1)) - catch those holding shift and over
+ -- acceleratering when falling so dynamic end point
+ -- so player dosent bounce back up
+ if vel.y < fall_tv and controls.sneak ~= true then
+ local tv_offset_y = -1*((-1*(vel.y+1)) + vel.y)
+ player:add_player_velocity({x=0, y=tv_offset_y, z=0})
+ end
+
+ -- Check for Swinging/attacking and set string
+ if controls.LMB or controls.RMB then
+ attack = "_atk"
+ end
+
+ -- get ladder pos node
+ local t_node = minetest.registered_nodes[minetest.get_node(ladder.n.pos).name]
+ if t_node and t_node.climbable then
+ ladder.n.is = true
+ end
+
+ ---------------------------------------------------------
+ -- Start of Animation Cases --
+ ---------------------------------------------------------
+ -------------------------------------
+ -- Crouch Slab/Node Exception Case
+
+ -- If player still udner slab/swimming in tunnel
+ -- and they let go of shift this stops them
+ -- standing up
+ local node_check = minetest.get_node({x=pos.x,y=pos.y+1,z=pos.z})
+ local nc_draw = "normal"
+ local nc_slab = 0
+ local nc_node = 0
+
+ if minetest.registered_nodes[node_check.name] then
+
+ nc_slab = minetest.get_item_group(node_check.name, "slab")
+ nc_draw = minetest.registered_nodes[node_check.name].drawtype
+
+ if nc_draw ~= "liquid" and
+ nc_draw ~= "flowingliquid" and
+ nc_draw ~= "airlike" then
+
+ nc_node = 1
+ end
+ end
+
+ if crouch_sneak and
+ nc_slab == 1 and
+ not attached_to and
+ not controls.sneak and
+ not node_fsable(pos,2,"a") then
+
+ local animiation = "duck_std"
+
+ -- when player moving
+ if controls_wasd then
+ local play_or_2 =player:get_physics_override()
+ pmeta:set_int("flyswim_std_under_slab", 1)
+ pmeta:set_float("flyswim_org_phy_or", play_or_2.speed)
+ player:set_physics_override({speed = play_or_2.speed*0.2})
+
+ animation = "duck"
+ end
+
+ if crouch_anim == true then
+ player_api.set_animation(player, animation ,ani_spd/2)
+ end
+
+ player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.45, 0.3}})
+ player:set_properties({eye_height = 1.27})
+ if tdebug then minetest.debug("crouch catch") end
+
+ elseif swim_sneak and
+ nc_node == 1 and
+ node_down_fsable(pos,1,"s") and
+ not attached_to and
+ not controls.sneak then
+
+ player_api.set_animation(player, "swim",ani_spd)
+ player:set_properties({collisionbox = {-0.4, 0, -0.4, 0.4, 0.5, 0.4}})
+ player:set_properties({eye_height = 0.7})
+ offset = 90
+ if tdebug then minetest.debug("swim through catch") end
+
+ -----------------------------
+ -- Climb
+ elseif climb_anim and
+ ladder.n.is and
+ (controls.jump or controls.sneak) then
+
+ -- Moved inside climb to save unessecary node checking
+ for k,def in pairs(ladder) do
+ if k ~= "n" then
+ local node = minetest.registered_nodes[minetest.get_node(def.pos).name]
+ if node and node.climbable then
+ def.is = true
+ end
+ end
+ end
+
+ if (controls.sneak and ladder.n_b.is) or
+ (controls.jump and ladder.n_a.is) then
+
+ player_api.set_animation(player, "climb",ani_spd)
+ if tdebug then minetest.debug("climb") end
+ end
+
+ -----------------------------
+ -- Swim
+
+ elseif swim_anim == true and
+ controls_wasd and
+ node_down_fsable(pos,2,"s") and
+ not attached_to then
+
+ player_api.set_animation(player,"swim"..attack ,ani_spd)
+ offset = 90
+ if tdebug then minetest.debug("swim") end
+
+ elseif swim_sneak == true and
+ swim_anim == true and
+ controls.sneak and
+ node_down_fsable(pos,1,"s") and
+ not attached_to then
+
+ player_api.set_animation(player, "swim",ani_spd)
+ player:set_properties({collisionbox = {-0.4, 0, -0.4, 0.4, 0.5, 0.4}})
+ player:set_properties({eye_height = 0.7})
+ offset = 90
+ if tdebug then minetest.debug("swim through") end
+ -----------------------------
+ -- Sneak
+
+ -- first elseif Crouch-walk workaround.
+ -- First slab player enters counts as a true slab
+ -- and has an edge. As such the shift edge detection
+ -- kicks in and player can't move forwards. This
+ -- case sets the player collision box to 1 high for that first slab
+
+ elseif crouch_anim and
+ controls.sneak and
+ controls.up and
+ not node_fsable(pos,2,"a") and
+ not attached_to and
+ play_s <= 1 and is_slab == 1 then
+
+ if crouch_anim == true then
+ player_api.set_animation(player, "duck",ani_spd/2)
+ player:set_properties({eye_height = 1.27})
+ end
+
+ if crouch_sneak == true then
+ -- Workaround set collision box to 1 high
+ player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.0, 0.3}})
+ end
+ if tdebug then minetest.debug("crouch_1") end
+
+ elseif crouch_anim and
+ controls.sneak and
+ not node_fsable(pos,2,"a") and
+ not attached_to then
+
+ local animation = "duck_std"
+ if controls_wasd then animation = "duck" end
+
+ player_api.set_animation(player, animation, ani_spd/2)
+ player:set_properties({eye_height = 1.27})
+
+ if crouch_sneak == true then
+ player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.45, 0.3}})
+ end
+ if tdebug then minetest.debug("crouch_2") end
+
+ -----------------------------
+ -- Flying
+
+ elseif fly_anim == true and
+ privs.fly == true and
+ node_down_fsable(pos,3,"a") and
+ not attached_to then
+
+ -- Vel.y value is a compromise for code simplicity,
+ -- Flyers wont get fall animation until below -18m/s
+ if controls_wasd then
+ player_api.set_animation(player, "fly"..attack, ani_spd)
+ offset = 90
+ if tdebug then minetest.debug("fly") end
+
+ elseif fall_anim == true and
+ vel.y < -18.0 then
+ player_api.set_animation(player, "fall"..attack, ani_spd)
+ offset = 90
+ if tdebug then minetest.debug("fly_fall") end
+ end
+
+ -----------------------------
+ -- Falling
+
+ elseif fall_anim == true and
+ node_down_fsable(pos,5,"a") and
+ vel.y < -0.5 and
+ not attached_to then
+
+ player_api.set_animation(player, "fall"..attack, ani_spd)
+ offset = 90
+ if tdebug then minetest.debug("fall") end
+ end
+
+ ---------------------------------------------------------
+ -- Post MT 5.3 Head Animation --
+ ---------------------------------------------------------
+ -- this function was added in 5.3 which has the bone position
+ -- change break animations fix - i think (MT #9807)
+ -- I'm not too sure how to directly test for the bone fix/ MT version
+ -- so I simply check for this function.
+ local check_v = minetest.is_creative_enabled
+
+ if check_v ~= nil then
+
+ local look_degree = -math.deg(player:get_look_vertical())
+
+ if look_degree > 29 and offset ~= 0 then
+ offset = offset - (look_degree-30)
+
+ elseif look_degree > 60 and offset == 0 then
+ offset = offset - (look_degree-60)
+
+ elseif look_degree < -60 and offset == 0 then
+ offset = offset - (look_degree+60)
+ end
+
+ -- Code by LoneWolfHT - Headanim mod MIT Licence --
+ player:set_bone_position("Head", vector.new(0, 6.35, 0),vector.new(look_degree + offset, 0, 0))
+ -- Code by LoneWolfHT - Headanim mod MIT Licence --
+ end
+ --minetest.chat_send_all(play_s.." km/h") -- for diagnosing chunk emerge issues when falling currently unsolved
+
+ end
+end)
\ No newline at end of file
diff --git a/mods/3d_armor_flyswim/mod.conf b/mods/3d_armor_flyswim/mod.conf
new file mode 100644
index 00000000..bd51ad3e
--- /dev/null
+++ b/mods/3d_armor_flyswim/mod.conf
@@ -0,0 +1,8 @@
+name = 3d_armor_flyswim
+description = Adds Flying, Swimming, Crouching and Climbing animations to base character model for 3d_armor
+depends = player_api
+optional_depends = 3d_armor, simple_skins, skinsdb, clothing
+author = sirrobzeroone
+title = Add Fly, Swim, Crouch and Climb animations
+
+release = 13392
diff --git a/mods/3d_armor_flyswim/models/3d_armor_character.b3d b/mods/3d_armor_flyswim/models/3d_armor_character.b3d
new file mode 100644
index 00000000..f2431c25
Binary files /dev/null and b/mods/3d_armor_flyswim/models/3d_armor_character.b3d differ
diff --git a/mods/3d_armor_flyswim/models/3d_armor_character_sf.b3d b/mods/3d_armor_flyswim/models/3d_armor_character_sf.b3d
new file mode 100644
index 00000000..ba366ba0
Binary files /dev/null and b/mods/3d_armor_flyswim/models/3d_armor_character_sf.b3d differ
diff --git a/mods/3d_armor_flyswim/models/3d_armor_character_sf.blend b/mods/3d_armor_flyswim/models/3d_armor_character_sf.blend
new file mode 100644
index 00000000..9fcda5a4
Binary files /dev/null and b/mods/3d_armor_flyswim/models/3d_armor_character_sf.blend differ
diff --git a/mods/3d_armor_flyswim/models/3d_armor_character_sf.blend1 b/mods/3d_armor_flyswim/models/3d_armor_character_sf.blend1
new file mode 100644
index 00000000..e4969658
Binary files /dev/null and b/mods/3d_armor_flyswim/models/3d_armor_character_sf.blend1 differ
diff --git a/mods/3d_armor_flyswim/models/3d_armor_character_sfc.b3d b/mods/3d_armor_flyswim/models/3d_armor_character_sfc.b3d
new file mode 100644
index 00000000..f2431c25
Binary files /dev/null and b/mods/3d_armor_flyswim/models/3d_armor_character_sfc.b3d differ
diff --git a/mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend b/mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend
new file mode 100644
index 00000000..93f9ad41
Binary files /dev/null and b/mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend differ
diff --git a/mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend1 b/mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend1
new file mode 100644
index 00000000..9fa1b796
Binary files /dev/null and b/mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend1 differ
diff --git a/mods/3d_armor_flyswim/models/character_sf.b3d b/mods/3d_armor_flyswim/models/character_sf.b3d
new file mode 100644
index 00000000..a3ab9d27
Binary files /dev/null and b/mods/3d_armor_flyswim/models/character_sf.b3d differ
diff --git a/mods/3d_armor_flyswim/models/character_sf.blend b/mods/3d_armor_flyswim/models/character_sf.blend
new file mode 100644
index 00000000..c4e70793
Binary files /dev/null and b/mods/3d_armor_flyswim/models/character_sf.blend differ
diff --git a/mods/3d_armor_flyswim/models/character_sf.blend1 b/mods/3d_armor_flyswim/models/character_sf.blend1
new file mode 100644
index 00000000..9bb650fc
Binary files /dev/null and b/mods/3d_armor_flyswim/models/character_sf.blend1 differ
diff --git a/mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.b3d b/mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.b3d
new file mode 100644
index 00000000..501209a2
Binary files /dev/null and b/mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.b3d differ
diff --git a/mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.blend b/mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.blend
new file mode 100644
index 00000000..32e2fc41
Binary files /dev/null and b/mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.blend differ
diff --git a/mods/3d_armor_flyswim/screenshot.png b/mods/3d_armor_flyswim/screenshot.png
new file mode 100644
index 00000000..cede16ea
Binary files /dev/null and b/mods/3d_armor_flyswim/screenshot.png differ
diff --git a/mods/3d_armor_flyswim/settingtypes.txt b/mods/3d_armor_flyswim/settingtypes.txt
new file mode 100644
index 00000000..0d7d77ed
--- /dev/null
+++ b/mods/3d_armor_flyswim/settingtypes.txt
@@ -0,0 +1,32 @@
+# Make capes part of 3d Armor when enabled
+capes_add_to_3darmor (Add Capes to 3d Armor) bool true
+
+# Disable the Example Cape "Someones Cape"
+example_cape (The Example Cape "Someones Cape" which grants fly_privs when worn and a 100% speed increase) bool true
+
+# Enable Fly Animation
+fly_anim (Enable fly animation) bool true
+
+# Enable Fall Animation
+fall_anim (Enable fall animation) bool true
+
+# Terminal Velocity Speed kp/h
+fall_tv (Set terminal velocity speed - recommend not exceeding 100 kp/h) int 100
+
+# Enable Swim Animation
+swim_anim (Enable swim animation) bool true
+
+# Enable Swim Sneak - swim through 1x1 hole
+swim_sneak (Enable swim sneak) bool true
+
+# Enable Climb Animation
+climb_anim (Enable climb animation) bool true
+
+# Enable Crouch/Duck Animation
+crouch_anim (Enable crouch/duck animation) bool true
+
+# Enable Crouch/Duck - through through 1.5 hole
+crouch_sneak (Enable crouch/duck animation) bool true
+
+
+
diff --git a/mods/3d_armor_flyswim/swimming_animated.gif b/mods/3d_armor_flyswim/swimming_animated.gif
new file mode 100644
index 00000000..0849f390
Binary files /dev/null and b/mods/3d_armor_flyswim/swimming_animated.gif differ
diff --git a/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape.png b/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape.png
new file mode 100644
index 00000000..7f16dc02
Binary files /dev/null and b/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape.png differ
diff --git a/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape_inv.png b/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape_inv.png
new file mode 100644
index 00000000..dd921553
Binary files /dev/null and b/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape_inv.png differ
diff --git a/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape_preview.png b/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape_preview.png
new file mode 100644
index 00000000..789dd1e9
Binary files /dev/null and b/mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape_preview.png differ
diff --git a/mods/3d_armor_flyswim/textures/3d_armor_trans.png b/mods/3d_armor_flyswim/textures/3d_armor_trans.png
new file mode 100644
index 00000000..4a31242b
Binary files /dev/null and b/mods/3d_armor_flyswim/textures/3d_armor_trans.png differ
diff --git a/mods/3d_armor_flyswim/textures/screenshot.xcf b/mods/3d_armor_flyswim/textures/screenshot.xcf
new file mode 100644
index 00000000..61b31eb2
Binary files /dev/null and b/mods/3d_armor_flyswim/textures/screenshot.xcf differ
diff --git a/mods/bike/README.txt b/mods/bike/README.txt
new file mode 100644
index 00000000..826d8486
--- /dev/null
+++ b/mods/bike/README.txt
@@ -0,0 +1,15 @@
+Minetest mod: bike
+========================
+See license.txt for license information.
+
+Authors of source code
+----------------------
+Originally by PilzAdam (MIT)
+Various Minetest developers and contributors (MIT)
+GreenDimond (MIT)
+Hume2 (MIT)
+
+Authors of media (textures and model)
+-------------------------------------
+Textures: GreenDimond (MIT)
+Model: GreenDimond (MIT)
diff --git a/mods/bike/init.lua b/mods/bike/init.lua
new file mode 100644
index 00000000..3c377d66
--- /dev/null
+++ b/mods/bike/init.lua
@@ -0,0 +1,879 @@
+local S = minetest.get_translator(minetest.get_current_modname())
+
+--[[ Helpers ]]--
+
+-- Skin mod detection
+local skin_mod
+
+local skin_mods = {"skinsdb", "skins", "u_skins", "simple_skins", "wardrobe"}
+
+-- local settings
+local setting_max_speed = tonumber(minetest.settings:get("bike_max_speed")) or 6.9
+local setting_acceleration = tonumber(minetest.settings:get("bike_acceleration")) or 3.16
+local setting_decceleration = tonumber(minetest.settings:get("bike_decceleration")) or 7.9
+local setting_friction = tonumber(minetest.settings:get("bike_friction")) or 0.79
+local setting_turn_speed = tonumber(minetest.settings:get("bike_turn_speed")) or 1.58
+local setting_friction_cone = tonumber(minetest.settings:get("bike_friction_cone")) or 0.4
+local agility_factor = 1/math.sqrt(setting_friction_cone)
+local setting_wheely_factor = tonumber(minetest.settings:get("bike_wheely_factor")) or 2.0
+local setting_stepheight = tonumber(minetest.settings:get("bike_stepheight")) or 0.6
+local setting_wheely_stepheight = tonumber(minetest.settings:get("bike_wheely_stepheight")) or 0.6
+local setting_water_friction = tonumber(minetest.settings:get("bike_water_friction")) or 13.8
+local setting_offroad_friction = tonumber(minetest.settings:get("bike_offroad_friction")) or 1.62
+
+-- general settings
+local setting_turn_look = minetest.settings:get_bool("mount_turn_player_look", false)
+local setting_ownable = minetest.settings:get_bool("mount_ownable", false)
+
+--[[ Crafts ]]--
+
+minetest.register_craftitem("bike:wheel", {
+ description = S("Bike Wheel"),
+ inventory_image = "bike_wheel.png",
+})
+
+minetest.register_craftitem("bike:handles", {
+ description = S("Bike Handles"),
+ inventory_image = "bike_handles.png",
+})
+
+-- Set item names according to installed mods
+local rubber = "group:wood"
+local iron = "default:steel_ingot"
+local red_dye = "dye:red"
+local green_dye = "dye:green"
+local blue_dye = "dye:blue"
+local container = "default:glass"
+
+if minetest.get_modpath("technic") ~= nil then
+ rubber = "technic:rubber"
+end
+
+if minetest.get_modpath("vessels") ~= nil then
+ container = "vessels:glass_bottle"
+end
+
+if minetest.get_modpath("mcl_core") ~= nil then
+ iron = "mcl_core:iron_ingot"
+ container = "mcl_core:glass"
+end
+
+if minetest.get_modpath("mcl_rubber") ~= nil then
+ rubber = "mcl_rubber:rubber"
+end
+
+if minetest.get_modpath("mcl_dye") ~= nil then
+ red_dye = "mcl_dye:red"
+ green_dye = "mcl_dye:green"
+ blue_dye = "mcl_dye:blue"
+end
+
+if minetest.get_modpath("mcl_potions") ~= nil then
+ container = "mcl_potions:glass_bottle"
+end
+
+for _, mod in pairs(skin_mods) do
+ local path = minetest.get_modpath(mod)
+ if path then
+ skin_mod = mod
+ end
+end
+
+local function get_player_skin(player)
+ local name = player:get_player_name()
+ local armor_tex = ""
+ if minetest.global_exists("armor") then
+ -- Filter out helmet (for bike helmet) and boots/shield (to not mess up UV mapping)
+ local function filter(str, find)
+ for _,f in pairs(find) do
+ str = str:gsub("%^"..f.."_(.-.png)", "")
+ end
+ return str
+ end
+ armor_tex = filter("^"..armor.textures[name].armor, {"shields_shield", "3d_armor_boots", "3d_armor_helmet"})
+ end
+ -- Return the skin with armor (if applicable)
+ if skin_mod == "skinsdb" then
+ return "[combine:64x32:0,0="..skins.get_player_skin(player):get_texture()..armor_tex
+ elseif (skin_mod == "skins" or skin_mod == "simple_skins") and skins.skins[name] then
+ return skins.skins[name]..".png"..armor_tex
+ elseif skin_mod == "u_skins" and u_skins.u_skins[name] then
+ return u_skins.u_skins[name]..".png"..armor_tex
+ elseif skin_mod == "wardrobe" and wardrobe.playerSkins and wardrobe.playerSkins[name] then
+ return wardrobe.playerSkins[name]..armor_tex
+ end
+ local skin = player:get_properties().textures[1]
+ -- If we just have 3d_armor enabled make sure we get the player skin properly
+ if minetest.global_exists("armor") and armor.get_player_skin then
+ skin = armor:get_player_skin(name)
+ end
+ return skin..armor_tex
+end
+
+-- Bike metal texture handling
+local function is_hex(color)
+ if color:len() ~= 7 then return nil end
+ return color:match("#%x%x%x%x%x%x")
+end
+
+local function colormetal(color, alpha)
+ return "bike_metal_base.png^[colorize:"..(color)..":"..tostring(alpha)
+end
+
+-- Terrain checkers
+local function is_water(pos)
+ local nn = minetest.get_node(pos).name
+ return minetest.get_item_group(nn, "liquid") ~= 0
+end
+
+local function is_bike_friendly(pos)
+ local nn = minetest.get_node(pos).name
+ return minetest.get_item_group(nn, "crumbly") == 0 or minetest.get_item_group(nn, "bike_friendly") ~= 0
+end
+
+-- Maths
+local function get_sign(i)
+ if i == 0 then
+ return 0
+ else
+ return i / math.abs(i)
+ end
+end
+
+local function get_velocity(v, yaw, y)
+ local x = -math.sin(yaw) * v
+ local z = math.cos(yaw) * v
+ return {x = x, y = y, z = z}
+end
+
+local function get_v(v)
+ return math.sqrt(v.x ^ 2 + v.z ^ 2)
+end
+
+-- Custom hand
+minetest.register_node("bike:hand", {
+ description = "",
+ -- No interaction on a bike :)
+ range = 0,
+ on_place = function(itemstack, placer, pointed_thing)
+ return ItemStack("bike:hand "..itemstack:get_count())
+ end,
+ -- Copy default:hand looks so it doesnt look as weird when the hands are switched
+ wield_image = minetest.registered_items[""].wield_image,
+ wield_scale = minetest.registered_items[""].wield_scale,
+ node_placement_prediction = "",
+})
+
+--[[ Bike ]]--
+
+-- Default textures (overidden when mounted or colored)
+local function default_tex(metaltex, alpha)
+ return {
+ "bike_metal_grey.png",
+ "bike_gear.png",
+ colormetal(metaltex, alpha),
+ "bike_leather.png",
+ "bike_chain.png",
+ "bike_metal_grey.png",
+ "bike_leather.png",
+ "bike_metal_black.png",
+ "bike_metal_black.png",
+ "bike_blank.png",
+ "bike_tread.png",
+ "bike_gear.png",
+ "bike_spokes.png",
+ "bike_tread.png",
+ "bike_spokes.png",
+ }
+end
+
+-- Entity
+local bike = {
+ physical = true,
+ -- Warning: Do not change the position of the collisionbox top surface,
+ -- lowering it causes the bike to fall through the world if underwater
+ collisionbox = {-0.5, -0.4, -0.5, 0.5, 0.8, 0.5},
+ collide_with_objects = false,
+ visual = "mesh",
+ mesh = "bike.b3d",
+ textures = default_tex("#FFFFFF", 150),
+ stepheight = setting_stepheight,
+ driver = nil,
+ color = "#FFFFFF",
+ alpha = 150,
+ old_driver = {},
+ v = 0, -- Current velocity
+ last_v = 0, -- Last velocity
+ max_v = setting_max_speed, -- Max velocity
+ fast_v = 0, -- Fast adder
+ f_speed = 30, -- Frame speed
+ last_y = 0, -- Last height
+ up = false, -- Are we going up?
+ timer = 0,
+ removed = false,
+ __is_bike__ = true,
+}
+
+-- Dismont the player
+local function dismount_player(bike, exit)
+ bike.object:set_velocity({x = 0, y = 0, z = 0})
+ -- Make the bike empty again
+ bike.object:set_properties({textures = default_tex(bike.color, bike.alpha)})
+ bike.v = 0
+
+ if bike.driver then
+ bike.driver:set_detach()
+ -- Reset original player properties
+ bike.driver:set_properties({visual_size=bike.old_driver["vsize"]})
+ bike.driver:set_eye_offset(bike.old_driver.eye_offset.offset_first,
+ bike.old_driver.eye_offset.offset_third)
+ bike.driver:hud_set_flags(bike.old_driver["hud"])
+ local pinv = bike.driver:get_inventory()
+ if pinv then
+ pinv:set_stack("hand", 1, pinv:get_stack("old_hand", 1))
+ if minetest.global_exists("mcl_skins") then
+ local node_id = mcl_skins.get_node_id_by_player(bike.driver)
+ pinv:set_stack("hand", 1, "mcl_meshhand:" .. node_id)
+ end
+ end
+ -- Is the player leaving? If so, dont do this stuff or Minetest will have a fit
+ if not exit then
+ local pos = bike.driver:get_pos()
+ pos = {x = pos.x, y = pos.y + 0.2, z = pos.z}
+ bike.driver:set_pos(pos)
+ end
+ bike.driver = nil
+ end
+end
+
+-- Mounting
+function bike.on_rightclick(self, clicker)
+ if not clicker or not clicker:is_player() or clicker:get_attach() ~= nil then
+ return
+ end
+ if not self.driver then
+ local pname = clicker:get_player_name()
+ if setting_ownable and self.owner and pname ~= self.owner then
+ minetest.chat_send_player(pname, "You cannot ride " .. self.owner .. "'s bike.")
+ return
+ end
+
+ -- Make integrated player appear
+ self.object:set_properties({
+ textures = {
+ "bike_metal_grey.png",
+ "bike_gear.png",
+ colormetal(self.color, self.alpha),
+ "bike_leather.png",
+ "bike_chain.png",
+ "bike_metal_grey.png",
+ "bike_leather.png",
+ "bike_metal_black.png",
+ "bike_metal_black.png",
+ get_player_skin(clicker).."^bike_helmet.png",
+ "bike_tread.png",
+ "bike_gear.png",
+ "bike_spokes.png",
+ "bike_tread.png",
+ "bike_spokes.png",
+ },
+ })
+ -- Save the player's properties that we need to change
+ self.old_driver["vsize"] = clicker:get_properties().visual_size
+ local offset_first, offset_third = clicker:get_eye_offset()
+ self.old_driver.eye_offset = {
+ offset_first = offset_first,
+ offset_third = offset_third
+ }
+ self.old_driver["hud"] = clicker:hud_get_flags()
+ clicker:get_inventory():set_stack("old_hand", 1, clicker:get_inventory():get_stack("hand", 1))
+ -- Change the hand
+ clicker:get_inventory():set_stack("hand", 1, "bike:hand")
+ local attach = clicker:get_attach()
+ if attach and attach:get_luaentity() then
+ local luaentity = attach:get_luaentity()
+ if luaentity.driver then
+ luaentity.driver = nil
+ end
+ clicker:set_detach()
+ end
+ self.driver = clicker
+ -- Set new properties and hide HUD
+ clicker:set_properties({visual_size = {x=0,y=0}})
+ clicker:set_attach(self.object, "body", {x = 0, y = 10, z = 5}, {x = 0, y = 0, z = 0})
+ clicker:set_eye_offset({x=0,y=-3,z=10},{x=0,y=0,z=5})
+ clicker:hud_set_flags({
+ hotbar = false,
+ wielditem = false,
+ })
+ -- Look forward initially
+ clicker:set_look_horizontal(self.object:get_yaw())
+ end
+end
+
+function bike.on_activate(self, staticdata, dtime_s)
+ self.object:set_acceleration({x = 0, y = -9.8, z = 0})
+ self.object:set_armor_groups({immortal = 1})
+ if staticdata ~= "" then
+ local data = minetest.deserialize(staticdata)
+ if data ~= nil then
+ self.v = data.v
+ self.color = data.color
+ self.alpha = data.alpha
+ self.owner = data.owner
+ end
+ end
+ self.object:set_properties({textures=default_tex(self.color, self.alpha)})
+ self.last_v = self.v
+end
+
+-- Save velocity and color data for reload
+function bike.get_staticdata(self)
+ local data = {
+ v = self.v,
+ color = self.color,
+ alpha = self.alpha,
+ owner = self.owner,
+ }
+ return minetest.serialize(data)
+end
+
+-- Pick up/color
+function bike.on_punch(self, puncher)
+ local itemstack = puncher:get_wielded_item()
+ -- Bike painting
+ if itemstack:get_name() == "bike:painter" then
+ -- No painting while someone is riding :P
+ if self.driver then
+ return
+ end
+ -- Get color data
+ local meta = itemstack:get_meta()
+ self.color = meta:get_string("paint_color")
+ self.alpha = meta:get_string("alpha")
+ self.object:set_properties({
+ textures = {
+ "bike_metal_grey.png",
+ "bike_gear.png",
+ colormetal(self.color, self.alpha),
+ "bike_leather.png",
+ "bike_chain.png",
+ "bike_metal_grey.png",
+ "bike_leather.png",
+ "bike_metal_black.png",
+ "bike_metal_black.png",
+ "bike_blank.png",
+ "bike_tread.png",
+ "bike_gear.png",
+ "bike_spokes.png",
+ "bike_tread.png",
+ "bike_spokes.png",
+ },
+ })
+ return
+ end
+ if not puncher or not puncher:is_player() or self.removed then
+ return
+ end
+ -- Make sure no one is riding
+ if not self.driver then
+ local pname = puncher:get_player_name()
+ if setting_ownable and self.owner and pname ~= self.owner then
+ minetest.chat_send_player(pname, "You cannot take " .. self.owner .. "'s bike.")
+ return
+ end
+
+ local inv = puncher:get_inventory()
+ -- We can only carry one bike
+ if not inv:contains_item("main", "bike:bike") then
+ local stack = ItemStack({name="bike:bike", count=1, wear=0})
+ local meta = stack:get_meta()
+ -- Set the stack to the bike color
+ meta:set_string("color", self.color)
+ meta:set_string("alpha", self.alpha)
+ local leftover = inv:add_item("main", stack)
+ -- If no room in inventory add the bike to the world
+ if not leftover:is_empty() then
+ minetest.add_item(self.object:get_pos(), leftover)
+ end
+ else
+ -- Turn it into raw materials
+ if not (creative and creative.is_enabled_for(pname)) then
+ local ctrl = puncher:get_player_control()
+ if not ctrl.sneak then
+ minetest.chat_send_player(pname, "Warning: Destroying the bike gives you only some resources back. If you are sure, hold sneak while destroying the bike.")
+ return
+ end
+ local leftover = inv:add_item("main", iron .. " 6")
+ -- If no room in inventory add the iron to the world
+ if not leftover:is_empty() then
+ minetest.add_item(self.object:get_pos(), leftover)
+ end
+ end
+ end
+ self.removed = true
+ -- Delay remove to ensure player is detached
+ minetest.after(0.1, function()
+ self.object:remove()
+ end)
+ end
+end
+
+-- Animations
+local function bike_anim(self)
+ -- The `self.object:get_animation().y ~= ` is to check if the animation is already running
+ if self.driver then
+ local ctrl = self.driver:get_player_control()
+ -- Wheely
+ if ctrl.jump then
+ -- We are moving
+ if self.v > 0 then
+ if self.object:get_animation().y ~= 79 then
+ self.object:set_animation({x=59,y=79}, self.f_speed + self.fast_v, 0, true)
+ end
+ return
+ -- Else we are not
+ else
+ if self.object:get_animation().y ~= 59 then
+ self.object:set_animation({x=59,y=59}, self.f_speed + self.fast_v, 0, true)
+ end
+ return
+ end
+ end
+
+ -- Left or right tilt, but only if we arent doing a wheely
+ local t_left = false
+ local t_right = false
+
+ if not setting_turn_look then
+ t_left = ctrl.left
+ t_right = ctrl.right
+ else
+ local turning = math.floor(self.driver:get_look_horizontal() * 100)
+ - math.floor(self.object:get_yaw() * 100)
+
+ -- use threshold of 1 to determine if animation should be updated
+ t_left = turning > 1
+ t_right = turning < -1
+ end
+
+ if t_left then
+ if self.object:get_animation().y ~= 58 then
+ self.object:set_animation({x=39,y=58}, self.f_speed + self.fast_v, 0, true)
+ end
+ return
+ elseif t_right then
+ if self.object:get_animation().y ~= 38 then
+ self.object:set_animation({x=19,y=38}, self.f_speed + self.fast_v, 0, true)
+ end
+ return
+ end
+ end
+ -- If none of that, then we are just moving forward
+ if self.v > 0 then
+ if self.object:get_animation().y ~= 18 then
+ self.object:set_animation({x=0,y=18}, 30, 0, true)
+ end
+ return
+ -- Or not
+ else
+ if self.object:get_animation().y ~= 0 then
+ self.object:set_animation({x=0,y=0}, 0, 0, false)
+ end
+ end
+end
+
+-- Run every tick
+function bike.on_step(self, dtime, moveresult)
+ -- Player checks
+ if self.driver then
+ -- Is the actual player somehow still visible?
+ local p_prop = self.driver:get_properties()
+ if p_prop and p_prop.visual_size ~= {x=0,y=0} then
+ self.driver:set_properties({visual_size = {x=0,y=0}})
+ end
+
+ -- Has the player left?
+ if self.driver:get_attach() == nil then
+ dismount_player(self, true)
+ return
+ end
+ end
+
+ -- Have we come to a sudden stop?
+ if math.abs(self.last_v - self.v) > 3 then
+ -- And is Minetest not being dumb
+ if not self.up then
+ minetest.log("info", "[bike] forcing stop")
+ self.v = 0
+ end
+ end
+
+ self.last_v = self.v
+
+ self.timer = self.timer + dtime;
+ if self.timer >= 0.5 then
+ -- Recording y values to check if we are going up
+ self.last_y = self.object:get_pos().y
+ self.timer = 0
+ end
+
+ -- Are we going up?
+ if self.last_y < self.object:get_pos().y then
+ self.up = true
+ else
+ self.up = false
+ end
+
+ -- Run animations
+ bike_anim(self)
+
+ -- Are we falling?
+ if self.object:get_velocity().y < -10 and self.driver ~= nil then
+ -- If so, dismount
+ dismount_player(self)
+ return
+ end
+
+ local current_v = get_v(self.object:get_velocity()) * get_sign(self.v)
+ self.v = (current_v + self.v*3) / 4
+ if self.driver then
+ local ctrl = self.driver:get_player_control()
+ local yaw = self.object:get_yaw()
+ local agility = 0
+
+ -- Sneak dismount
+ if ctrl.sneak then
+ dismount_player(self)
+ return
+ end
+
+ if self.v > setting_friction_cone then
+ agility = 1/math.sqrt(self.v)/agility_factor
+ else
+ agility = 1.0
+ end
+
+ -- Forward
+ if ctrl.up then
+ -- Are we going fast?
+ if ctrl.aux1 then
+ if self.fast_v ~= 5 then
+ self.fast_v = 5
+ end
+ else
+ if self.fast_v > 0 then
+ self.fast_v = self.fast_v - 0.05 * agility
+ end
+ end
+ self.v = self.v + dtime*setting_acceleration + (self.fast_v*0.1) * agility
+ -- Brakes
+ elseif ctrl.down then
+ self.v = self.v - dtime*setting_decceleration * agility
+ if self.fast_v > 0 then
+ self.fast_v = self.fast_v - dtime*setting_friction * agility
+ end
+ -- Nothin'
+ else
+ self.v = self.v - dtime*setting_friction * agility
+ if self.fast_v > 0 then
+ self.fast_v = self.fast_v - dtime*setting_friction * agility
+ end
+ end
+
+ -- Wheely will change turning speed
+ local turn_speed = setting_turn_speed
+
+ -- Are we doing a wheely?
+ if ctrl.jump then
+ turn_speed = setting_wheely_factor * setting_turn_speed
+
+ if self.stepheight ~= setting_wheely_stepheight then
+ self.object:set_properties({stepheight=setting_wheely_stepheight})
+ self.stepheight = setting_wheely_stepheight
+ end
+ elseif self.stepheight ~= setting_stepheight then
+ self.object:set_properties({stepheight=setting_stepheight})
+ self.stepheight = setting_stepheight
+ end
+
+ -- Turning
+ if not setting_turn_look then
+ if ctrl.left then
+ self.object:set_yaw(yaw + turn_speed*dtime * agility)
+ elseif ctrl.right then
+ self.object:set_yaw(yaw - turn_speed*dtime * agility)
+ end
+ else
+ self.object:set_yaw(self.driver:get_look_horizontal())
+ end
+ end
+ -- Movement
+ local velo = self.object:get_velocity()
+ if self.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
+ self.object:move_to(self.object:get_pos())
+ return
+ end
+ local s = get_sign(self.v)
+ if s ~= get_sign(self.v) then
+ self.object:set_velocity({x = 0, y = 0, z = 0})
+ self.v = 0
+ return
+ end
+ if self.v > self.max_v + self.fast_v then
+ self.v = self.max_v + self.fast_v
+ elseif self.v < 0 then
+ self.v = 0
+ end
+
+ local p = self.object:get_pos()
+ if is_water(p) then
+ self.v = self.v / math.pow(setting_water_friction, dtime)
+ end
+
+ -- Can we ride good here?
+ local bike_friendly = true
+ for _, collision in ipairs(moveresult.collisions) do
+ if collision.type == "node" and collision.axis == "y" then
+ if not is_bike_friendly(collision.node_pos) then
+ bike_friendly = false
+ break
+ end
+ end
+ end
+ if not bike_friendly then
+ self.v = self.v / math.pow(setting_offroad_friction, dtime)
+ end
+
+ local new_velo
+ new_velo = get_velocity(self.v, self.object:get_yaw(), self.object:get_velocity().y)
+ self.object:move_to(self.object:get_pos())
+ self.object:set_velocity(new_velo)
+end
+
+-- Check for stray bike hand
+minetest.register_on_joinplayer(function(player)
+ local inv = player:get_inventory()
+ if inv:get_stack("hand", 1):get_name() == "bike:hand" then
+ inv:set_stack("hand", 1, inv:get_stack("old_hand", 1))
+ if minetest.global_exists("mcl_skins") then
+ local node_id = mcl_skins.get_node_id_by_player(player)
+ inv:set_stack("hand", 1, "mcl_meshhand:" .. node_id)
+ end
+ end
+end)
+
+-- Dismount all players on server shutdown
+minetest.register_on_shutdown(function()
+ for _, e in pairs(minetest.luaentities) do
+ if (e.__is_bike__ and e.driver ~= nil) then
+ dismount_player(e, true)
+ end
+ end
+end)
+
+-- Register the entity
+minetest.register_entity("bike:bike", bike)
+
+-- Bike craftitem
+minetest.register_craftitem("bike:bike", {
+ description = S("Bike"),
+ inventory_image = "bike_inventory.png",
+ wield_scale = {x = 3, y = 3, z = 2},
+ groups = {flammable = 2},
+ stack_max = 1,
+ on_place = function(itemstack, placer, pointed_thing)
+ local under = pointed_thing.under
+ local node = minetest.get_node(under)
+ local udef = minetest.registered_nodes[node.name]
+ if udef and udef.on_rightclick and
+ not (placer and placer:is_player() and
+ placer:get_player_control().sneak) then
+ return udef.on_rightclick(under, node, placer, itemstack,
+ pointed_thing) or itemstack
+ end
+
+ if pointed_thing.type ~= "node" then
+ return itemstack
+ end
+
+ local meta = itemstack:get_meta()
+ local sdata = {
+ v = 0,
+ -- Place bike with saved color
+ color = meta:get_string("color"),
+ alpha = tonumber(meta:get_string("alpha")),
+ -- Store owner
+ owner = placer:get_player_name(),
+ }
+
+ -- If it's a new bike, give it default colors
+ if sdata.alpha == nil then
+ sdata.color, sdata.alpha = "#FFFFFF", 150
+ end
+
+ local bike_pos = placer:get_pos()
+ bike_pos.y = bike_pos.y + 0.5
+ -- Use the saved attributes data and place the bike
+ bike = minetest.add_entity(bike_pos, "bike:bike", minetest.serialize(sdata))
+
+ -- Point it the right direction
+ if bike then
+ if placer then
+ bike:set_yaw(placer:get_look_horizontal())
+ end
+ local player_name = placer and placer:get_player_name() or ""
+ if not (creative and creative.is_enabled_for and
+ creative.is_enabled_for(player_name)) then
+ itemstack:take_item()
+ end
+ end
+ return itemstack
+ end,
+})
+
+--[[ Painter ]]--
+
+-- Helpers
+local function rgb_to_hex(r, g, b)
+ return string.format("#%02X%02X%02X", r, g, b)
+end
+
+local function hex_to_rgb(hex)
+ hex = hex:gsub("#","")
+ local rgb = {
+ r = tonumber("0x"..hex:sub(1,2)),
+ g = tonumber("0x"..hex:sub(3,4)),
+ b = tonumber("0x"..hex:sub(5,6)),
+ }
+ return rgb
+end
+
+-- Need to convert between 1000 units and 256
+local function from_slider_rgb(value)
+ value = tonumber(value)
+ return math.floor((255/1000*value)+0.5)
+end
+
+-- ...and back
+local function to_slider_rgb(value)
+ return 1000/255*value
+end
+
+-- Painter formspec
+local function show_painter_form(itemstack, player)
+ local meta = itemstack:get_meta()
+ local color = meta:get_string("paint_color")
+ local alpha = tonumber(meta:get_string("alpha"))
+ if alpha == nil then
+ color, alpha = "#FFFFFF", 128
+ end
+ local rgba = hex_to_rgb(color)
+ rgba.a = alpha
+ minetest.show_formspec(player:get_player_name(), "bike:painter",
+ -- Init formspec
+ "size[6,6;true]"..
+ "position[0.5, 0.45]"..
+ -- Hex/Alpha fields
+ "button[1.6,5.5;2,1;set;Set paint color]"..
+ "field[0.9,5;2,0.8;hex;Hex Color;"..color.."]"..
+ "field[2.9,5;2,0.8;alpha;Alpha (0-255);"..tostring(alpha).."]"..
+ -- RGBA sliders
+ "scrollbar[0,2;5,0.3;horizontal;r;"..tostring(to_slider_rgb(rgba.r)).."]"..
+ "label[5.1,1.9;R: "..tostring(rgba.r).."]"..
+ "scrollbar[0,2.6;5,0.3;horizontal;g;"..tostring(to_slider_rgb(rgba.g)).."]"..
+ "label[5.1,2.5;G: "..tostring(rgba.g).."]"..
+ "scrollbar[0,3.2;5,0.3;horizontal;b;"..tostring(to_slider_rgb(rgba.b)).."]"..
+ "label[5.1,3.1;B: "..tostring(rgba.b).."]"..
+ "scrollbar[0,3.8;5,0.3;horizontal;a;"..tostring(to_slider_rgb(rgba.a)).."]"..
+ "label[5.1,3.7;A: "..tostring(rgba.a).."]"..
+ -- Preview
+ "label[1,0;Preview:]"..
+ "image[2,0;2,2;bike_metal_base.png^[colorize:"..color..":"..tostring(rgba.a).."]"
+ )
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname == "bike:painter" then
+ local itemstack = player:get_wielded_item()
+ if fields.set then
+ if itemstack:get_name() == "bike:painter" then
+ local meta = itemstack:get_meta()
+ local hex = fields.hex
+ local alpha = tonumber(fields.alpha)
+ if is_hex(hex) == nil then
+ hex = "#FFFFFF"
+ end
+ if alpha == nil or alpha < 0 or alpha > 255 then
+ alpha = 128
+ end
+ -- Save color data to painter (rgba sliders will adjust to hex/alpha too!)
+ meta:set_string("paint_color", hex)
+ meta:set_string("alpha", tostring(alpha))
+ meta:set_string("description", "Bike Painter ("..hex:upper()..", A: "..tostring(alpha)..")")
+ player:set_wielded_item(itemstack)
+ show_painter_form(itemstack, player)
+ return
+ end
+ end
+ if fields.r or fields.g or fields.b or fields.a then
+ if itemstack:get_name() == "bike:painter" then
+ -- Save on slider adjustment (hex/alpha will adjust to match the rgba!)
+ local meta = itemstack:get_meta()
+ local function sval(value)
+ return from_slider_rgb(value:gsub(".*:", ""))
+ end
+ meta:set_string("paint_color", rgb_to_hex(sval(fields.r),sval(fields.g),sval(fields.b)))
+ meta:set_string("alpha", sval(fields.a))
+ -- Keep track of what this painter is painting
+ meta:set_string("description", "Bike Painter ("..meta:get_string("paint_color"):upper()..", A: "..meta:get_string("alpha")..")")
+ player:set_wielded_item(itemstack)
+ show_painter_form(itemstack, player)
+ end
+ end
+ end
+end)
+
+-- Make the actual thingy
+minetest.register_tool("bike:painter", {
+ description = S("Bike Painter"),
+ inventory_image = "bike_painter.png",
+ wield_scale = {x = 2, y = 2, z = 1},
+ on_place = show_painter_form,
+ on_secondary_use = show_painter_form,
+})
+
+minetest.register_craft({
+ output = "bike:wheel 2",
+ recipe = {
+ {"", rubber, ""},
+ {rubber, iron, rubber},
+ {"", rubber, ""},
+ },
+})
+
+minetest.register_craft({
+ output = "bike:handles",
+ recipe = {
+ {iron, iron, iron},
+ {rubber, "", rubber},
+ },
+})
+
+minetest.register_craft({
+ output = "bike:bike",
+ recipe = {
+ {"bike:handles", "", rubber},
+ {iron, iron, iron},
+ {"bike:wheel", "", "bike:wheel"},
+ },
+})
+
+minetest.register_craft({
+ output = "bike:painter",
+ recipe = {
+ {"", container, ""},
+ {iron, red_dye, green_dye},
+ {"", rubber, blue_dye},
+ },
+})
diff --git a/mods/bike/license.txt b/mods/bike/license.txt
new file mode 100644
index 00000000..3e095570
--- /dev/null
+++ b/mods/bike/license.txt
@@ -0,0 +1,27 @@
+License
+-------
+
+The MIT License (MIT)
+Copyright (C) 2012-2016 PilzAdam
+Copyright (C) 2012-2016 Various Minetest developers and contributors
+Copyright (C) 2018 GreenDimond
+Copyright (C) 2018 Hume2
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the "Software"), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+For more details:
+https://opensource.org/licenses/MIT
diff --git a/mods/bike/locale/.gitkeep b/mods/bike/locale/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/mods/bike/locale/bike.eo.tr b/mods/bike/locale/bike.eo.tr
new file mode 100644
index 00000000..e2d36df8
--- /dev/null
+++ b/mods/bike/locale/bike.eo.tr
@@ -0,0 +1,5 @@
+# textdomain: bike
+Bike Wheel=Bicikla Rado
+Bike Handles=Biciklaj Teniloj
+Bike=Biciklo
+Bike Painter=Bicikla Pentrilo
diff --git a/mods/bike/locale/bike.fr.tr b/mods/bike/locale/bike.fr.tr
new file mode 100644
index 00000000..6a111854
--- /dev/null
+++ b/mods/bike/locale/bike.fr.tr
@@ -0,0 +1,5 @@
+# textdomain: bike
+Bike Wheel=Roue de Vélo
+Bike Handles=Guidon de Vélo
+Bike=Vélo
+Bike Painter=Peinture pour Vélo
diff --git a/mods/bike/locale/template.txt b/mods/bike/locale/template.txt
new file mode 100644
index 00000000..34d02ac9
--- /dev/null
+++ b/mods/bike/locale/template.txt
@@ -0,0 +1,5 @@
+# textdomain: bike
+Bike Wheel=
+Bike Handles=
+Bike=
+Bike Painter=
diff --git a/mods/bike/mod.conf b/mods/bike/mod.conf
new file mode 100644
index 00000000..70b05bc3
--- /dev/null
+++ b/mods/bike/mod.conf
@@ -0,0 +1,6 @@
+name = bike
+description = Bike mod for Minetest
+optional_depends = skinsdb, skins, u_skins, simple_skins, wardrobe
+release = 17361
+author = Hume2
+title = Bike
diff --git a/mods/bike/models/bike.b3d b/mods/bike/models/bike.b3d
new file mode 100644
index 00000000..6ab69cbd
Binary files /dev/null and b/mods/bike/models/bike.b3d differ
diff --git a/mods/bike/models/bike.blend b/mods/bike/models/bike.blend
new file mode 100644
index 00000000..f8ba5a08
Binary files /dev/null and b/mods/bike/models/bike.blend differ
diff --git a/mods/bike/settingtypes.txt b/mods/bike/settingtypes.txt
new file mode 100644
index 00000000..08d91106
--- /dev/null
+++ b/mods/bike/settingtypes.txt
@@ -0,0 +1,38 @@
+# The maximum speed of a bike in m/s
+bike_max_speed (Maximum speed) float 6.9
+
+# The acceleration of a bike in m/s^2
+bike_acceleration (Acceleration) float 3.16
+
+# The decceleration of a bike caused by braking in m/s^2
+bike_decceleration (Decceleration) float 7.9
+
+# The decceleration of a bike caused by friciton in m/s^2
+bike_friction (Friction) float 0.79
+
+# The turning speed of a stopped bike in rad/s
+bike_turn_speed (Turn speed) float 1.58
+
+# The speed in m/s bellow which the bike accelerates linearily
+bike_friction_cone (Friction cone) float 0.4
+
+# How many nodes can be climbed/jumped
+bike_stepheight (Step height) float 0.6 0
+
+# How many nodes can be climbed/jumped with wheely
+bike_wheely_stepheight (Wheely step height) float 0.6 0
+
+# The factor by which the turning speed is increased by wheely
+bike_wheely_factor (Wheely factor) float 2.0
+
+# The factor by which the bike slows down per second in water
+bike_water_friction (Water friction) float 13.8
+
+# The factor by which the bike slows down per second offroad
+bike_offroad_friction (Offroad friction) float 1.62
+
+# Bike turns with player look direction instead of a/d keys.
+mount_turn_player_look (Turn with look direction) bool false
+
+# Bikes can only be used and picked up by owners.
+mount_ownable (Ownable bikes) bool false
diff --git a/mods/bike/textures/bike_blank.png b/mods/bike/textures/bike_blank.png
new file mode 100644
index 00000000..26ea0a42
Binary files /dev/null and b/mods/bike/textures/bike_blank.png differ
diff --git a/mods/bike/textures/bike_chain.png b/mods/bike/textures/bike_chain.png
new file mode 100644
index 00000000..fbc0667b
Binary files /dev/null and b/mods/bike/textures/bike_chain.png differ
diff --git a/mods/bike/textures/bike_gear.png b/mods/bike/textures/bike_gear.png
new file mode 100644
index 00000000..762a3274
Binary files /dev/null and b/mods/bike/textures/bike_gear.png differ
diff --git a/mods/bike/textures/bike_handles.png b/mods/bike/textures/bike_handles.png
new file mode 100644
index 00000000..2653607e
Binary files /dev/null and b/mods/bike/textures/bike_handles.png differ
diff --git a/mods/bike/textures/bike_helmet.png b/mods/bike/textures/bike_helmet.png
new file mode 100644
index 00000000..b05f2eb3
Binary files /dev/null and b/mods/bike/textures/bike_helmet.png differ
diff --git a/mods/bike/textures/bike_inventory.png b/mods/bike/textures/bike_inventory.png
new file mode 100644
index 00000000..ff7acfa8
Binary files /dev/null and b/mods/bike/textures/bike_inventory.png differ
diff --git a/mods/bike/textures/bike_leather.png b/mods/bike/textures/bike_leather.png
new file mode 100644
index 00000000..38217bd4
Binary files /dev/null and b/mods/bike/textures/bike_leather.png differ
diff --git a/mods/bike/textures/bike_metal_base.png b/mods/bike/textures/bike_metal_base.png
new file mode 100644
index 00000000..c41306ca
Binary files /dev/null and b/mods/bike/textures/bike_metal_base.png differ
diff --git a/mods/bike/textures/bike_metal_black.png b/mods/bike/textures/bike_metal_black.png
new file mode 100644
index 00000000..0c47d2ee
Binary files /dev/null and b/mods/bike/textures/bike_metal_black.png differ
diff --git a/mods/bike/textures/bike_metal_grey.png b/mods/bike/textures/bike_metal_grey.png
new file mode 100644
index 00000000..732f87ba
Binary files /dev/null and b/mods/bike/textures/bike_metal_grey.png differ
diff --git a/mods/bike/textures/bike_painter.png b/mods/bike/textures/bike_painter.png
new file mode 100644
index 00000000..f9653df3
Binary files /dev/null and b/mods/bike/textures/bike_painter.png differ
diff --git a/mods/bike/textures/bike_spokes.png b/mods/bike/textures/bike_spokes.png
new file mode 100644
index 00000000..3da4555b
Binary files /dev/null and b/mods/bike/textures/bike_spokes.png differ
diff --git a/mods/bike/textures/bike_spokes.xcf b/mods/bike/textures/bike_spokes.xcf
new file mode 100644
index 00000000..ccf06908
Binary files /dev/null and b/mods/bike/textures/bike_spokes.xcf differ
diff --git a/mods/bike/textures/bike_tread.png b/mods/bike/textures/bike_tread.png
new file mode 100644
index 00000000..831ee3d7
Binary files /dev/null and b/mods/bike/textures/bike_tread.png differ
diff --git a/mods/bike/textures/bike_tread2.png b/mods/bike/textures/bike_tread2.png
new file mode 100644
index 00000000..244e5283
Binary files /dev/null and b/mods/bike/textures/bike_tread2.png differ
diff --git a/mods/bike/textures/bike_wheel.png b/mods/bike/textures/bike_wheel.png
new file mode 100644
index 00000000..eced7870
Binary files /dev/null and b/mods/bike/textures/bike_wheel.png differ
diff --git a/mods/character_anim/.luacheckrc b/mods/character_anim/.luacheckrc
deleted file mode 100644
index b5e54ddd..00000000
--- a/mods/character_anim/.luacheckrc
+++ /dev/null
@@ -1,7 +0,0 @@
-globals = {"character_anim"}
-read_globals = {
- "modlib",
- -- Minetest
- math = {fields = {"sign"}},
- "vector", "minetest"
-}
diff --git a/mods/character_anim/Readme.md b/mods/character_anim/Readme.md
deleted file mode 100644
index 5a4f0e2e..00000000
--- a/mods/character_anim/Readme.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# Character Animations (`character_anim`)
-
-Animates the character. Resembles [`playeranim`](https://github.com/minetest-mods/playeranim) and [`headanim`](https://github.com/LoneWolfHT/headanim).
-
-## About
-
-Depends on [`modlib`](https://github.com/appgurueu/modlib). Code written by Lars Mueller aka LMD or appguru(eu) and licensed under the MIT license.
-
-## Screenshot
-
-
-
-## Links
-
-* [GitHub](https://github.com/appgurueu/character_anim) - sources, issue tracking, contributing
-* [Discord](https://discordapp.com/invite/ysP74by) - discussion, chatting
-* [Minetest Forum](https://forum.minetest.net/viewtopic.php?f=9&t=25385) - (more organized) discussion
-* [ContentDB](https://content.minetest.net/packages/LMD/character_anim) - releases (cloning from GitHub is recommended)
-
-## Features
-
-* Animates head, right arm & body
-* Support for arbitrary player models, as long as `Head`, `Arm_Right` & `Body` bones exist
- * If any of these bones do not exist, it will still try to animate the remaining bones
-* Advantages over `playeranim`:
- * Extracts exact animations and bone positions from b3d models at runtime (no complex installation)
- * Also animates attached players (with restrictions on angles)
-* Advantages over `headanim`:
- * Provides compatibility back until Minetest 0.4.x (as opposed to `headanim` supporting only 5.3+)
- * Head angles are clamped, head can tilt sideways
- * Animates right arm & body as well
-
-## Configuration
-
-
-### `default`
-
-#### `arm_right`
-
-##### `radius`
-
-Right arm spin radius
-
-* Type: number
-* Default: `10`
-* >= `-180`
-* <= `180`
-
-##### `speed`
-
-Right arm spin speed
-
-* Type: number
-* Default: `1000`
-* > `0`
-* <= `10000`
-
-##### `yaw`
-
-###### `max`
-
-Right arm yaw (max)
-
-* Type: number
-* Default: `160`
-* >= `-180`
-* <= `180`
-
-###### `min`
-
-Right arm yaw (min)
-
-* Type: number
-* Default: `-30`
-* >= `-180`
-* <= `180`
-
-
-
-#### `body`
-
-##### `turn_speed`
-
-Body turn speed
-
-* Type: number
-* Default: `0.2`
-* > `0`
-* <= `1000`
-
-
-#### `head`
-
-##### `pitch`
-
-###### `max`
-
-Head pitch (max)
-
-* Type: number
-* Default: `80`
-* >= `-180`
-* <= `180`
-
-###### `min`
-
-Head pitch (min)
-
-* Type: number
-* Default: `-60`
-* >= `-180`
-* <= `180`
-
-
-##### `yaw`
-
-###### `max`
-
-Head yaw (max)
-
-* Type: number
-* Default: `90`
-* >= `-180`
-* <= `180`
-
-###### `min`
-
-Head yaw (min)
-
-* Type: number
-* Default: `-90`
-* >= `-180`
-* <= `180`
-
-
-##### `yaw_restricted`
-
-###### `max`
-
-Head yaw restricted (max)
-
-* Type: number
-* Default: `45`
-* >= `-180`
-* <= `180`
-
-###### `min`
-
-Head yaw restricted (min)
-
-* Type: number
-* Default: `0`
-* >= `-180`
-* <= `180`
-
-
-##### `yaw_restriction`
-
-Head yaw restriction
-
-* Type: number
-* Default: `60`
-* >= `-180`
-* <= `180`
-
-
-
-### `models`
-
-Other models, same format as `default` model
-
-
-## API
-
-Minetest's `player:set_bone_position` is overridden so that it still works as expected.
-
-### `character_anim.set_bone_override(player, bonename, position, rotation)`
-
-The signature resembles that of `set_bone_position`. `bonename` must be a string. The following additional features are provided:
-
-* Using it like `set_bone_position` by setting `rotation` and `position` to non-`nil` values and using `""` to set the root bone
-* *Setting only the bone position* by setting `rotation` to `nil` - bone rotation will then be model-animation-determined
-* *Setting only the bone rotation* by setting `position` to `nil` - bone position will then be model-animation-determined
-* *Clearing the override* by setting both `rotation` and `position` to `nil` ("unset_bone_position")
diff --git a/mods/character_anim/depends.txt b/mods/character_anim/depends.txt
deleted file mode 100644
index 0098ab9f..00000000
--- a/mods/character_anim/depends.txt
+++ /dev/null
@@ -1 +0,0 @@
-modlib
diff --git a/mods/character_anim/init.lua b/mods/character_anim/init.lua
deleted file mode 100644
index 4d24262a..00000000
--- a/mods/character_anim/init.lua
+++ /dev/null
@@ -1,379 +0,0 @@
-assert(modlib.version >= 103, "character_anim requires at least version rolling-103 of modlib")
-local workaround_model = modlib.mod.require"workaround"
-
-character_anim = {}
-
-character_anim.conf = modlib.mod.configuration()
-
-local quaternion = modlib.quaternion
--- TODO deduplicate code: move to modlib (see ghosts mod)
-local media_paths = modlib.minetest.media.paths
-
-local static_model_names = {}
-local animated_model_names = {}
-for name in pairs(media_paths) do
- if (name:find"character" or name:find"player") and name:match"%.b3d$" then
- local fixed, data = pcall(workaround_model, name)
- if fixed then
- local static_name = "_character_anim_" .. name
- minetest.dynamic_add_media({
- filename = static_name,
- filedata = data,
- })
- static_model_names[name] = static_name
- animated_model_names[static_name] = name
- else
- minetest.log("warning", "character_anim: failed to workaround model " .. name)
- end
- end
-end
-
-local function find_node(root, name)
- if root.name == name then return root end
- for _, child in ipairs(root.children) do
- local node = find_node(child, name)
- if node then return node end
- end
-end
-
-local models = setmetatable({}, {__index = function(self, filename)
- if animated_model_names[filename] then
- return self[animated_model_names[filename]]
- end
- local _, ext = modlib.file.get_extension(filename)
- if not ext or ext:lower() ~= "b3d" then
- -- Only B3D support currently
- return
- end
- local path = assert(media_paths[filename], filename)
- local stream = io.open(path, "rb")
- local model = assert(modlib.b3d.read(stream))
- assert(stream:read(1) == nil, "EOF expected")
- stream:close()
- self[filename] = model
- return model
-end})
-
-function character_anim.is_interacting(player)
- local control = player:get_player_control()
- return minetest.check_player_privs(player, "interact") and (control.RMB or control.LMB)
-end
-
-local function get_look_horizontal(player)
- return -math.deg(player:get_look_horizontal())
-end
-
-local players = {}
-character_anim.players = players
-
-local function get_playerdata(player)
- local name = player:get_player_name()
- local data = players[name]
- if data then return data end
- -- Initialize playerdata if it doesn't already exist
- data = {
- interaction_time = 0,
- animation_time = 0,
- look_horizontal = get_look_horizontal(player),
- bone_positions = {}
- }
- players[name] = data
- return data
-end
-
-function character_anim.set_bone_override(player, bonename, position, rotation)
- local value = {
- position = position,
- euler_rotation = rotation
- }
- get_playerdata(player).bone_positions[bonename] = next(value) and value
-end
-
-local function nil_default(value, default)
- if value == nil then return default end
- return value
-end
-
--- Forward declaration
-local handle_player_animations
--- Raw PlayerRef methods
-local set_bone_position, set_animation, set_local_animation
-minetest.register_on_joinplayer(function(player)
- get_playerdata(player) -- Initalizes playerdata if it isn't already initialized
- if not set_bone_position then
- local PlayerRef = getmetatable(player)
-
- -- Keep our model hack completely opaque to the outside world
-
- local set_properties = PlayerRef.set_properties
- function PlayerRef:set_properties(props)
- if not self:is_player() then
- return set_properties(self, props)
- end
- local old_mesh = props.mesh
- props.mesh = static_model_names[old_mesh] or old_mesh
- set_properties(self, props)
- props.mesh = old_mesh
- end
-
- local get_properties = PlayerRef.get_properties
- function PlayerRef:get_properties()
- if not self:is_player() then
- return get_properties(self)
- end
- local props = get_properties(self)
- if not props then return nil end
- props.mesh = animated_model_names[props.mesh] or props.mesh
- return props
- end
-
- set_bone_position = PlayerRef.set_bone_position
- function PlayerRef:set_bone_position(bonename, position, rotation)
- if self:is_player() then
- character_anim.set_bone_override(self, bonename or "",
- position or {x = 0, y = 0, z = 0},
- rotation or {x = 0, y = 0, z = 0})
- end
- return set_bone_position(self, bonename, position, rotation)
- end
-
- set_animation = PlayerRef.set_animation
- function PlayerRef:set_animation(frame_range, frame_speed, frame_blend, frame_loop)
- if not self:is_player() then
- return set_animation(self, frame_range, frame_speed, frame_blend, frame_loop)
- end
- local player_animation = get_playerdata(self)
- if not player_animation then
- return
- end
- local prev_anim = player_animation.animation
- local new_anim = {
- nil_default(frame_range, {x = 1, y = 1}),
- nil_default(frame_speed, 15),
- nil_default(frame_blend, 0),
- nil_default(frame_loop, true)
- }
- player_animation.animation = new_anim
- if not prev_anim or (prev_anim[1].x ~= new_anim[1].x or prev_anim[1].y ~= new_anim[1].y) then
- -- Reset animation only if the animation changed
- player_animation.animation_time = 0
- handle_player_animations(0, player)
- elseif prev_anim[2] ~= new_anim[2] then
- -- Adapt time to new speed
- player_animation.animation_time = player_animation.animation_time * prev_anim[2] / new_anim[2]
- end
- end
- local set_animation_frame_speed = PlayerRef.set_animation_frame_speed
- function PlayerRef:set_animation_frame_speed(frame_speed)
- if not self:is_player() then
- return set_animation_frame_speed(self, frame_speed)
- end
- frame_speed = nil_default(frame_speed, 15)
- local player_animation = get_playerdata(self)
- if not player_animation then
- return
- end
- local prev_speed = player_animation.animation[2]
- player_animation.animation[2] = frame_speed
- -- Adapt time to new speed
- player_animation.animation_time = player_animation.animation_time * prev_speed / frame_speed
- end
-
- local get_animation = PlayerRef.get_animation
- function PlayerRef:get_animation()
- if not self:is_player() then
- return get_animation(self)
- end
- local anim = get_playerdata(self).animation
- if anim then
- return unpack(anim, 1, 4)
- end
- return get_animation(self)
- end
-
- set_local_animation = PlayerRef.set_local_animation
- function PlayerRef:set_local_animation(idle, walk, dig, walk_while_dig, frame_speed)
- if not self:is_player() then return set_local_animation(self) end
- frame_speed = frame_speed or 30
- get_playerdata(self).local_animation = {idle, walk, dig, walk_while_dig, frame_speed}
- end
- local get_local_animation = PlayerRef.get_local_animation
- function PlayerRef:get_local_animation()
- if not self:is_player() then return get_local_animation(self) end
- local local_anim = get_playerdata(self).local_animation
- if local_anim then
- return unpack(local_anim, 1, 5)
- end
- return get_local_animation(self)
- end
- end
-
- -- First update `character_anim` with the current animation
- -- which mods like `player_api` might have already set
- -- (note: these two methods are already hooked)
- player:set_animation(player:get_animation())
- -- Then disable animation & local animation
- local no_anim = {x = 0, y = 0}
- set_animation(player, no_anim, 0, 0, false)
- set_local_animation(player, no_anim, no_anim, no_anim, no_anim, 1)
-end)
-
-minetest.register_on_leaveplayer(function(player) players[player:get_player_name()] = nil end)
-
-local function clamp(value, range)
- if value > range.max then
- return range.max
- end
- if value < range.min then
- return range.min
- end
- return value
-end
-
-local function normalize_angle(angle)
- return ((angle + 180) % 360) - 180
-end
-
-local function normalize_rotation(euler_rotation)
- return vector.apply(euler_rotation, normalize_angle)
-end
-
-function handle_player_animations(dtime, player)
- local mesh
- do
- local props = player:get_properties()
- if not props then
- -- HACK inside on_joinplayer, the player object may be invalid
- -- causing get_properties() to return nothing - just ignore this
- return
- end
- mesh = props.mesh
- end
- if static_model_names[mesh] then
- player:set_properties{mesh = mesh}
- elseif animated_model_names[mesh] then
- mesh = animated_model_names[mesh]
- end
- local model = models[mesh]
- if not model then
- return
- end
- local conf = character_anim.conf.models[mesh] or character_anim.conf.default
- local player_animation = get_playerdata(player)
- local anim = player_animation.animation
- if not anim then
- return
- end
- local range, frame_speed, _, frame_loop = unpack(anim, 1, 4)
- local animation_time = player_animation.animation_time
- animation_time = animation_time + dtime
- player_animation.animation_time = animation_time
- local range_min, range_max = range.x + 1, range.y + 1
- local keyframe
- if range_min == range_max then
- keyframe = range_min
- elseif frame_loop then
- keyframe = range_min + ((animation_time * frame_speed) % (range_max - range_min))
- else
- keyframe = math.min(range_max, range_min + animation_time * frame_speed)
- end
- local bones = {}
- local animated_bone_props = model:get_animated_bone_properties(keyframe, true)
- local body_quaternion
- for _, props in ipairs(animated_bone_props) do
- local bone = props.bone_name
- if bone == "Body" then
- body_quaternion = props.rotation
- end
- local position, rotation = modlib.vector.to_minetest(props.position), props.rotation
- -- Invert quaternion to match Minetest's coordinate system
- rotation = {-rotation[1], -rotation[2], -rotation[3], rotation[4]}
- local euler_rotation = quaternion.to_euler_rotation(rotation)
- bones[bone] = {position = position, rotation = rotation, euler_rotation = euler_rotation}
- end
- local Body = (bones.Body or {}).euler_rotation
- local Head = (bones.Head or {}).euler_rotation
- local Arm_Right = (bones.Arm_Right or {}).euler_rotation
- local look_vertical = math.deg(player:get_look_vertical())
- if Head then Head.x = -look_vertical end
- local interacting = character_anim.is_interacting(player)
- if interacting and Arm_Right then
- local interaction_time = player_animation.interaction_time
- -- Note: -90 instead of -Arm_Right.x because it looks better
- Arm_Right.x = -90 - look_vertical - math.sin(-interaction_time) * conf.arm_right.radius
- Arm_Right.y = Arm_Right.y + math.cos(-interaction_time) * conf.arm_right.radius
- player_animation.interaction_time = interaction_time + dtime * math.rad(conf.arm_right.speed)
- else
- player_animation.interaction_time = 0
- end
- local look_horizontal = get_look_horizontal(player)
- local diff = look_horizontal - player_animation.look_horizontal
- if math.abs(diff) > 180 then
- diff = math.sign(-diff) * 360 + diff
- end
- local moving_diff = math.sign(diff) * math.abs(diff) * math.min(1, dtime / conf.body.turn_speed)
- player_animation.look_horizontal = player_animation.look_horizontal + moving_diff
- if math.abs(moving_diff) < 1e-6 then
- player_animation.look_horizontal = look_horizontal
- end
- local lag_behind = diff - moving_diff
- local attach_parent, _, _, attach_rotation = player:get_attach()
- if attach_parent then
- local parent_rotation
- if attach_parent.get_rotation then
- parent_rotation = attach_parent:get_rotation()
- else -- 0.4.x doesn't have get_rotation(), only yaw
- parent_rotation = {x = 0, y = attach_parent:get_yaw(), z = 0}
- end
- if attach_rotation and parent_rotation then
- parent_rotation = vector.apply(parent_rotation, math.deg)
- local total_rotation = normalize_rotation(vector.subtract(attach_rotation, parent_rotation))
- local function rotate_relative(euler_rotation)
- if not euler_rotation then return end
- euler_rotation.y = euler_rotation.y + look_horizontal
- local new_rotation = normalize_rotation(vector.subtract(euler_rotation, total_rotation))
- modlib.table.add_all(euler_rotation, new_rotation)
- end
-
- rotate_relative(Head)
- if interacting then rotate_relative(Arm_Right) end
- end
- elseif Body and not modlib.table.nilget(rawget(_G, "player_api"), "player_attached", player:get_player_name()) then
- Body.y = Body.y + lag_behind
- if Head then Head.y = Head.y + lag_behind end
- if interacting and Arm_Right then Arm_Right.y = Arm_Right.y + lag_behind end
- end
-
- -- HACK this essentially only works for very character.b3d-like models;
- -- it tries to find the (sole) X-rotation of the body relative to a subsequent (180°) Y-rotation.
- if body_quaternion then
- local body_rotation = assert(assert(find_node(model.node, "Body")).rotation)
- local body_x = quaternion.to_euler_rotation(modlib.quaternion.compose(body_rotation, body_quaternion)).x
- if Head then Head.x = normalize_angle(Head.x - body_x) end
- if interacting and Arm_Right then Arm_Right.x = normalize_angle(Arm_Right.x - body_x) end
- end
-
- if Head then
- Head.x = clamp(Head.x, conf.head.pitch)
- Head.y = clamp(Head.y, conf.head.yaw)
- if math.abs(Head.y) > conf.head.yaw_restriction then
- Head.x = clamp(Head.x, conf.head.yaw_restricted)
- end
- end
- if Arm_Right then Arm_Right.y = clamp(Arm_Right.y, conf.arm_right.yaw) end
-
- -- Replace animation with serverside bone animation
- for bone, values in pairs(bones) do
- local overridden_values = player_animation.bone_positions[bone]
- overridden_values = overridden_values or {}
- set_bone_position(player, bone,
- overridden_values.position or values.position,
- overridden_values.euler_rotation or values.euler_rotation)
- end
-end
-
-minetest.register_globalstep(function(dtime)
- for _, player in pairs(minetest.get_connected_players()) do
- handle_player_animations(dtime, player)
- end
-end)
diff --git a/mods/character_anim/mod.conf b/mods/character_anim/mod.conf
deleted file mode 100644
index 95386855..00000000
--- a/mods/character_anim/mod.conf
+++ /dev/null
@@ -1,8 +0,0 @@
-name = character_anim
-title = Character Animations
-description = Animates the character
-author = LMD
-depends = modlib
-# these mods are supported, but don't necessarily need to load before character_anim
-optional_depends = player_api, 3d_armor, skinsdb
-release = 29484
diff --git a/mods/character_anim/schema.lua b/mods/character_anim/schema.lua
deleted file mode 100644
index e6ff0ea0..00000000
--- a/mods/character_anim/schema.lua
+++ /dev/null
@@ -1,65 +0,0 @@
-local function angle(description, default)
- return { type = "number", range = { min = -180, max = 180 }, description = description, default = default }
-end
-local range = function(description, default_min, default_max)
- return {
- type = "table",
- entries = {
- min = angle(description .. " (min)", default_min),
- max = angle(description .. " (max)", default_max)
- },
- func = function(range)
- if range.max < range.min then return "Minimum range value is not <= maximum range value" end
- end
- }
-end
-local model = {
- type = "table",
- entries = {
- body = {
- type = "table",
- entries = {
- turn_speed = {
- type = "number",
- range = { min_exclusive = 0, max = 1e3 },
- description = "Body turn speed",
- default = 0.2
- }
- }
- },
- head = {
- type = "table",
- entries = {
- pitch = range("Head pitch", -60, 80),
- yaw = range("Head yaw", -90, 90),
- yaw_restricted = range("Head yaw restricted", 0, 45),
- yaw_restriction = angle("Head yaw restriction", 60)
- }
- },
- arm_right = {
- type = "table",
- entries = {
- radius = angle("Right arm spin radius", 10),
- speed = {
- type = "number",
- range = { min_exclusive = 0, max = 1e4 },
- description = "Right arm spin speed",
- default = 1e3
- },
- yaw = range("Right arm yaw", -30, 160)
- }
- }
- }
-}
-
-return {
- type = "table",
- entries = {
- default = model,
- models = {
- type = "table",
- keys = { type = "string" },
- description = "Other models, same format as `default` model"
- }
- }
-}
diff --git a/mods/character_anim/screenshot.png b/mods/character_anim/screenshot.png
deleted file mode 100644
index 9dd6be9c..00000000
Binary files a/mods/character_anim/screenshot.png and /dev/null differ
diff --git a/mods/character_anim/settingtypes.txt b/mods/character_anim/settingtypes.txt
deleted file mode 100644
index 3e04027a..00000000
--- a/mods/character_anim/settingtypes.txt
+++ /dev/null
@@ -1,53 +0,0 @@
-[character_anim.default]
-
-[*character_anim.default.arm_right]
-
-# Right arm spin radius
-character_anim.default.arm_right.radius (Character anim Default Arm right Radius) float 10 -180.000000 180.000000
-
-# Right arm spin speed
-character_anim.default.arm_right.speed (Character anim Default Arm right Speed) float 1000 0.000000 10000.000000
-
-[**character_anim.default.arm_right.yaw]
-
-# Right arm yaw (max)
-character_anim.default.arm_right.yaw.max (Character anim Default Arm right Yaw Max) float 160 -180.000000 180.000000
-
-# Right arm yaw (min)
-character_anim.default.arm_right.yaw.min (Character anim Default Arm right Yaw Min) float -30 -180.000000 180.000000
-
-[*character_anim.default.body]
-
-# Body turn speed
-character_anim.default.body.turn_speed (Character anim Default Body Turn speed) float 0.2 0.000000 1000.000000
-
-[*character_anim.default.head]
-
-# Head yaw restriction
-character_anim.default.head.yaw_restriction (Character anim Default Head Yaw restriction) float 60 -180.000000 180.000000
-
-[**character_anim.default.head.pitch]
-
-# Head pitch (max)
-character_anim.default.head.pitch.max (Character anim Default Head Pitch Max) float 80 -180.000000 180.000000
-
-# Head pitch (min)
-character_anim.default.head.pitch.min (Character anim Default Head Pitch Min) float -60 -180.000000 180.000000
-
-[**character_anim.default.head.yaw]
-
-# Head yaw (max)
-character_anim.default.head.yaw.max (Character anim Default Head Yaw Max) float 90 -180.000000 180.000000
-
-# Head yaw (min)
-character_anim.default.head.yaw.min (Character anim Default Head Yaw Min) float -90 -180.000000 180.000000
-
-[**character_anim.default.head.yaw_restricted]
-
-# Head yaw restricted (max)
-character_anim.default.head.yaw_restricted.max (Character anim Default Head Yaw restricted Max) float 45 -180.000000 180.000000
-
-# Head yaw restricted (min)
-character_anim.default.head.yaw_restricted.min (Character anim Default Head Yaw restricted Min) float 0 -180.000000 180.000000
-
-[character_anim.models]
\ No newline at end of file
diff --git a/mods/character_anim/workaround.lua b/mods/character_anim/workaround.lua
deleted file mode 100644
index 32438b30..00000000
--- a/mods/character_anim/workaround.lua
+++ /dev/null
@@ -1,54 +0,0 @@
--- See https://github.com/luanti-org/luanti/issues/15692
-
-local mod = modlib.mod
-local b3d = modlib.b3d
-
-local media_paths = modlib.minetest.media.paths
-
-local function is_perfect(quat)
- local mat = modlib.matrix4.rotation(quat)
- local diag_abs_sum = 0
- for i = 1, 3 do
- diag_abs_sum = diag_abs_sum + math.abs(mat[i][i])
- end
- return math.abs(diag_abs_sum - 3) < 1e-5
-end
-
-return function(name)
- local stream = assert(io.open(media_paths[name], "rb"))
- local character = assert(b3d.read(stream))
- stream:close()
-
- local function wiggle_rotation(quat)
- if math.abs(quat[1]) + math.abs(quat[2]) + math.abs(quat[3]) < 1e-5 then return quat end -- identity
- if not is_perfect(quat) then return quat end
- local wiggled = {}
- for i = 1, 4 do
- wiggled[i] = quat[i] + 1e-3
- end
- wiggled = modlib.quaternion.normalize(wiggled)
- if not is_perfect(wiggled) then return wiggled end
- for i = 1, 4 do
- wiggled[i] = quat[i] - 1e-3
- end
- wiggled = modlib.quaternion.normalize(wiggled)
- if not is_perfect(wiggled) then return wiggled end
- return quat -- this shouldn't happen
- end
-
- local function wiggle_rotations(node)
- node.rotation = wiggle_rotation(node.rotation)
- node.keys = {}
- for _, child in ipairs(node.children) do
- wiggle_rotations(child)
- end
- end
-
- wiggle_rotations(character.node)
-
- local rope = {}
- character:write({write = function(_, str)
- table.insert(rope, str)
- end})
- return table.concat(rope)
-end
diff --git a/mods/cozylights/LICENSE b/mods/cozylights/LICENSE
new file mode 100644
index 00000000..c10767e4
--- /dev/null
+++ b/mods/cozylights/LICENSE
@@ -0,0 +1,13 @@
+For code:
+Copyright 2024 SingleDigitIq
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Media licenses:
+default_glass.png is by Krock (CC0 1.0)
+my debug textures are WTFPL if anything
\ No newline at end of file
diff --git a/mods/cozylights/README.md b/mods/cozylights/README.md
new file mode 100644
index 00000000..ec1551a6
--- /dev/null
+++ b/mods/cozylights/README.md
@@ -0,0 +1,256 @@
+# Cozy Lights
+
+Improves the way light sources(torches etc) behave and allows to create huge lights, literal projectors with just a mouse click, light map will be computed for you.
+
+Early alpha, but at least NotSoWow, Sumi, MisterE, Agura and Sharp have expressed curiosity, that already makes six of us, good enough for release. Feedback, suggestions, bug reports are very welcome. At this dev stage Cozy Lights can be good for builders in creative mode, singleplayer survival is somewhat ok, multiplayer is not yet recommended, unless it's 2-5 players or just schematics with cozy lights and no functionality.
+
+**Light sources illuminate bigger area with default settings:**
+
+
+
+Voxel light maps are a complete game changer - it is almost like going from 2d to 3d in terms of depth. You now have 14 shades for every visible building block, and it does not have to register 14 versions of every building block. Cobble only challenge has got a whole lot easier, something fun to look at with the least fun texture is possible now with just this mod :> Disabling smooth lighting might can make for an interesting aesthetic in some cases.
+
+You can also build these lights just like you do with any structures, in other words, place invisible blocks of light of all possible engine light levels block-by-block. Tools are coming soon to make this process more user-friendly, right now you will need to make them visible and interactable in debug mode.
+
+It is eventually supposed to become accurate enough so that if you learn how to draw, you will have an easier time understanding how depth and shadows work and what can be done with them.
+
+**Cozy wielded light:**
+
+
+
+**WARNING:**
+
+**1. after removing Cozy Lights from your world you will be left with spheres of unknown nodes. Easiest could be to reenable the mod and call ```/clearlights``` in all locations Cozy Lights are active.**
+
+**2. if you have override_engine_lights enabled, then in case you ever remove Cozy Lights mod from your world, you will be left with broken lights. To fix it, you will need to use the mod fixmap or anything that updates/fixes engine lights. override_engine_lights is disabled by default, so it should be safe.**
+
+## Known issues
+
+1. worldedit:placeholder nodes can prevent light map from generating correctly and this currenly happens without notice or options provided. Current workaround is to define a worldedit region and run ```//replace worldedit:placeholder air``` before adding lights to the scene. This issue also involves cozy wielded light, wordedit placeholders can appear anywhere if the mod is active. There can be other invisible nodes from some mods and games which would interfere with light map.
+
+2. You will have to disable K Ambient Light to use Cozy Lights, together, they are not recommended for now.
+
+3. Light emitting liquids always straight up ignored, light emitting airlikes too
+
+4. When there are too many light sources in a generated area, it gets ignored. If you run /rebuildlights in such area, it will attempt to do so, but probably would need too much time
+
+5. If you are moving too fast(creative or falling from above) and its first time you visit many areas, generation will not look like it's immediate
+
+6. Some lights are still being missed in generated mapblocks
+
+*For what it does it's quite fast, it is supposed to somehow get even faster. I have recently discovered that my CPU is 10(!) years old and it's actually usable on my PC. Would appreciate if somebody with a beast PC would try this mod out and post a couple of benchmarks, and also if some phone poster will try to do the same*
+
+## Light Brush
+
+
+
+*Click or hold left mouse button* to draw light with given settings. Light Brush' reach is 100 nodes, so you can have perspective. Note: with radiuses over 30 nodes as of now mouse hold won't have an effect.
+
+*On right click* settings menu opens up. The menu has hopefully useful tooltips for each setting. You can set radius, brightness, strength and draw mode. There are 6 draw modes so far: default, erase, override, lighten, darken and blend.
+
+
+
+## Chat Commands
+
+```/cozysettings``` opens a global settings menu for cozy lights, here you can adjust node light sources like torches, meselamps, fireflies, etc to make it work better with potato or make light reach mad far and stuff. Some settings which you can find in Minetest game settings for the mod are still not present here(like override_engine_lights which makes everything nicer). These changes persist after exiting and re-entering the world again.
+
+
+
+Currently max radius is 120 for commands below, if your value is invalid it will adjust to closest valid or throw an error. Some potatoes might experience issues with big radiuses. Eventually max radius will be much bigger.
+
+```/clearlights ``` removes invisible light nodes in area with specified radius. Helpful to remove lights created with light brush. Example usage: ```/clearlights 120```
+
+```/rebuildlights ``` rebuilds light map in an area with specified radius. Useful in case you changed the settings or accidentally broke some lights by other commands or by mining in debug. This can be slow if there are lots of light sources in the area with far reaching light. Example usage: ```/rebuildlights 40```
+
+```/fixedges ``` fixes obstacles' opposite edges for light map in an area with specified radius. Default algorithm sacrifices accuracy for speed, because of that the lights can still go through diagonal walls if they are only one node thick, and as of now they can sometimes light up an edge(1 block from a corner) of the opposite side of an obstacle. With this command you are supposed to be able to fix it, but currently it's weird, broken. You can use it but the result wont necessarily look good.
+
+```/cozydebugon ``` makes all cozy light nodes visible and interactable in an area with a specified radius. With it you can also basically build lights just as you would with any other structures before the tools for that are available.
+
+```/cozydebugoff ``` makes all cozy light nodes invisible and non-interactable again in an area with a specified radius.
+
+```/optimizeformobile ``` removes all cozy light nodes which do not touch a surface of some visible node, like cobble for example. It is maybe useful, because default algo spreads light in a sphere and lights up the air above the ground too, which might be a bit challenging for potato and mobile to render reliably, they might experience FPS drops. Good if you are building a schematic for a multiplayer server. This option might slightly decrease the quality of light map, example: you have a light node with strength of 7 above the ground, and that ground is visible because of that, but after using this option that light node will be removed, so that part of the ground might be left in complete darkness. Basically might make some places darker.
+
+```/spawnlight ``` spawn a light at your position which does not use user friendly light brush algo, but ambient light algo. "float" means it can be with some arbitrary amount of decimals, or simple integer
+
+```/daynightratio ``` change Minetest engine day_night_ratio for the player who used the command. ```0``` is the darkest night possible, you can observe how dark it can be on the screenshots, was useful in testing, probably will help with building too. ```1``` is the brightest possible day. Some gradations in between are maybe under appreciated and seem pretty moody, I guess that would depend on a texture pack.
+
+```/cozyadjust ``` change brightness of all cozy light nodes by adjust_by value in the area of size. Adjust_by can be negative. Keep_map is 1 by default and can be omitted, when it's 1 and adjust_by will result in a value out of bounds of 1-14(engine light levels) even for one node in the area, the command will revert(have no effect at all), so that light map will be preserved. If you are ok with breaking light map, type 0 for keep_map.
+
+
+Shortcuts for all commands follow a convention to easier memorize them:
+
+```zcl``` - clearlights
+
+```zrl``` - rebuildlights
+
+```zfe``` - fixedges
+
+```zdon``` - cozydebugon
+
+```zdoff``` - cozydebugoff
+
+```zofm``` - optimizeformobile
+
+```zsl``` - spawnlight
+
+```zs``` - cozysettings
+
+```zdnr``` - daynightratio
+
+```za``` - cozyadjust
+
+## Supported mods and games
+
+Most of the most popular ones on paper, but its early alpha, so it can still be broken. It's not just popular ones, actually no idea how many it supports, some of them are not even on ContentDB.
+
+For definitely supported games, check the section of supported games on ContentDB, or mod.conf, if the game is in a list then support is full for what the mod can currently do, current *known* exceptions are:
+
+**Nodecore** - partial support, light map does not update for dynamic light sources(the ones that change brightness over time)
+
+**Age of Mending** - partial support, too many light sources in caves sometimes, and so far Cozy Lights cant process that without completely freezing everything for some time
+
+**Piranesi** - does not seem to work at all, probably something schematic related
+
+**Shadow Forest** - it works as intended, but there is only campfire to make cozy, wont feel like an upgrade
+
+If a mod or a game you like is not supported or there are some problems not listed here, tell me immediately. You can just drop a list of games/mods you have issues with in review. Eventually cozy lights' support will attempt to balance the overall feel and look of the game/mod with meticulous consideration, but we are not at that stage yet.
+
+## For Developers
+
+There are like I think 5 algo versions of drawing lights or I refactored that, because I never heard of DRY, never happened. All algos sacrifice accuracy for speed and miss some nodes for huge spheres.
+
+*Plans for API:*
+
+- You will be able to override cozylights' global step, disable it and call it from your global step
+
+- You will be able to override any default settings
+
+- Register unique settings for specific nodes
+
+## Todo
+
+- is it possible to have trees grow within the radius of a light block like torches
+
+- add undo
+
+- figure out what to do about lights going through diagonal, one node thick walls. also still somehow manage to keep algo cheap
+
+- Optimize memory usage, use several voxel manipulators for biggest lights, will be slower but much more stable, also increase max radius to even more mentally challenged value
+
+- see what can be done with race condition of wielded light and node light
+
+- save brush settings in item metadata and change icon somehow to resemble the settings
+
+- add /disableongen
+
+- all queues should be saved in case of server shutdown, so they can be resumed
+
+- add /ignore certain block
+
+- algo for many adjacent lights
+
+- see what can be done about snow and slabs not passing the light through
+
+- make dropped items emit cozy light if they have light_source above 0, just like in original wielded light mod
+
+- make sure bigger lights wont go unnoticed in on_generated and schematic placement. apparnetly on generated can support lights up to 80 if max area radius is 120
+
+- stress test it with heavily modded worlds, possible problem: luajit ram limit for default luajit on linux?
+
+- illuminate transparent liquids too if possible without making it look weird, except dont make floodable light sources work underwater just like in original wielded light
+
+- fix nodecore dynamic light source not updating the brightness/radius
+
+- add privileges so schematics can be used on multiplayer server
+
+- parse minetest forum for optional_depends
+
+- add inventory images for lights and debug lights, make them only available in creative
+
+- make darkness nodes, wielded darkness, Darkness Brush
+
+- add static natural scene(stop the time, fix the sun/moon in one position, update the area accordingly)
+
+- raytracing
+
+- allow people to run cpu and memory-friendly minimal schematic support version, for multiplayer servers for example
+
+- if certain treshold of light source commonality in an area is reached, those light sources should be ignored
+
+- would it be possible without too much work to programatically determine global commonality of a node from mapgen?
+
+- add optional more pleasant day/night cycle
+
+- add optional sky textures
+
+- add multiplayer/mobile settings(very little light nodes, very simple light map), and mid settings(more or less okayish), max is default
+
+- move to base "unsafe" methods for tables? seems like luajit optimizes it all away and it's useless to bother?
+
+- try spread work over several loops and try vector.add
+
+- maybe three types of darkness nodes, ones that are completely overridable with cozylights, and ones that arent(make a darker light shade), and ones that completely ignore cozylights
+
+- lights auto rebuild on first load after settings change?
+
+- make a table for existing decoration nodes
+
+- make sure spheres of big sizes dont miss too many blocks
+
+- give light sources metadata, so when nearby light sources are destroyed you can find and rebuild easily, also give metadata to light brush epicenter for the same reason
+
+- maintain files in which you record light source positions, which can be quickly grabbed to rebuild lights if there is a removal
+
+- add cone light blocks, so those lights can be built on top of each other to make static lights from old games
+
+- add light grabber tool, Light Excavation Tool 9000 TURBO V3, so that the light wont be selectable without it
+
+- add Consumer Grade Reality Bending Device to create preset nodes with chosen qualities
+
+- add global step override api, ability to implement cozylights global step into a game/other mod global step more efficiently, maybe add generic global step call like mainloop or mainstep, see what other games do with it, choose or create convention for this i guess
+
+- add handle_async where it makes sense
+
+- ci for optional_depends auto update according to content db mods/games updates and releases
+
+### Some expensive notes stackoverflow will never tell about LuaJIT to you or to future me. Summing up my discord rambling because COVID made me forget some of Lua I tried before, so I am writing it down for now.
+
+TLDR: LuaJIT is certainly impressive in some parts, however I would rather refrain from using it for absolutely anything that implies even a bit of performance, and unless there is no way to avoid it, deprecate Lua as a terrible inconvenience and never look back. It's too slow, and when you try to squeeze anything out of it, it loses most of its appeal/narrative, it even loses purpose. If still too many words, remember just this about Lua: never try to optimize Lua too much, it's never worth it, and, just let Lua iterate.
+
+1. While being smol, it still fails to outperform another state of the art JIT - JS V8. And thats given that JS V8 is big tech kind state of the art, which means there is certainly at the very least a significant room for improvement. Advantage of Lua in comparison to V8: less RAM consuption for small programs, so it's reasonable to run a bit of LuaJIT on weak hardware, like phones, watches, some smart-whatever, robots. In that case it's not the worst choice.
+
+2. Readability syntax is a meme, I am here to code, not to shitpost, I prefer completely different state of mind from that, something very different, like curly braces and what not.
+
+3. Lua bytecode, same way as Python, keeps function and variable names uncompressed. You could argue but hey that means we can at least restore the original file almost one-to-one from bytecode? Who needs that really, when RAM efficiency is 25%(!) better after using a minifier, and if you use minifier, you abandon debugging and readability(just like in Python, which is a meme language too, and it's typical very readable one letter long variable names). This is how as codebase grows, Lua loses it's only advantage over V8. Hence technically peak Lua is a joke.
+
+3. Peak performance Lua cant be, without a lot of effort, transpiled and rewritten into peak performance compiled language, since it's behavior and optimization techniques are drastically different and by that I mean next level drastically different. Therefore it's not as good for prototyping as JS V8, unless you denounce the very idea of optimizing Lua as heresy.
+
+4. LuaJIT does so much behind your back, so that performance becomes exhaustingly unpredictable. Stuff that works in nearly any other language does not work here, and you will often have to rely on profiler no matter how good you are with Lua, rather than act according to assumptions based on fundamentals. You could even say that what you have to do when Lua performance is concerned, is literal trial and error. You then find a sweet spot and never touch that part of the code again, because it's impossible to reason with/reliably reproduce/improve upon.
+
+5. While you could cope that CPU just does not have enough cache and all, clearly, LuaJIT is best at optimizing *simple* loops. Branches, hash look ups, math? Try your best to decrease the amount for all of those in a loop. It appears that Lua would rather iterate uselessly over and over again the same entries, than have a branch to cut amount of iterations/operations in general. Optimization tip is basically this: try to break down a complicated loop into several simpler loops. LuaJIT is ridiculously fast with simple loops.
+
+6. It appears that most popular object positioned most efficiently in memory. I am not entirely certain how exactly does that happen, because I didn't study LuaJIT source much since it's underwhelming performance in anything remotely complicated leaves me feeling powerless, so it's not fun. A hack could be a loop that interacts with an object on startup, if you call/interact with the object enough times it will be slightly faster. It is noticeable in massively expensive loops.
+
+7. If you know you are guaranteeed to have an object consume more RAM during runtime, you may want to preallocate if the codebase is complex enough. Well, at least this behavior can be fully expected based on fundamentals.
+
+8. Refrain from having too many hash look-ups, it's not slow by itself, but apparently it clogs cache fast, so sometimes adding just one hash look-up can result in a massive drop in performance. Surprisingly, refrain from having too much of one of the most simplest parts of LuaJIT - math, best is to pull numbers out of Lua' ass, like in Cozy Lights. Luckily predictably this time, branches are the worst in a loop, however this time they are bad even if they cut a massive amount of operations. So you have to balance here, peak performance Lua demands abandoning DRY completely, but that also means you have to consume more RAM. Ideal Lua loop is when it does nothing at all, just iterates. Just let Lua iterate.
+
+9. You can obviously somewhat control cache with local variables, but there is a catch, it only gives somewhat coherent performance results if the loop is very simple.
+
+10. Apparently because of memory allocation being complex in LuaJIT, it can crash during trying to allocate too much in one go as if it's in the earliest dev stage and not ready for prod. JS V8 maybe leaks, but at least does not crash just like that.
+
+11. To make any good use of ffi types, you have to be aware of the fact that amount of types in function context will affect it's performance. More types = slower. So same as V8, it might optimize smaller functions better, but not necessarily, it depends: if it's one lua type number and there is a lot of work to do for that type, then you better off having a big one surely. Ffi is not a simple plug-and-play for previously optimized pure Lua algo, you may need to restructure your code to ensure more types dont clog the cache. And with Minetest API it may end up being useless.
+
+12. Offloading work to C has it's caveats. If you are doing it through ffi and need to manipulate a lot of data, like vm_data in Cozy Lights example, while your algorithm itself will be faster, if like with Minetest example, the api expects lua table and only that, you will have to interpret C results, run a loop to make a lua table, and that part is so extremely slow, you might end up with slower code overall. In less complicated cases it's useful.
+
+## LICENSE
+
+MIT+(you are not legally allowed to infect it with GPL, AGPL or EUPL) for my code.
+
+Will appreciate reasonable attribution, as in, dont be a typical open source dev who takes a good part of some other open source project and only mentions it in code, so that not only most of the devs, users have no way of ever learning about that.
+
+And there is a texture from MTG, which will be eventually replaced:
+
+default_glass.png is by Krock (CC0 1.0)
+
+my debug textures are WTFPL if anything
diff --git a/mods/cozylights/chat_commands.lua b/mods/cozylights/chat_commands.lua
new file mode 100644
index 00000000..66cca859
--- /dev/null
+++ b/mods/cozylights/chat_commands.lua
@@ -0,0 +1,536 @@
+local c_air = minetest.get_content_id("air")
+local c_light1 = minetest.get_content_id("cozylights:light1")
+
+local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
+ c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
+local c_light14 = c_lights[14]
+local c_light_debug1 = minetest.get_content_id("cozylights:light_debug1")
+local c_light_debug14 = c_light_debug1 + 13
+
+local mf = math.floor
+
+local clearlights = {
+ params = "",
+ description = "removes cozy and debug light nodes. max radius is 120 for now",
+ func = function(name, param)
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ local size = tonumber(param) or cozylights.default_size
+ minetest.log("action", name .. " uses /clearlights "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then
+ return false, "Radius is too big"
+ end
+ cozylights:clear(pos,size)
+ return true, "Done."
+ end,
+}
+
+local rebuildlights = {
+ params = "",
+ description = "force rebuilds lights in the area. max radius is 120 for now",
+ func = function(name, param)
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ local size = tonumber(param) or cozylights.default_size
+ minetest.log("action", name .. " uses /rebuildlights "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then return false, "Radius is too big" end
+ local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos, size)
+ for i in a:iterp(minp,maxp) do
+ local node_name = minetest.get_name_from_content_id(data[i])
+ local cozy_item = cozylights.cozy_items[node_name]
+ if cozy_item ~= nil then
+ local radius, _ = cozylights:calc_dims(cozy_item)
+ local posrebuild = a:position(i)
+ if vector.in_area(vector.subtract(posrebuild, radius), minp, maxp)
+ and vector.in_area(vector.add(posrebuild, radius), minp, maxp)
+ then
+ cozylights:draw_node_light(posrebuild, cozy_item, vm, a, data, param2data)
+ else
+ local single_light_queue = cozylights.single_light_queue
+ single_light_queue[#single_light_queue+1] = {
+ pos=posrebuild,
+ cozy_item=cozy_item
+ }
+ end
+ end
+ end
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ return true, "Done."
+ end,
+}
+
+local fixedges = {
+ params = "",
+ description = "same as rebuild lights but additionally fixes edges for all lights in the area, regardless of always_fix_edges setting. max radius is 120",
+ func = function(name, param)
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ local size = tonumber(param) or cozylights.default_size
+ minetest.log("action", name .. " uses /fixedges "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then return false, "Radius is too big" end
+ local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos, size)
+ for i in a:iterp(minp,maxp) do
+ local node_name = minetest.get_name_from_content_id(data[i])
+ local cozy_item = cozylights.cozy_items[node_name]
+ if cozy_item ~= nil then
+ local radius, _ = cozylights:calc_dims(cozy_item)
+ local posrebuild = a:position(i)
+ if vector.in_area(vector.subtract(posrebuild, radius), minp, maxp)
+ and vector.in_area(vector.add(posrebuild, radius), minp, maxp)
+ then
+ cozylights:draw_node_light(posrebuild, cozy_item, vm, a, data, param2data, true)
+ else
+ local single_light_queue = cozylights.single_light_queue
+ single_light_queue[#single_light_queue+1] = {
+ pos=posrebuild,
+ cozy_item=cozy_item
+ }
+ end
+ end
+ end
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ return true, "Done."
+ end,
+}
+
+local cozydebugon = {
+ params = "",
+ description = "replaces cozy light nodes with debug light nodes which are visible and interactable in an area",
+ func = function(name, param)
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ local size = tonumber(param) or cozylights.default_size
+ minetest.log("action", name .. " uses /cozydebugon "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then
+ return false, "Radius is too big"
+ end
+ local minp,maxp,vm,data,_,a = cozylights:getVoxelManipData(pos,size)
+ for i in a:iterp(minp, maxp) do
+ local cid = data[i]
+ if cid >= c_light1 and cid <= c_light14 then
+ data[i] = cid + 14
+ end
+ end
+ cozylights:setVoxelManipData(vm,data)
+ return true, "Done."
+ end,
+}
+
+local cozydebugoff = {
+ params = "",
+ description = "replaces debug light nodes back with cozy light nodes in an area",
+ func = function(name, param)
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ local size = tonumber(param) or cozylights.default_size
+ minetest.log("action", name .. " uses /cozydebugoff "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then
+ return false, "Radius is too big"
+ end
+ local minp,maxp,vm,data,_,a = cozylights:getVoxelManipData(pos,size)
+ for i in a:iterp(minp, maxp) do
+ local cid = data[i]
+ if cid >= c_light14 + 1 and cid <= c_light_debug14 then
+ data[i] = cid - 14
+ end
+ end
+ cozylights:setVoxelManipData(vm,data)
+ return true, "Done."
+ end,
+}
+
+local optimizeformobile = {
+ params = "",
+ description = "optimizes schematic for mobile and potato gpu",
+ func = function(name, param)
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ local size = tonumber(param) or cozylights.default_size
+ minetest.log("action", name .. " uses /optimizeformobile "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then
+ return false, "Radius is too big"
+ end
+ local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
+ local zstride, ystride = a.zstride, a.ystride
+ local function keep(i)
+ local keep = false
+ for inz = -1,1 do
+ for iny = -1,1 do
+ for inx = -1,1 do
+ local inidx = i + inx + iny*ystride + inz*zstride
+ if a:containsi(inidx) then
+ local incid = data[inidx]
+ if incid ~= c_air and (incid < c_light1 or incid > c_light14 + 14) then
+ keep = true
+ break
+ end
+ end
+ end
+ end
+ end
+ return keep
+ end
+ for i in a:iterp(minp, maxp) do
+ local cid = data[i]
+ if cid >= c_light1 and cid <= c_light14 + 14 then
+ if not keep(i) then
+ data[i] = c_air
+ param2data[i] = 0
+ end
+ end
+ end
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ return true, "Done."
+ end,
+}
+
+local spawnlight = {
+ params = " ",
+ description = "spawns light_brush-like light with given characteristics at player position",
+ func = function(name, param)
+ local brightness, radius, strength = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ minetest.log("action", name .. " uses /optimizeformobile "..brightness.." "..radius.." "..strength.." at position: "..cozylights:dump(pos))
+ brightness = mf(tonumber(brightness) or 0)
+ if brightness < 0 then brightness = 0 end
+ if brightness > 14 then brightness = 14 end
+ radius = tonumber(radius) or 0
+ if radius < 0 then radius = 0 end
+ if radius > 120 then radius = 120 end
+ strength = tonumber(strength) or 0
+ if strength < 0 then strength = 0 end
+ if strength > 1 then strength = 1 end
+ local lb = {brightness=brightness,radius=radius,strength=strength,mode=0,cover_only_surfaces=0}
+ cozylights:draw_brush_light(pos, lb)
+ return true, "Done."
+ end,
+}
+
+local cozysettingsgui = {
+ privs = {},
+ description = "changes global ambient light settings",
+ func = function(name)
+ local settings_formspec = {
+ "formspec_version[4]",
+ --"size[6,6.4]",
+ "size[5.2,8.6]",
+ "label[0.95,0.5;Global Cozy Lights Settings]",
+
+ "label[0.95,1.35;Wielded Light Radius]",
+ "field[3.5,1.1;0.8,0.5;wielded_light_radius;;"..cozylights.max_wield_light_radius.."]",
+ "tooltip[0.95,1.1;3.4,0.5;If radius is -1 cozy wielded light is disabled, if 0 then only one node will be lit up just like in familiar Minetest wielded light mod.\n"..
+ "If not zero then it's a sphere of affected nodes with specified radius.\n"..
+ "Max radius is 30 as of now. If you run a potato - you may want to decrease it.]",
+
+ "label[0.95,2.05;Wield Light Step]",
+ "field[3.5,1.8;0.8,0.5;wield_step;;"..cozylights.wield_step.."]",
+ "tooltip[0.95,1.8;3.4,0.5;Cozy Lights Wield Light step - smaller value will result in more frequent, fluid wield light update.\n"..
+ "Smaller values might be too expensive for potato. Valid values are from 0.01 to 10.00]",
+
+ "label[0.95,2.75;Brush Hold Step]",
+ "field[3.5,2.5;0.8,0.5;brush_hold_step;;"..cozylights.brush_hold_step.."]",
+ "tooltip[0.95,2.5;3.4,0.5;Brush Hold Step - smaller value will result in more frequent, fluid light update for light brush on mouse hold.\n"..
+ "Smaller values wont necessarily result in a more fluid update even on a beast PC, because Minetest architecture is too complicated for it to not be janky. Valid values are from 0.01 to 10.00]",
+
+ "label[0.95,3.45;On_Gen() Step]",
+ "field[3.5,3.2;0.8,0.5;on_gen_step;;"..cozylights.on_gen_step.."]",
+ "tooltip[0.95,3.2;3.4,0.5;Generated areas step - smaller value will result in more frequent, fluid light update for newly generated map chunks and schematics if they have light sources, it will also rebuild lights faster when something was destroyed.\n"..
+ "Small values will be too expensive for potato. Valid values are from 0.01 to 10.00]",
+
+ "label[0.95,4.15;Brightness Factor]",
+ "field[3.5,3.9;0.8,0.5;brightness_factor;;"..cozylights.brightness_factor.."]",
+ "tooltip[0.95,3.9;3.4,0.5;Brightness factor determines how bright overall(relative to own light source brightness) the light will be.\n"..
+ "Affects placed nodes(like torches, mese lamps, etc) and wielded light, but not light brush.\n"..
+ "Valid values are from -10.0 to 10.0.\n"..
+ "Brightness factor is not an equivalent of light source brightness(from 1 to 14), it is very low key, affects lights slightly.]",
+
+ "label[0.95,4.85;Reach Factor]",
+ "field[3.5,4.6;0.8,0.5;reach_factor;;"..cozylights.reach_factor.."]",
+ "tooltip[0.95,4.6;3.4,0.5;Reach factor determines how far light of all light source nodes will reach.\n"..
+ "Affects placed nodes(like torches, mese lamps, etc) and wielded light, but not light brush.\n"..
+ "Valid values are from 0.0 to 10.0.\n"..
+ "Not recommended to change if you are not willing to spend probably a lot of time tuning lights.\n"..
+ "Not recommended to Increase if you run a potato.]",
+
+ "label[0.95,5.55;Dim Factor]",
+ "field[3.5,5.3;0.8,0.5;dim_factor;;"..cozylights.dim_factor.."]",
+ "tooltip[0.95,5.3;3.4,0.5;Dim factor determines how quickly the light fades farther from the source.\n"..
+ "Affects placed nodes(like torches, mese lamps, etc) and wielded light, but not light brush.\n"..
+ "Valid values are from 0.0 to 10.0.\n"..
+ "Not recommended to change if you are not willing to spend probably a lot of time tuning lights.\n"..
+ "Not recommended to Decrease if you run a potato.]",
+
+ "label[0.95,6.25;Uncozy mode]",
+ "field[3.5,6;0.8,0.5;uncozy_mode;;"..cozylights.uncozy_mode.."]",
+ "tooltip[0.95,6;3.4,0.5;Uncozy mode if above 0 removes all cozy lights in an area with specified radius around all players continuously.\n"..
+ "Useful before Cozy Lights mod uninstall. If you uninstall without prior running this, you will be left with spheres of unknown nodes in an area where Cozy Lights were active.\n"..
+ "Valid values are from 0 to 120. As of now can crash with out of memory error if you move too fast and the radius is too high.]",
+
+ "label[0.95,6.95;Crispy Potato]",
+ "checkbox[3.5,6.95;crispy_potato;;"..(cozylights.crispy_potato == true and "true" or "false").."]",
+ "tooltip[0.95,6.7;3.4,0.4;Crispy Potato when checked will auto adjust wielded light and generated area step times to keep potato alive.]",
+
+ "button_exit[1.1,7.5;3,0.8;confirm;Confirm]",
+ }
+ minetest.show_formspec(name, "cozylights:settings",table.concat(settings_formspec, ""))
+ return true
+ end,
+}
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname ~= ("cozylights:settings") then return end
+ if player == nil then return end
+ if fields.wielded_light_radius then
+ local wielded_light_radius = tonumber(fields.wielded_light_radius) > 30 and 30 or tonumber(fields.wielded_light_radius)
+ wielded_light_radius = wielded_light_radius < -1 and -1 or mf(wielded_light_radius or -1)
+ cozylights:set_wielded_light_radius(wielded_light_radius)
+ cozylights:switch_wielded_light(wielded_light_radius ~= -1)
+ end
+ if fields.wield_step then
+ local wield_step = tonumber(fields.wield_step) > 1 and 1 or tonumber(fields.wield_step)
+ wield_step = wield_step < 0.01 and 0.01 or wield_step
+ cozylights:set_wield_step(wield_step)
+ end
+ if fields.brush_hold_step then
+ local brush_hold_step = tonumber(fields.brush_hold_step) > 1 and 1 or tonumber(fields.brush_hold_step)
+ brush_hold_step = brush_hold_step < 0.01 and 0.01 or brush_hold_step
+ cozylights:set_brush_hold_step(brush_hold_step)
+ end
+ if fields.on_gen_step then
+ local on_gen_step = tonumber(fields.on_gen_step) > 1 and 1 or tonumber(fields.on_gen_step)
+ on_gen_step = on_gen_step < 0.01 and 0.01 or on_gen_step
+ cozylights:set_on_gen_step(on_gen_step)
+ end
+ if fields.brightness_factor then
+ local brightness_factor = tonumber(fields.brightness_factor) > 10 and 10 or tonumber(fields.brightness_factor)
+ cozylights.brightness_factor = brightness_factor < -10 and -10 or brightness_factor or 3
+ minetest.settings:set("cozylights_brightness_factor",cozylights.brightness_factor)
+ end
+ if fields.reach_factor then
+ local reach_factor = tonumber(fields.reach_factor) > 10 and 10 or tonumber(fields.reach_factor)
+ cozylights.reach_factor = reach_factor < 0 and 0 or reach_factor or 4
+ minetest.settings:set("cozylights_reach_factor",cozylights.reach_factor)
+ end
+ if fields.dim_factor then
+ local dim_factor = tonumber(fields.dim_factor) > 10 and 10 or tonumber(fields.dim_factor)
+ cozylights.dim_factor = dim_factor < 0 and 0 or dim_factor or 9
+ minetest.settings:set("cozylights_dim_factor",cozylights.dim_factor)
+ end
+ if fields.crispy_potato then
+ cozylights.crispy_potato = fields.crispy_potato == "true" and true or false
+ minetest.settings:set_bool("cozylights_crispy_potato", cozylights.crispy_potato)
+ end
+ if fields.uncozy_mode then
+ local uncozy_mode = tonumber(fields.uncozy_mode) > 120 and 120 or tonumber(fields.uncozy_mode)
+ cozylights.uncozy_mode = uncozy_mode < 0 and 0 or uncozy_mode
+ minetest.settings:set("cozylights_uncozy_mode", cozylights.uncozy_mode)
+ end
+end)
+
+
+local cozysettings = {
+ params = " ",
+ privs = {},
+ description = "changes global ambient light settings",
+ func = function(_, param)
+ local brightness_, reach_factor_, dim_factor_ = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+ brightness_ = tonumber(brightness_)
+ if brightness_ ~= nil then
+ cozylights.brightness_factor = brightness_
+ minetest.settings:set("cozylights_brightness_factor",brightness_)
+ end
+ reach_factor_ = tonumber(reach_factor_)
+ if reach_factor_ ~= nil then
+ cozylights.reach_factor = reach_factor_
+ minetest.settings:set("cozylights_reach_factor",reach_factor_)
+ end
+ dim_factor_ = tonumber(dim_factor_)
+ if dim_factor_ ~= nil then
+ cozylights.dim_factor = dim_factor_
+ minetest.settings:set("cozylights_dim_factor",dim_factor_)
+ end
+ return true, "set brightness as "..brightness_..", reach_factor as "..reach_factor_..", dim_factor as "..dim_factor_
+ end,
+}
+
+local daynightratio = {
+ params = "",
+ description = "fixes old schematic torches alignment to walls and what not",
+ func = function(name, param)
+ local player = minetest.get_player_by_name(name)
+ local pos = vector.round(player:getpos())
+ local ratio = tonumber(param)
+ minetest.log("action", name .. " uses /daynightratio "..ratio.." at position: "..cozylights:dump(pos))
+ if ratio > 1.0 then ratio = 1 end
+ if ratio < 0 then ratio = 0 end
+ player:override_day_night_ratio(ratio)
+ return true, "Done."
+ end,
+}
+
+local cozyadjust = {
+ params = " ",
+ description = "adjust brightness of all light nodes in the area.\n"..
+ "keep_map is 1 by default and can be omitted, when it's 1 and adjust_by will result in a value out of bounds of 1-14(engine light levels) "..
+ "even for one node in the area, the command will revert(have no effect at all), so that light map will be preserved\n"..
+ "if you are ok with breaking light map, type 0 for third value",
+ func = function(name, param)
+ local pos = vector.round(minetest.get_player_by_name(name):getpos())
+ local size, adjust_by, keep_map = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+ size = mf(tonumber(size) or cozylights.default_size)
+ adjust_by = mf(tonumber(adjust_by) or 1)
+ keep_map = mf(tonumber(keep_map) or 1)
+ minetest.log("action", name .. " uses /clearlights "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then
+ return false, "Radius is too big"
+ end
+ local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
+ for i in a:iterp(minp, maxp) do
+ local cid = data[i]
+ if cid >= c_light1 and cid <= c_light14 then
+ local precid = cid + adjust_by
+ if precid >= c_light1 and precid <= c_light14 then
+ data[i] = precid
+ param2data[i] = precid
+ elseif keep_map == 1 then
+ return false, "Aborted to preserve light map."
+ elseif precid > c_light14 then
+ data[i] = c_light14
+ param2data[i] = c_light14
+ else
+ data[i] = c_air
+ param2data[i] = 0
+ end
+ elseif cid >= c_light_debug1 and cid <= c_light_debug14 then
+ local precid = cid + adjust_by
+ if precid >= c_light_debug1 and precid <= c_light_debug14 then
+ data[i] = precid
+ param2data[i] = precid
+ elseif keep_map == 1 then
+ return false, "Aborted to preserve light map."
+ elseif precid > c_light_debug14 then
+ data[i] = c_light_debug14
+ param2data[i] = c_light_debug14
+ else
+ data[i] = c_air
+ param2data[i] = 0
+ end
+ end
+
+ end
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ return true, "Done."
+ end,
+}
+
+local fixtorches = {
+ params = "",
+ description = "fixes old schematic torches alignment to walls and what not",
+ func = function(name, param)
+ local placer = minetest.get_player_by_name(name)
+ local pos = vector.round(placer:getpos())
+ local size = mf(tonumber(param) or cozylights.default_size)
+ minetest.log("action", name .. " uses /fixtorches "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then
+ return false, "Radius is too big"
+ end
+ local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
+ local c_torch = minetest.get_content_id("default:torch")
+ local c_torch_wall = c_torch + 1
+ local c_torch_ceiling = c_torch + 2
+ local ystride, zstride = a.ystride, a.zstride
+ for i in a:iterp(minp, maxp) do
+ if data[i] == c_torch then
+ local above = minetest.registered_nodes[minetest.get_name_from_content_id(data[i-ystride])]
+ if above.walkable == true then
+ data[i] = c_torch_ceiling
+ param2data[i] = 0
+ end
+
+ local below = minetest.registered_nodes[minetest.get_name_from_content_id(data[i-ystride])]
+ if below.walkable == true then
+ data[i] = c_torch
+ param2data[i] = 1
+ end
+
+ if above.walkable == false and below.walkable == false then
+ local plusz = minetest.registered_nodes[minetest.get_name_from_content_id(data[i + zstride])]
+ data[i] = c_torch_wall
+ if plusz.walkable == true then param2data[i] = 4 end
+ local minusz = minetest.registered_nodes[minetest.get_name_from_content_id(data[i - zstride])]
+ if minusz.walkable == true then param2data[i] = 5 end
+ local plusx = minetest.registered_nodes[minetest.get_name_from_content_id(data[i + 1])]
+ if plusx.walkable == true then param2data[i] = 2 end
+ local minusx = minetest.registered_nodes[minetest.get_name_from_content_id(data[i - 1])]
+ if minusx.walkable == true then param2data[i] = 3 end
+ end
+ end
+ end
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ return true, "Done."
+ end,
+}
+
+local uncozymode = {
+ params = "",
+ description = "clears lights every 5 seconds around every player in area of given radius.\n "..
+ "Useful before Cozy Lights uninstall or for those who is uncertain about the mod.\n",
+ func = function(name, param)
+ local placer = minetest.get_player_by_name(name)
+ local pos = vector.round(placer:getpos())
+ local size = mf(tonumber(param) or cozylights.default_size)
+ minetest.log("action", name .. " uses /uncozymode "..size.." at position: "..cozylights:dump(pos))
+ if size > 120 then
+ return false, "Radius is too big"
+ end
+ cozylights.uncozy_mode = size
+ minetest.settings:set("cozylights_uncozy_mode",size)
+ return true, "Done."
+ end,
+}
+
+local cozymode = {
+ description = "stops /uncozymode.",
+ func = function(name)
+ local placer = minetest.get_player_by_name(name)
+ local pos = vector.round(placer:getpos())
+ minetest.log("action", name .. " uses /cozymode at position: "..cozylights:dump(pos))
+ cozylights.uncozy_mode = 0
+ minetest.settings:set("cozylights_uncozy_mode",0)
+ return true, "Done."
+ end,
+}
+
+
+minetest.register_chatcommand("clearlights", clearlights)
+minetest.register_chatcommand("zcl", clearlights)
+
+minetest.register_chatcommand("rebuildlights", rebuildlights)
+minetest.register_chatcommand("zrl", rebuildlights)
+
+minetest.register_chatcommand("fixedges", fixedges)
+minetest.register_chatcommand("zfe", fixedges)
+
+minetest.register_chatcommand("cozydebugon", cozydebugon)
+minetest.register_chatcommand("zdon", cozydebugon)
+
+minetest.register_chatcommand("cozydebugoff", cozydebugoff)
+minetest.register_chatcommand("zdoff", cozydebugoff)
+
+minetest.register_chatcommand("optimizeformobile", optimizeformobile)
+minetest.register_chatcommand("zofm", optimizeformobile)
+
+minetest.register_chatcommand("spawnlight", spawnlight)
+minetest.register_chatcommand("zsl", spawnlight)
+
+minetest.register_chatcommand("cozysettings", cozysettingsgui)
+minetest.register_chatcommand("zs", cozysettingsgui)
+
+minetest.register_chatcommand("daynightratio", daynightratio)
+minetest.register_chatcommand("zdnr", daynightratio)
+
+minetest.register_chatcommand("cozyadjust", cozyadjust)
+minetest.register_chatcommand("za", cozyadjust)
+
+minetest.register_chatcommand("fixtorches", fixtorches)
+minetest.register_chatcommand("zft", fixtorches)
+
+minetest.register_chatcommand("uncozymode", uncozymode)
+minetest.register_chatcommand("uzm", uncozymode)
+
+minetest.register_chatcommand("cozymode", cozymode)
+minetest.register_chatcommand("zm", cozymode)
\ No newline at end of file
diff --git a/mods/cozylights/helpers.lua b/mods/cozylights/helpers.lua
new file mode 100644
index 00000000..fbc8f594
--- /dev/null
+++ b/mods/cozylights/helpers.lua
@@ -0,0 +1,41 @@
+function cozylights:dump(o)
+ if type(o) == 'table' then
+ local s = '{ '
+ for k,v in pairs(o) do
+ if type(k) ~= 'number' then k = '"'..k..'"' end
+ s = s .. '['..k..'] = ' .. cozylights:dump(v) .. ','
+ end
+ return s .. '} '
+ else
+ return tostring(o)
+ end
+end
+
+function cozylights:finalize(table)
+ return setmetatable({}, {
+ __index = table,
+ __newindex = nil
+ })
+end
+
+function cozylights:prealloc(table, amount, default_val)
+ for i = 1, amount do
+ table[i] = default_val
+ end
+end
+
+function cozylights:mod_loaded(str)
+ if minetest.get_modpath(str) ~= nil then
+ return true
+ end
+ return false
+end
+
+function cozylights:findIn(value,array)
+ for i=1, #array do
+ if array[i] == value then
+ return true
+ end
+ end
+ return false
+end
\ No newline at end of file
diff --git a/mods/cozylights/init.lua b/mods/cozylights/init.lua
new file mode 100644
index 00000000..816a7dd5
--- /dev/null
+++ b/mods/cozylights/init.lua
@@ -0,0 +1,688 @@
+cozylights = {
+ -- constant size values and tables
+ version = "0.2.8",
+ default_size = tonumber(minetest.settings:get("mapfix_default_size")) or 40,
+ brightness_factor = tonumber(minetest.settings:get("cozylights_brightness_factor")) or 8,
+ reach_factor = tonumber(minetest.settings:get("cozylights_reach_factor")) or 2,
+ dim_factor = tonumber(minetest.settings:get("cozylights_dim_factor")) or 9.5,
+ wield_step = tonumber(minetest.settings:get("cozylights_wield_step")) or 0.01,
+ brush_hold_step = tonumber(minetest.settings:get("cozylights_brush_hold_step")) or 0.07,
+ on_gen_step = tonumber(minetest.settings:get("cozylights_on_gen_step")) or 0.7,
+ max_wield_light_radius = tonumber(minetest.settings:get("cozylights_wielded_light_radius")) or 17,
+ override_engine_lights = minetest.settings:get_bool("cozylights_override_engine_lights", false),
+ always_fix_edges = minetest.settings:get_bool("cozylights_always_fix_edges", false),
+ uncozy_mode = tonumber(minetest.settings:get("cozylights_uncozy_mode")) or 0,
+ crispy_potato = minetest.settings:get_bool("cozylights_crispy_potato", true),
+ -- this is a table of modifiers for global light source settings.
+ -- lowkeylike and dimlike usually assigned to decorations in hopes to make all ambient naturally occuring light sources weaker
+ -- this is for two reasons:
+ -- 1. performance: never know how many various nice looking blocks which emit light will be there, or for example computing lights for
+ -- every node of a lava lake would be extremely expensive if those would reach far/would be very bright
+ -- 2. looks: they were made with default engine lighting in mind, so usually are very frequent, with such frequency default cozylights
+ -- settings will make the environment look blunt
+ coziest_table = {
+ --"dimlike"
+ [1] = {
+ brightness_factor = 0,
+ reach_factor = 0,
+ dim_factor = 0
+ },
+ --"lowkeylike" almost the same as dimlike, but reaches much farther with its barely visible light
+ [2] = {
+ brightness_factor = 0,
+ reach_factor = 2,
+ dim_factor = -3
+ },
+ -- "candlelike" something-something
+ [3] = {
+ brightness_factor = 0,
+ reach_factor = 2,
+ dim_factor = -3
+ },
+ -- "torchlike" torches, fires, flames. made much dimmer than what default engine lights makes them
+ [4] = {
+ brightness_factor = -2,
+ reach_factor = 0,
+ dim_factor = 0
+ },
+ -- "lamplike" a bright source, think mese lamp(actually turned out its like a projector, and below is even bigger projector)
+ [5] = {
+ brightness_factor = 0,
+ reach_factor = 3,
+ dim_factor = 4
+ },
+ -- "projectorlike" a bright source with massive reach
+ [6] = {
+ brightness_factor = 1,
+ reach_factor = 3,
+ dim_factor = 4
+ },
+ },
+ -- appears nodes and items might not necessarily be the same array
+ source_nodes = nil,
+ cozy_items = nil,
+ -- dynamic size tables, okay now what about functions
+ cozycids_sunlight_propagates = {},
+ cozycids_light_sources = {},
+ cozyplayers = {},
+ area_queue = {},
+ single_light_queue = {},
+ modpath = minetest.get_modpath(minetest.get_current_modname())
+}
+
+local modpath = minetest.get_modpath(minetest.get_current_modname())
+dofile(modpath.."/helpers.lua")
+
+-- backrooms test attempts to resolve mt engine lights problem with invisible lights, default settings will result
+-- in many places being very well lit
+-- me thinks ideal scenery with cozy lights in particular can be achieved with removal of all invisible lights
+-- it also looks interesting after maybe a two thirds of light sources are broken
+-- however the backrooms idea is not about broken windows theory at all, more about supernatural absence of any life
+-- in a seemingly perfectly functioning infinite manmade mess, or idk i am not mentally masturbating any further,
+-- some of the internets do that way too often, way too much
+if cozylights:mod_loaded("br_core") then
+ cozylights.brightness_factor = cozylights.brightness_factor - 6
+end
+
+--if cozylights:mod_loaded("default") then
+-- default.can_grow = function(pos)
+-- local node_under = minetest.get_node_or_nil({x = pos.x, y = pos.y - 1, z = pos.z})
+-- if not node_under then
+-- return false
+-- end
+-- if minetest.get_item_group(node_under.name, "soil") == 0 then
+-- return false
+-- end
+-- local light_level = minetest.get_node_light(pos)
+-- if not light_level or light_level < 13 then
+-- return false
+-- end
+-- return true
+-- end
+--end
+
+
+dofile(modpath.."/nodes.lua")
+dofile(modpath.."/shared.lua")
+dofile(modpath.."/chat_commands.lua")
+--ffi = require("ffi")
+dofile(modpath.."/wield_light.lua")
+dofile(modpath.."/node_light.lua")
+dofile(modpath.."/light_brush.lua")
+
+local c_air = minetest.get_content_id("air")
+local c_light1 = minetest.get_content_id("cozylights:light1")
+
+local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
+ c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
+
+local mf = math.floor
+
+------------------------------------------
+
+minetest.register_on_mods_loaded(function()
+ local source_nodes = {}
+ local cozy_items = {}
+ local cozycids_sunlight_propagates = {}
+ local cozycids_light_sources = {}
+ local override = cozylights.override_engine_lights
+ for _,def in pairs(minetest.registered_items) do
+ if def.light_source and def.light_source > 1
+ and def.drawtype ~= "airlike" and def.drawtype ~= "liquid"
+ and string.find(def.name, "lava_flowing") == nil
+ and string.find(def.name, "lava_source") == nil
+ --and def.liquid_renewable == nil and def.drowning == nil
+ then
+ -- here we are going to define more specific skips and options for sus light sources
+ local skip = false
+ if string.find(def.name, "everness:") ~= nil and def.groups.vine ~= nil then
+ skip = true -- like goto continue
+ end
+ if skip == false then
+ local mods = nil
+ --if def.drawtype == "plantlike" then
+ -- mods = 1
+ --end
+ --if string.find(def.name,"torch") then
+ -- mods = 3
+ --end
+ cozy_items[def.name] = {light_source= def.light_source or 0,floodable=def.floodable or false,modifiers=mods}
+ if not string.find(def.name, "cozylights:light") then
+ source_nodes[#source_nodes+1] = def.name
+ end
+ end
+ end
+ end
+ for node,def in pairs(minetest.registered_nodes) do
+ if def.sunlight_propagates == true then
+ local cid = minetest.get_content_id(def.name)
+ cozycids_sunlight_propagates[cid] = true
+ end
+ if def.light_source and def.light_source > 1
+ and def.drawtype ~= "airlike" and def.drawtype ~= "liquid"
+ and not string.find(def.name, "lava_flowing")
+ and not string.find(def.name, "lava_source")
+ --and def.liquid_viscosity == nil and def.liquid_renewable == nil and def.drowning == nil
+ then
+ local cid = minetest.get_content_id(def.name)
+ if cid < c_lights[1] or cid > c_lights[14]+14 then
+ local skip = false
+ if string.find(def.name, "everness:") ~= nil and def.groups.vine ~= nil then
+ skip = true -- like goto :continue:
+ end
+ if skip == false then
+ cozycids_light_sources[cid] = true
+ if def.on_destruct then
+ local base_on_destruct = def.on_destruct
+ minetest.override_item(node,{
+ on_destruct = function(pos)
+ base_on_destruct(pos)
+ print(cozylights:dump(pos))
+ print(def.name.." is destroyed")
+ cozylights:destroy_light(pos, cozy_items[def.name])
+ end,
+ })
+ else
+ minetest.override_item(node,{
+ on_destruct = function(pos)
+ print(cozylights:dump(pos))
+ print(def.name.." is destroyed1")
+ cozylights:destroy_light(pos, cozy_items[def.name])
+ end,
+ })
+ end
+ if def.on_construct ~= nil then
+ local base_on_construct = def.on_construct
+ local light = override == true and 1 or def.light_source
+ if def.name == "br_core:ceiling_light_1" then
+ light = def.light_source - 7
+ end
+ minetest.override_item(node,{
+ light_source = light,
+ use_texture_alpha= def.use_texture_alpha or "clip",
+ on_construct = function(pos)
+ base_on_construct(pos)
+ cozylights:draw_node_light(pos, cozy_items[def.name])
+ end,
+ })
+ else
+ local light = override == true and 1 or def.light_source
+ if def.name == "br_core:ceiling_light_1" then
+ light = def.light_source - 7
+ end
+ minetest.override_item(node,{
+ light_source = light,
+ use_texture_alpha= def.use_texture_alpha or "clip",
+ on_construct = function(pos)
+ cozylights:draw_node_light(pos, cozy_items[def.name])
+ end,
+ })
+ end
+ end
+ end
+ end
+ end
+ cozylights.source_nodes = source_nodes
+ cozylights.cozy_items = cozy_items
+ cozylights.cozycids_sunlight_propagates = cozycids_sunlight_propagates
+ cozylights.cozycids_light_sources = cozycids_light_sources
+end)
+
+--clean up possible stale wielded light on join, since on server shutdown we cant execute on_leave
+--todo: make it more normal and less of a hack
+function cozylights:on_join_cleanup(pos, radius)
+ local vm = minetest.get_voxel_manip()
+ local emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
+ local data = vm:get_data()
+ local a = VoxelArea:new{
+ MinEdge = emin,
+ MaxEdge = emax
+ }
+ local param2data = vm:get_param2_data()
+ local max_radius = radius * (radius + 1)
+ for z = -radius, radius do
+ for y = -radius, radius do
+ for x = -radius, radius do
+ --local p = vector.add(pos,{x=x,y=y,z=z})
+ local p = {x=x+pos.x,y=y+pos.y,z=z+pos.z}
+ local idx = a:indexp(p)
+ local squared = x * x + y * y + z * z
+ if data[idx] >= c_lights[1] and data[idx] <= c_lights[14] and param2data[idx] == 0 and squared <= max_radius then
+ data[idx] = c_air
+ end
+ end
+ end
+ end
+ vm:set_data(data)
+ vm:update_liquids()
+ vm:write_to_map()
+end
+
+minetest.register_on_joinplayer(function(player)
+ if not player then return end
+ local pos = vector.round(player:getpos())
+ pos.y = pos.y + 1
+ cozylights:on_join_cleanup(pos, 30)
+ cozylights.cozyplayers[player:get_player_name()] = {
+ name=player:get_player_name(),
+ pos_hash=pos.x + (pos.y)*100 + pos.z*10000,
+ wielded_item=0,
+ last_pos=pos,
+ last_wield="",
+ prev_wielded_lights={},
+ lbrush={
+ brightness=6,
+ radius=0,
+ strength=0.5,
+ mode=1,
+ cover_only_surfaces=0,
+ pos_hash=0,
+ }
+ }
+end)
+
+minetest.register_on_leaveplayer(function(player)
+ if not player then return end
+ local name = player:get_player_name()
+ for i=1,#cozylights.cozyplayers do
+ if cozylights.cozyplayers[i].name == name then
+ cozylights:wielded_light_cleanup(player,cozylights.cozyplayers[i],30)
+ table.remove(cozylights.cozyplayers,i)
+ end
+ end
+end)
+
+minetest.register_on_shutdown(function()
+ for i=1,#cozylights.cozyplayers do
+ local player = minetest.get_player_by_name(cozylights.cozyplayers[i].name)
+ if player ~= nil then
+ cozylights:wielded_light_cleanup(player,cozylights.cozyplayers[i],30)
+ end
+ end
+end)
+
+local agent_total = 0
+local agent_count = 0
+local recently_updated = {}
+local function build_lights_after_generated(minp,maxp,sources)
+ local t = os.clock()
+ local vm = minetest.get_voxel_manip()
+ local emin, emax = vm:read_from_map(vector.subtract(minp, 1), vector.add(maxp, 1))
+ local data = vm:get_data()
+ local param2data = vm:get_param2_data()
+ local a = VoxelArea:new{
+ MinEdge = emin,
+ MaxEdge = emax
+ }
+ if sources then
+ for i=1, #sources do
+ local s = sources[i]
+ --local hash = minetest.hash_node_position(s.pos)
+ local hash = s.pos.x + (s.pos.y)*100 + s.pos.z*10000
+ if recently_updated[hash] == nil then
+ recently_updated[hash] = true
+ cozylights:draw_node_light(s.pos, s.cozy_item, vm, a, data, param2data)
+ end
+ end
+ else
+ local cozycids_light_sources = cozylights.cozycids_light_sources
+ for i in a:iterp(minp,maxp) do
+ local cid = data[i]
+ if cozycids_light_sources[cid] then
+ local cozy_item = cozylights.cozy_items[minetest.get_name_from_content_id(cid)]
+ -- check if radius is not too big
+ local radius, _ = cozylights:calc_dims(cozy_item)
+ local p = a:position(i)
+ if a:containsp(vector.subtract(p,radius)) and a:containsp(vector.add(p,radius))
+ then
+ cozylights:draw_node_light(p,cozy_item,vm,a,data,param2data)
+ else
+ table.insert(cozylights.single_light_queue, { pos=p, cozy_item=cozy_item })
+ end
+ end
+ end
+ end
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ agent_total = agent_total + mf((os.clock() - t) * 1000)
+ agent_count = agent_count + 1
+ print("Av build after generated time: "..
+ mf(agent_total/agent_count).." ms. Sample of: "..agent_count..". Areas left: "..#cozylights.area_queue
+ )
+end
+
+--idk, size should be smarter than a constant
+local size = 85
+local function place_schem_but_real(pos, schematic, rotation, replacements, force_placement, flags)
+ if tonumber(schematic) ~= nil or type(schematic) == "string" then -- schematic.data
+ cozylights.area_queue[#cozylights.area_queue+1]={
+ minp=vector.subtract(pos, size),
+ maxp=vector.add(pos, size),
+ sources=nil
+ }
+ return
+ end
+ local sd = schematic.data
+ local update_needed = false
+ for i, node in pairs(sd) do
+ -- todo: account for replacements
+ if cozylights.cozy_items[node.name] then
+ -- rotation can be random so we cant know the position
+ -- todo: account for faster cases when its not random
+ update_needed = true
+ break
+ end
+ end
+ if update_needed == true then
+ local cozycids_light_sources = cozylights.cozycids_light_sources
+ print("UPDATE NEEDED")
+ local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos, size)
+ for i in a:iterp(minp, maxp) do
+ local cid = data[i]
+ if cozycids_light_sources[cid] then
+ local cozy_item = cozylights.cozy_items[minetest.get_name_from_content_id(cid)]
+ -- check if radius is not too big
+ local radius, _ = cozylights:calc_dims(cozy_item)
+ local p = a:position(i)
+ if a:containsp(vector.subtract(p,radius)) and a:containsp(vector.add(p,radius))
+ then
+ cozylights:draw_node_light(p,cozy_item,vm,a,data,param2data)
+ else
+ table.insert(cozylights.single_light_queue, { pos=p, cozy_item=cozy_item })
+ end
+ end
+ end
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ end
+end
+
+--a feeble attempt to cover schematics placements
+local placeschemthatisnotreal = minetest.place_schematic
+--todo: if its a village(several schematics) dont rebuild same lights
+--todo: schematic exception table, if we have discovered for a fact somehow that a particular schematic
+--cant possibly have any kind of lights then we ignore
+--if not in runtime, then a constant table,
+--might require additional tools to load all schematics on contentdb to figure this out
+local schem_queue = {}
+minetest.place_schematic = function(pos, schematic, rotation, replacements, force_placement, flags)
+ if not placeschemthatisnotreal(pos, schematic, rotation, replacements, force_placement, flags) then return end
+ -- now totally real stuff starts to happen
+ schem_queue[#schem_queue+1] = {
+ pos = pos,
+ schematic = schematic,
+ rotation = rotation,
+ replacements = replacements,
+ force_placement = force_placement,
+ flags = flags
+ }
+end
+
+local place_schematic_on_vmanip_nicely = minetest.place_schematic_on_vmanip
+minetest.place_schematic_on_vmanip = function(vmanip, minp, filename, rotation, replacements, force_placement,flags)
+ if not place_schematic_on_vmanip_nicely(vmanip, minp, filename, rotation, replacements, force_placement,flags) then return end
+ schem_queue[#schem_queue+1] = {
+ pos = minp,
+ schematic = filename,
+ rotation = rotation,
+ replacements = replacements,
+ force_placement = force_placement,
+ flags = flags
+ }
+end
+
+local createschemthatisveryreadable = minetest.create_schematic
+minetest.create_schematic = function(p1, p2, probability_list, filename, slice_prob_list)
+ if not createschemthatisveryreadable(p1, p2, probability_list, filename, slice_prob_list) then return end
+ -- unreadable stuff happens here
+ cozylights.area_queue[#cozylights.area_queue+1] = {
+ minp = p1,
+ maxp = p2,
+ sources = nil
+ }
+end
+
+local wield_light_enabled = cozylights.max_wield_light_radius > -1 and true or false
+local wield_step = cozylights.wield_step
+local brush_hold_step = cozylights.brush_hold_step
+local on_gen_step = cozylights.on_gen_step
+
+function cozylights:switch_wielded_light(enabled)
+ wield_light_enabled = enabled
+end
+
+function cozylights:set_wield_step(_time)
+ wield_step = _time
+ minetest.settings:set("cozylights_wield_step",_time)
+ cozylights.wield_step = _time
+end
+
+function cozylights:set_brush_hold_step(_time)
+ brush_hold_step = _time
+ minetest.settings:set("cozylights_brush_hold_step",_time)
+ cozylights.brush_hold_step = _time
+end
+
+function cozylights:set_on_gen_step(_time)
+ on_gen_step = _time
+ minetest.settings:set("cozylights_on_gen_step",_time)
+ cozylights.on_gen_step = _time
+end
+
+local brush_hold_dtime = 0
+local wield_dtime = 0
+local on_gen_dtime = 0
+
+local total_brush_hold_time = 0
+local total_brush_hold_step_count = 0
+local total_wield_time = 0
+local total_wield_step_count = 0
+local uncozy_queue = {}
+
+local function on_brush_hold(player,cozyplayer,pos,t)
+ local control_bits = player:get_player_control_bits()
+ if control_bits < 128 or control_bits >= 256 then return end
+ local lb = cozyplayer.lbrush
+ if lb.radius > 10 then return end
+ local look_dir = player:get_look_dir()
+ local endpos = vector.add(pos, vector.multiply(look_dir, 100))
+ local hit = minetest.raycast(pos, endpos, false, false):next()
+ if not hit then return end
+ local nodenameunder = minetest.get_node(hit.under).name
+ local nodedefunder = minetest.registered_nodes[nodenameunder]
+ local above = hit.above
+ if nodedefunder.buildable_to == true then
+ above.y = above.y - 1
+ end
+ local above_hash = above.x + (above.y)*100 + above.z*10000
+ if above_hash ~= lb.pos_hash or lb.mode == 2 or lb.mode == 4 or lb.mode == 5 then
+ lb.pos_hash = above_hash
+ cozylights:draw_brush_light(above, lb)
+ local exe_time = os.clock() - t
+ total_brush_hold_time = total_brush_hold_time + mf(exe_time * 1000)
+ total_brush_hold_step_count = total_brush_hold_step_count + 1
+ print("Av cozy lights brush step time " .. mf(total_brush_hold_time/total_brush_hold_step_count) .. " ms. Sample of: "..total_brush_hold_step_count)
+ --if exe_time > brush_hold_step then
+ -- minetest.chat_send_all("brush hold step was adjusted to "..(exe_time*2).." secs to help crispy potato.")
+ -- brush_hold_step = exe_time*2
+ --end
+ end
+end
+
+minetest.register_globalstep(function(dtime)
+ if wield_light_enabled then
+ wield_dtime = wield_dtime + dtime
+ if wield_dtime > wield_step then
+ wield_dtime = 0
+ for _,cozyplayer in pairs(cozylights.cozyplayers) do
+ local t = os.clock()
+ local player = minetest.get_player_by_name(cozyplayer.name)
+ local pos = vector.round(player:getpos())
+ pos.y = pos.y + 1
+ local wield_name = player:get_wielded_item():get_name()
+ -- simple hash, collision will result in a rare minor barely noticeable glitch if a user teleports:
+ -- if in collision case right after teleport the player does not move, wielded light wont work until the player starts moving
+ local pos_hash = pos.x + (pos.y)*100 + pos.z*10000
+ if pos_hash == cozyplayer.pos_hash and cozyplayer.last_wield == wield_name then
+ goto next_player
+ end
+ if cozylights.cozy_items[wield_name] ~= nil then
+ local vel = vector.round(vector.multiply(player:get_velocity(),wield_step))
+ cozylights:draw_wielded_light(
+ pos,
+ cozyplayer.last_pos,
+ cozylights.cozy_items[wield_name],
+ vel,
+ cozyplayer
+ )
+ else
+ cozylights:wielded_light_cleanup(player,cozyplayer,cozyplayer.last_wield_radius or 0)
+ end
+ cozyplayer.pos_hash = pos_hash
+ cozyplayer.last_pos = pos
+ cozyplayer.last_wield = wield_name
+ local exe_time = (os.clock() - t)
+ total_wield_time = total_wield_time + mf(exe_time * 1000)
+ total_wield_step_count = total_wield_step_count + 1
+ print("Av wielded cozy light step time " .. mf(total_wield_time/total_wield_step_count) .. " ms. Sample of: "..total_wield_step_count)
+ if cozylights.crispy_potato and exe_time > wield_step then
+ cozylights:set_wielded_light_radius(cozylights.max_wield_light_radius - 1)
+ minetest.chat_send_all("wield light step was adjusted to "..(exe_time*2).." secs to help crispy potato.")
+ wield_step = exe_time*2
+ end
+ ::next_player::
+ end
+ end
+ end
+
+ brush_hold_dtime = brush_hold_dtime + dtime
+ if brush_hold_dtime > brush_hold_step then
+ brush_hold_dtime = 0
+ for _,cozyplayer in pairs(cozylights.cozyplayers) do
+ local t = os.clock()
+ local player = minetest.get_player_by_name(cozyplayer.name)
+ local pos = vector.round(player:getpos())
+ pos.y = pos.y + 1
+ local wield_name = player:get_wielded_item():get_name()
+ --todo: checking against a string is expensive, what do
+ if wield_name == "cozylights:light_brush" then
+ on_brush_hold(player,cozyplayer,pos,t)
+ end
+ end
+ end
+
+ on_gen_dtime = on_gen_dtime + dtime
+ if on_gen_dtime > on_gen_step then
+ on_gen_dtime = 0
+ if cozylights.uncozy_mode == 0 then
+ if #schem_queue > 0 then
+ local s = schem_queue[1]
+ place_schem_but_real(s.pos, s.schematic, s.rotation, s.replacements, s.force_placement, s.flags)
+ table.remove(schem_queue, 1)
+ end
+ if #cozylights.area_queue ~= 0 then
+ local ar = cozylights.area_queue[1]
+ table.remove(cozylights.area_queue, 1)
+ print("build_lights_after_generated: "..cozylights:dump(ar.minp))
+ build_lights_after_generated(ar.minp,ar.maxp,ar.sources)
+ else
+ cozylights:rebuild_light()
+ if #recently_updated > 0 then
+ recently_updated = {}
+ end
+ end
+ else
+ for _,cozyplayer in pairs(cozylights.cozyplayers) do
+ local player = minetest.get_player_by_name(cozyplayer.name)
+ local pos = vector.round(player:getpos())
+ pos.y = pos.y + 1
+ -- simple hash, collision will result in a rare minor barely noticeable glitch if a user teleports:
+ -- if in collision case right after teleport the player does not move, wielded light wont work until the player starts moving
+ local pos_hash = pos.x + (pos.y)*100 + pos.z*10000
+ if pos_hash == cozyplayer.pos_hash then
+ goto next_player
+ end
+ cozyplayer.pos_hash = pos_hash
+ cozyplayer.last_pos = pos
+ uncozy_queue[#uncozy_queue+1] = pos
+ ::next_player::
+ end
+ if #uncozy_queue > 0 then
+ local exe_time = cozylights:clear(uncozy_queue[1], cozylights.uncozy_mode)
+ table.remove(uncozy_queue, 1)
+ if cozylights.crispy_potato and exe_time > on_gen_step then
+ minetest.chat_send_all("on_generated step was adjusted to "..(exe_time*2).." secs to help crispy potato.")
+ on_gen_step = exe_time*2
+ end
+ end
+ end
+ end
+end)
+
+local gent_total = 0
+local gent_count = 0
+minetest.register_on_generated(function(minp, maxp)
+ local pos = vector.add(minp, vector.floor(vector.divide(vector.subtract(maxp,minp), 2)))
+ local light_sources = minetest.find_nodes_in_area(minp,maxp,cozylights.source_nodes)
+ if #light_sources == 0 then return end
+ if #light_sources > 1000 then
+ print("Error: too many light sources around "..cozylights:dump(pos).." Report this to Cozy Lights dev")
+ return
+ end
+ --local minp_exp,maxp_exp,_,data,_,a = cozylights:getVoxelManipData(pos, size)
+ --local t = os.clock()
+ local sources = {}
+ local a = VoxelArea:new{
+ MinEdge = minp,
+ MaxEdge = maxp
+ }
+ local minp_exp, maxp_exp = minp, maxp
+ for _, p in pairs(light_sources) do
+ local name = minetest.get_node(p).name--get_name_from_content_id(cid)
+ local cozy_item = cozylights.cozy_items[name]
+ local radius, _ = cozylights:calc_dims(cozy_item)
+ local min_rad = vector.subtract(p,radius)
+ local max_rad = vector.add(p,radius)
+ if a:containsp(min_rad) and a:containsp(max_rad) then
+ sources[#sources+1] = {
+ pos=p,
+ cozy_item=cozy_item
+ }
+ else
+ minp_exp = {
+ x = minp_exp.x > min_rad.x and min_rad.x or minp_exp.x,
+ y = minp_exp.y > min_rad.y and min_rad.y or minp_exp.y,
+ z = minp_exp.z > min_rad.z and min_rad.z or minp_exp.z,
+ }
+ maxp_exp = {
+ x = maxp_exp.x < max_rad.x and max_rad.x or maxp_exp.x,
+ y = maxp_exp.y < max_rad.y and max_rad.y or maxp_exp.y,
+ z = maxp_exp.z < max_rad.z and max_rad.z or maxp_exp.z,
+ }
+
+ a = VoxelArea:new{
+ MinEdge = minp_exp,
+ MaxEdge = maxp_exp
+ }
+ sources[#sources+1] = {
+ pos=p,
+ cozy_item=cozy_item
+ }
+ --print("adding "..name.." to single_light_queue")
+ --table.insert(cozylights.single_light_queue, {
+ -- pos=p,
+ -- cozy_item=cozy_item
+ --})
+ end
+ end
+ --gent_total = gent_total + mf((os.clock() - t) * 1000)
+ --gent_count = gent_count + 1
+ --print("Av mapchunk generation time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
+ if #sources > 0 then
+ print("on_generated adding area:"..cozylights:dump({minp=minp_exp,maxp=maxp_exp, volume=a:getVolume()}))
+ cozylights.area_queue[#cozylights.area_queue+1]={
+ minp=minp_exp,
+ maxp=maxp_exp,
+ sources=sources
+ }
+ end
+end)
+
diff --git a/mods/cozylights/light_brush.lua b/mods/cozylights/light_brush.lua
new file mode 100644
index 00000000..de1f2fc9
--- /dev/null
+++ b/mods/cozylights/light_brush.lua
@@ -0,0 +1,341 @@
+local mf = math.floor
+
+local function on_secondary_use(user)
+ local lb = cozylights.cozyplayers[user:get_player_name()].lbrush
+ local settings_formspec = {
+ "formspec_version[4]",
+ --"size[6,6.4]",
+ "size[5.2,5]",
+ "label[1.45,0.5;Light Brush Settings]",
+
+ "label[0.95,1.35;Radius]",
+ "field[3.6,1.1;0.7,0.5;radius;;"..lb.radius.."]",
+ "tooltip[0.95,1.1;3.4,0.5;If radius is 0 then only one node will be affected by the brush.\n"..
+ "If not zero then it's a sphere of affected nodes with specified radius.\n"..
+ "As of now max radius is only 120.\n"..
+ "With radiuses over 30 mouse hold as of now does not work, only point and click]",
+
+ "label[0.95,2.05;Brightness]",
+ "field[3.6,1.8;0.7,0.5;brightness;;"..lb.brightness.."]",
+ "tooltip[0.95,1.8;3.4,0.5;Brightness - for most brush modes values are from 1 to 14, corresponding to engine light levels.\n"..
+ "If brush mode is 'darken' or 'override' then 0 will replace lowest light levels with air.]",
+
+ "label[0.95,2.75;Strength]",
+ "field[3.6,2.5;0.7,0.5;strength;;"..lb.strength.."]",
+ "tooltip[0.95,2.5;3.4,0.5;Strength, can be from 0 to 1, decimal values of any precision are valid.\n"..
+ "Determines how bright(relative to brightness setting) light nodes in affected area will be.]",
+
+ "label[0.95,3.45;Brush Mode]",
+ "dropdown[2.8,3.2;1.5,0.5;mode;default,erase,override,lighten,darken,blend;"..lb.mode.."]",
+ "tooltip[0.95,3.2;3.4,0.5;\nDefault - replace only dimmer light nodes or air with brush.\n\n"..
+ "Erase - inverse of default, replaces only lighter nodes with darker nodes or air if brightness is 0.\n\n"..
+ "Override - set light nodes as brush settings dictate regardless of difference in brigthness.\n\n"..
+ "Lighten - milder than default mode.\n\n"..
+ "Darken - milder erase, does not darken below light 1(does not replace with air).\n\n"..
+ "Blend - blend affected nodes' brigthness with brush brigthness.\n"..
+ "Even though behaves correctly, as of now looks weird and unintuitive if radius is not 0.]",
+ --"checkbox[1.7,4.6;cover_only_surfaces;cover only surfaces;"..(lb.cover_only_surfaces == 1 and "true" or "false").."]",
+ --"tooltip[1.7,4.4;2.6,0.4;if enabled brush will not fill up the air with light above the ground;"..bgcolor..";#FFFFFF]",
+ --"button_exit[1,5.1;4,0.8;confirm;Confirm]",
+ "button_exit[1.1,4;3,0.8;confirm;Confirm]",
+ }
+ minetest.show_formspec(user:get_player_name(), "cozylights:brush_settings",table.concat(settings_formspec, ""))
+end
+
+minetest.register_tool("cozylights:light_brush", {
+ description = "Light Brush",
+ inventory_image = "light_brush.png",
+ wield_image = "light_brush.png^[transformR90",
+ tool_capabilities = {
+ full_punch_interval = 0.3,
+ max_drop_level = 1,
+ },
+ range = 100.0,
+ on_use = function(itemstack, user, pointed_thing)
+ if pointed_thing.under then
+ local nodenameunder = minetest.get_node(pointed_thing.under).name
+ local nodedefunder = minetest.registered_nodes[nodenameunder]
+ local lb = cozylights.cozyplayers[user:get_player_name()].lbrush
+ local above = pointed_thing.above
+ if nodenameunder ~= "air" and nodedefunder.buildable_to == true then
+ above.y = above.y - 1
+ end
+ local above_hash = above.x + (above.y)*100 + above.z*10000
+ lb.pos_hash = above_hash
+ cozylights:draw_brush_light(pointed_thing.above, lb)
+ end
+ end,
+ on_place = function(_, placer)
+ on_secondary_use(placer)
+ end,
+ on_secondary_use = function(_, user)
+ on_secondary_use(user)
+ end,
+ sound = {breaks = "default_tool_breaks"}
+})
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname ~= ("cozylights:brush_settings") then return end
+ if player == nil then return end
+ local lb = cozylights.cozyplayers[player:get_player_name()].lbrush
+ if fields.brightness then
+ local brightness = tonumber(fields.brightness) > 14 and 14 or tonumber(fields.brightness)
+ lb.brightness = brightness < 0 and 0 or mf(brightness or 0)
+ end
+ if fields.radius then
+ local radius = tonumber(fields.radius) > 200 and 200 or tonumber(fields.radius)
+ lb.radius = radius < 0 and 0 or mf(radius or 0)
+ end
+ if fields.strength then
+ local strength = tonumber(fields.strength) > 1 and 1 or tonumber(fields.strength)
+ lb.strength = strength < 0 and 0 or strength
+ end
+ if fields.mode then
+ local mode = fields.mode
+ local idx = 6
+ if mode == "default" then
+ idx = 1
+ elseif mode == "erase" then
+ idx = 2
+ elseif mode == "override" then
+ idx = 3
+ elseif mode == "lighten" then
+ idx = 4
+ elseif mode == "darken" then
+ idx = 5
+ end
+ lb.mode = idx
+ end
+ if fields.cover_only_surfaces then
+ lb.cover_only_surfaces = fields.cover_only_surfaces == "true" and 1 or 0
+ end
+end)
+
+local function calc_dims_for_brush(brightness, radius, strength, even)
+ local dim_levels = {}
+ --- this gradient attempts to get more colors, but that looks like a super weird monochrome rainbow and immersion braking
+ --strength = (strength+0.05)*2
+ --
+ --local current_brightness = brightness
+ --local step = math.sqrt(radius/brightness)
+ --local initial_step = step
+ --for i = 1, radius do
+ -- dim_levels[i] = current_brightness
+ -- if i>step then
+ -- step = step*strength + math.sqrt(i)
+ -- current_brightness = current_brightness - 1
+ -- end
+ --end
+ --- this gradient drops brightness fast but spreads dimmer lights over farther
+ if strength == 1 then
+ even = true
+ end
+ strength = strength*5
+ dim_levels[1] = brightness
+ if even ~= true then
+
+ for i = 2, radius do
+ local dim = math.sqrt(math.sqrt(i)) * (6-strength)
+ local light_i = mf(brightness - dim)
+ if light_i > 0 then
+ if light_i < 15 then
+ dim_levels[i] = light_i
+ else
+ dim_levels[i] = 14
+ end
+ else
+ dim_levels[i] = 1
+ end
+ end
+ else
+ for i = 2, radius do
+ dim_levels[i] = brightness
+ end
+ end
+
+ return dim_levels
+end
+
+local c_air = minetest.get_content_id("air")
+local c_light1 = minetest.get_content_id("cozylights:light1")
+local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
+ c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
+local gent_total = 0
+local gent_count = 0
+
+local function draw_one_node(pos,lb)
+ local node = minetest.get_node(pos)
+ local brightness = lb.brightness
+ local new_node_name = "cozylights:light"..brightness
+ if brightness == 0 then
+ new_node_name = "air"
+ end
+
+ if node.name == "air" and new_node_name ~= node.name then
+ minetest.set_node(
+ pos,
+ {
+ name=new_node_name,
+ param2=brightness
+ }
+ )
+ return
+ end
+ if string.find(node.name,"cozylights:") then
+ if lb.mode == 1 and brightness <= node.param2 then return end
+ if lb.mode == 2 and brightness >= node.param2 then return end
+ if lb.mode == 4 then
+ if brightness <= node.param2 then return end
+ brightness = mf((brightness+node.param2)/2+0.5)
+ if brightness < 1 then return end
+ new_node_name = "cozylights:light"..brightness
+ elseif lb.mode == 5 then
+ if brightness >= node.param2 then return end
+ brightness = mf((brightness+node.param2)/2)
+ new_node_name = "cozylights:light"..brightness
+ if brightness < 1 then
+ brightness = 0
+ new_node_name = "air"
+ end
+ elseif lb.mode == 6 then
+ brightness = mf((brightness+node.param2)/2+0.5)
+ new_node_name = "cozylights:light"..brightness
+ if brightness < 0 then
+ brightness = 0
+ new_node_name = "air"
+ end
+ end
+ minetest.set_node(
+ pos,
+ {
+ name=new_node_name,
+ param2=brightness
+ }
+ )
+ end
+end
+
+
+--this function pulls numbers out of its ass instead of seriously computing everything, so its faster
+--some nodes are being missed for big spheres
+function cozylights:draw_brush_light(pos, lb)
+ local t = os.clock()
+ local radius = lb.radius
+ if radius == 0 then
+ draw_one_node(pos,lb)
+ return
+ end
+ local mode = lb.mode
+ local brightness = lb.brightness
+ local dim_levels = calc_dims_for_brush(brightness,radius,lb.strength, mode==2 and true or false)
+ print("dim_levels:"..cozylights:dump(dim_levels))
+ local vm = minetest.get_voxel_manip()
+ local emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
+ local data = vm:get_data()
+ local param2data = vm:get_param2_data()
+ local a = VoxelArea:new{
+ MinEdge = emin,
+ MaxEdge = emax
+ }
+ local sphere_surface = cozylights:get_sphere_surface(radius)
+ local ylvl = 1
+ local cid = data[a:index(pos.x,pos.y-1,pos.z)]
+ local cida = data[a:index(pos.x,pos.y+1,pos.z)]
+ if cid and cida then
+ if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
+ and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
+ then
+ ylvl = -1
+ end
+ else
+ return
+ end
+ pos.y = pos.y + ylvl
+
+ if mode == 1 then
+ if cozylights.always_fix_edges == true then
+ local visited_pos = {}
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
+ end
+ else
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+ elseif mode == 2 then
+ if cozylights.always_fix_edges == true then
+ local visited_pos = {}
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_erase_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
+ end
+ else
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_erase(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+ elseif mode == 3 then
+ if cozylights.always_fix_edges == true then
+ local visited_pos = {}
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_override_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
+ end
+ else
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_override(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+ elseif mode == 4 then
+ if cozylights.always_fix_edges == true then
+ local visited_pos = {}
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_lighten_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
+ end
+ else
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_lighten(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+ elseif mode == 5 then
+ if cozylights.always_fix_edges == true then
+ local visited_pos = {}
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_darken_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
+ end
+ else
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_darken(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+ else
+ if cozylights.always_fix_edges == true then
+ local visited_pos = {}
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_blend_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
+ end
+ else
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_blend(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+ end
+ vm:set_data(data)
+ vm:set_param2_data(param2data)
+ vm:update_liquids()
+ vm:write_to_map()
+ gent_total = gent_total + mf((os.clock() - t) * 1000)
+ gent_count = gent_count + 1
+ print("Av draw time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
+end
diff --git a/mods/cozylights/mod.conf b/mods/cozylights/mod.conf
new file mode 100644
index 00000000..ffef9eaf
--- /dev/null
+++ b/mods/cozylights/mod.conf
@@ -0,0 +1,8 @@
+author = SingleDigitIQ
+name = cozylights
+description = voxel light maps
+title = Cozy Lights
+supported_games = *, minetest_game, mineclone2, mineclonia, voxelforge, regulus_mtgamejam_2023, mesecraft, exile, tutorial, asuna, hades_revisited,
+optional_depends = adv_lightsabers, airutils, anchorstone, aom_item_entity, aom_itemframe, regulus_mapgen, apartment, artdeco, a_trees, basenodes, basic_materials, beacon, boxes, building_blocks, cartographer_minetest_game, castle_lighting, caverealms, caverealms_but_actually_lite, caverealms_lite, chaos, christmas, christmas_decor, church_candles, cityscape, cloudlands, cloudscape, columnia, commoditymarket_fantasy, computers, craftguide, crafting, craft_ingredients, ctf_map, darkage, darkness_nerf, decor_api, decoz, default, defense_mob_api, df_dependencies, df_farming, df_mapitems, df_primordial_items, df_trees, df_underworld_items, digilines, diplazer, doc_items, dungeon_crates, dungeon_rooms, earthbuild, earthbuild, ecobots2, electronic, element, elixirs, environ, epic, equip_exam, equipment, ethereal, everness, fake_fire, fantasy_mobs, farming, filler, fireflies, firez, flowers, furniture, furnz, gadgets_magic, gal_ecology_plants, gal_ecology_trees, gal_geology, games, gems_tools, geomoria, geomoria, goodtorch, guns4d, hammer_of_power, homedecor_climate_control, homedecor_common, homedecor_electronics, homedecor_exterior, homedecor_lighting, homedecor_misc, homedecor_plasmascreen, homedecor_windows_and_treatments, hyperloop, ice_sprites, ilights, illumination, industrious, ip_elevator, ip_explosives, ip_main, ip_more_fire, ip_soviet, itemframes, item_holders, jelys_pizzaria, jumper, kilns, lab, lampz, laptop, lavalamp, letters, lib_forge, lib_handtools, lib_lighting, lightball, lightning, light_tool, livingcaves, logspikes, loud_walking, lvae, lzr_decor, lzr_laser, magic_gadgets_shield, magma_conduits, mapgen, ma_pops_furniture, maptools, marinara, material, mcl_amethyst, mcl_beacons, mcl_beds, mcl_blackstone, mcl_blast_furnace, mcl_campfires, mcl_chests, mcl_conduits, mcl_core, mcl_crimson, mcl_deepslate, mcl_dripping, mcl_end, mcl_falling_nodes, mcl_farming, mcl_fire, mcl_furnaces, mcl_item_entity, mcl_lanterns, mcl_lush_caves, mcl_mushrooms, mcl_nether, mcl_ocean, mcl_portals, mcl_sculk, mcl_smoker, mcl_torches, mcl_tt, mcl_villages, mcl_wieldview, mech, melding, mesecons, mesecons_button, mesecons_commandblock, mesecons_extrawires, mesecons_lamp, mesecons_lightstone, mesecons_powerplant, mesecons_torch, meseportals, mf_abyss, mf_decor, mg_villages, mine_gas, mobs_butterfly, mobs_mc, mobs_monster, mobs_npc, mobs_races, mobs_soldiers, mods, moontest_lights, moontest_power, moontest_terraformer, moontest_wielded_light, moreblocks, morelights_extras, morelights_modern, morelights_vintage, moretorches, mtg_default, mtg_fire, mtg_fireflies, mtg_stairs, mtg_tnt, mts_lights, mts_liquids, mts_powercrystals, mts_teleporters, mylandscaping, mylights, nautilus, nbea, nc_api, nc_api_active, nc_api_ents, nc_api_visinv, nc_fire, nc_igneous, nc_lantern, nc_lode, nc_lux, nc_optics, nc_player_hand, nc_scaling, nc_terrain, nc_torch, nether, new_campfire, new_fireworks, newhorizons, newplanet, nl_fog, nmobs, nodeglow, nodez, ocean, other_worlds, pala_decoblock, pala_furnace, pbj_pup, penumbra, pipeworks, plant, plasmascreen, pmb_fire, pmb_lights, pmb_stone, pmb_wood, protector, pxs_default, randungeon, rangedweapons, rp_builtin_item, rp_default, rp_fire, rp_lumien, rp_tnt, sailing, scaffolding, scifi_nodes, shark, shooter, signs_bot, smartline, smartshop, spacestation, spawners, squaresville, stairs, sticks_and_torches, technic, teleport_potion, tempsurvive, testnodes, throwing, tnt, tombs, torch_bomb, torchz, towercrane, travelnet, tt_base, tubelib, tubelib_addons1, tubelib_addons2, tutorial, underch, unified_inventory, unittests, vacuum, valleys_mapgen, variety, vk_nodes, vk_party, void_chest, voxelmodel, vu_base, wands, wielded_light, wiki, wm_blocks, wm_mobs, workbench, ws_core, xdecor, x_farming, xlocate, x_obsidianmese, xpanes, cellestial, cellestiall, glitch_nodes, vlf_falling_nodes, vlf_item_entity, vlf_dripping, vlf_villages, vlf_sculk, vlf_blackstone, vlf_nether, vlf_ocean, vlf_end, vlf_furnaces, vlf_chests, vlf_conduits, vlf_lanterns, vlf_farming, vlf_mushrooms, vlf_copper, vlf_campfires, vlf_fire, vlf_portals, vlf_lush_caves, vlf_candles, vlf_torches, vlf_core, vlf_crimson, vlf_beacons, vlf_beds, vlf_tt, vlf_wieldview, abriglass, princess, yellow_crystal, subspacewalker, digibuilder, digistuff, nixie_tubes, mesecons_wireless, lwcomponents, furnaces_api, buckets_api, carts_api, containers_api, doors_api, lights_api, fences_api, earth_api, liquids_api, base_liquids, pterygota_api, fire_api, sd_tools, xray, st_nodes, sudoku, decorations_sea, magic_materials, led_marquee, gadgets_api, fancy_vend, moognu, bweapons_api, bweapons_magic_pack, decoblocks, mesecraft_fireplace, mesecraft_mars, mesecraft_christmas, mesecraft_magic_mirror, mesecraft_holiday_lights, mesecraft_miners_helmet, mesecraft_television, mesecraft_torch, frost_land, nightshade, japaneseforest, dorwinion, badland, too_many_stones, physics, ld_game, lottthrowing, lottother, lottplants, lottblocks, lottores, lottpotion, lottfarming, brickbuild, ghost_crypt_main, mf_lamps, rp_nodes, super_sam_nodes, what_were_you_expecting_default, omg_moonrealm, lavastuff, digiscreen, jumpdrive, vehicles, rgblightstone, mesecon_node, regulus_story, regulus_nodes, kl_nodes, tutorial_castle, tutorial_default, tutorial_darkage, nv_flora, nv_planetgen, ch_world, ch_buildings, ch_ambient, ch_silver, labyrinthus, pkr_nodes, bnb_nodes, inferno, nodes_nature, artifacts, rings, grafitti, piredo_terrain, minetestrpg_blocks, citadel_core, vexcazer, vexcazer_extras, vexcazer_flashlight, toxic, examobs, weather, paintnings, xesmartshop, ikea, ikea_warehouse, klots_terrain_nc, klots_terrain_scifi, hades_furniture, hades_lamps, hades_tt, hades_torches, hades_glowcrystals, hades_core, hades_furnaces, hades_stairs, fl_light_sources, fl_liquids, fl_fire, electricity, loria, datatest, colorlandia_colorland, bs_molotov, bs_rangedweapons, bs_medic_stand, flags, abritorch, invector, alter_mirror, sotm_tools, sotm_nodes, mcl_copper, sf_mobs, sf_nodes, sf_portals, torrl_aliens, torrl_nodes, torrl_meteors, sm_mapnodes, ssp_base, gloopblocks, bobblocks, stained_glass, display_blocks_redo, blox, ufos, titanium, simple_streetlights, shooter_flaregun, spectrum, nether_mobs, uwu, technic_cnc, spawners_env, spawners_ores, spawners_mobs, livingslimes, mcl_small_3d_plants, mcl_decor, exchangeclone, uraniumstuff, cave_explorer, mcl_more_ore_variants, witches, hammermod, x_enchanting, crosssection, tunnelmaker, wc_luminous, elepower_lighting, tardis_new, visible_wielditem, digiterms, resource_crops, alchemy, goblins, equippable_accessories, pick_axe_tweaks, corroded_metal, flexrealm, fishing_boat, magicalities, fluidity, metal_melter, thelowerroad, cheese, streets, automobiles_lib, pochie_mod, laptop_extend_pack_2, stoneblocks, area_containers, 3dforniture, lucky_block, nssm, church_grave, church_glass, church_cross, mobs_balrog, amethyst_new, claycrafter, offhand, chakram, sunset_biomes, advtrains_signals_ks, mex_chariot, mex_moreblocks, industrial_furnace, abripanes, pk_nodes, mobkit_sapien, abriflame, wc_noditions, adventure_core, forfun, morelights_dim, useful_contraptions, lighting_rocket, warps, laptop_pc1, ultra_colors, boulder_dig, trucraft, cannon73, moremesecons_switchtorch, moremesecons_commandblock, moremesecons_induction_transmitter, yellowglassblock, nc_exmachina, hades_cottages, nc_ctf, wc_storage, alien_tools, gocm_carbon, nc_forcelode, cube_nodes, block_in_block, oreveins, qyintessential_dragonegglock, real_elevators, greek, mcl_wieldlight, k_colorblocks, machines, new_biomes, obdy, legendary_armor, nether_addons, ocular_networks, mesecons_morewires, ponti, chess, assets_warehouse, nc_reative, lwwires, biogasmachines, minertools, mid_measure, minercantile, morebricks, placeable_buckets, ambient_light, vm_lighting_wand, nutra_paste, lwroad_tracks, lua_inv, wc_furniture, lightdead, pilzmod, hero_mines, defripper, mystic_runes, mystic_rune_blocks, doorbell, hades_extrafarming, hades_castle_lighting, hades_moreblocks, nc_luxgate, bens_gear_bonus_materials, quikbild, hades_travelnet, cave_lighting, mystical_agriculture, markers, anarglass, sprint_lite, nssm_extra, lumpblocks, invisiblocks, lwcolorable, wc_road, phonics_lib, protect_block_area, factory_mine, smoke_signals, fragments, advtrains_crafting_compatibility_patch, cropocalypse, basic_machines, nssb, candles_3d, xnether, falling_item, goops_exchange, lavagold, mesecons_luacontroller_block, usl, superblock, aether_new, colorcubes, 3dmaze, hedges, topaz_items, mcl_light_blocks, watershed, blackhole_containment, blobcats, smartrenting, nyftyblocks, alien_material, lwcomputers, mathplot, real_torch, ham_radio, bank_accounts, breadcrumbs, wc_bugs, k_ambient_light, luablock, wc_sealife, embedded_lights, locked_travelnet, compass, wc_quicksilver, more_mese_post_light, lava_ore_gen, sane_builderwand, sane_hammer, sane_scraper, city_block, hero_tnt, crucifix, stellar, oillamps, titanic_mod, wc_plumbum, greenscreen, arrowboards, chest2, better_roads, nc_adamant, presents, realm_lamp, grimoire, potions_and_magic, goops_rings, falls, drwho_tardis, digilines_lightstone, challenge, pink_lava, nyanland, nneett_nc_heaters, trainblocks, remove_unknowns, titanic_mod2, colored_steel, cavetools, fake_liquids, small_why_things, useful_green_potatoes, christmastree, transporter, poly_decor, barrier, moonflowers, pvp_control, craftable_lava, medieval, invisible_blocks, minertrade, borders, atium, cloud_items, wc_naturae, colored_meselamps, balrog_sword, underbed_toilet_math_mg, windmill, headlamp, elektron, item_repair, balloonblocks, more_stones, automata, item_replicator, openion_glostone_building_blocks, libox_controller, random_stuff, fly_b_gone, wc_meltdown, nodeu, vbots, kudzu, wc_crystals, nc_nature, x_bows_extras, britsignals, cucina_vegana, etherium_stuff, toomanymetals, brazier, fsg, bas_ctf, luxury_decor, legendary_ore, nc_light, wc_adamant, symmetool, nc_pebbles, bs_cta, time_travel
+
+release = 26277
diff --git a/mods/cozylights/node_light.lua b/mods/cozylights/node_light.lua
new file mode 100644
index 00000000..5119feb0
--- /dev/null
+++ b/mods/cozylights/node_light.lua
@@ -0,0 +1,192 @@
+local c_air = minetest.get_content_id("air")
+local c_light1 = minetest.get_content_id("cozylights:light1")
+
+local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
+ c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
+
+local gent_total = 0
+local gent_count = 0
+
+local remt_total = 0
+local remt_count = 0
+local mf = math.floor
+
+local dirfloor = 0.5
+--- raycast but normal
+local function darknesscast(pos, dir, radius,data,param2data, a)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local c_light1 = c_lights[1]
+ local c_light14 = c_lights[14]
+ for i = 1, radius do
+ local x = mf(dx*i+dirfloor) + px
+ local y = mf(dy*i+dirfloor) + py
+ local z = mf(dz*i+dirfloor) + pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cid and (cid == c_air or (cid >= c_light1 and cid <= c_light14+14)) then
+ data[idx] = c_air
+ param2data[idx] = 0
+ else
+ break
+ end
+ end
+end
+
+function cozylights:draw_node_light(pos,cozy_item,vm,a,data,param2data,fix_edges)
+ local t = os.clock()
+ local update_needed = 0
+ local radius, dim_levels = cozylights:calc_dims(cozy_item)
+ --print("cozy_item:"..cozylights:dump(cozy_item))
+ --print("dim_levels: "..cozylights:dump(dim_levels))
+ --print("spreading light over a sphere with radius of "..radius)
+ if vm == nil then
+ _,_,vm,data,param2data,a = cozylights:getVoxelManipData(pos,radius)
+ update_needed = 1
+ end
+ local sphere_surface = cozylights:get_sphere_surface(radius)
+ local ylvl = 1
+ local cid = data[a:index(pos.x,pos.y-1,pos.z)]
+ local cida = data[a:index(pos.x,pos.y+1,pos.z)]
+ if cid and cida then
+ if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
+ and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
+ then
+ ylvl = -1
+ end
+ else
+ return
+ end
+ pos.y = pos.y + ylvl
+ fix_edges = fix_edges == nil and cozylights.always_fix_edges or fix_edges
+ if fix_edges == true then
+ local visited_pos = {}
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights:lightcast_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
+ end
+ else
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ cozylights.dir = vector.direction(pos, end_pos)
+ cozylights:lightcast(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+ if update_needed == 1 then
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ end
+ gent_total = gent_total + mf((os.clock() - t) * 1000)
+ gent_count = gent_count + 1
+ print("Av illum time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
+end
+
+-- handle_async?
+function cozylights:rebuild_light()
+ local single_light_queue = cozylights.single_light_queue
+ if #single_light_queue == 0 then
+ return
+ end
+ print("#single_light_queue is: "..#single_light_queue)
+ cozylights:draw_node_light(single_light_queue[1].pos, single_light_queue[1].cozy_item)
+ table.remove(single_light_queue, 1)
+end
+
+function cozylights:destroy_light(pos, cozy_item)
+ local t = os.clock()
+ local radius = cozylights:calc_dims(cozy_item)
+ local _,_,vm,data,param2data,a = cozylights:getVoxelManipData(pos, radius)
+ local sphere_surface = cozylights:get_sphere_surface(radius)
+ local ylvl = 1
+ local cid = data[a:index(pos.x,pos.y-1,pos.z)]
+ local cida = data[a:index(pos.x,pos.y+1,pos.z)]
+ if cid and cida then
+ if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
+ and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
+ then
+ ylvl = -1
+ end
+ else
+ return
+ end
+ pos.y = pos.y + ylvl
+ for i,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ darknesscast(pos, vector.direction(pos, end_pos),radius,data,param2data, a)
+ end
+
+ cozylights:setVoxelManipData(vm,data,param2data)
+
+ local rebuild_range = 78
+ local rebuild_minp = vector.subtract(pos, rebuild_range)
+ local rebuild_maxp = vector.add(pos, rebuild_range)
+ local posrebuilds = minetest.find_nodes_in_area(
+ rebuild_minp,
+ rebuild_maxp,
+ cozylights.source_nodes
+ )
+ local pos_hash = pos.x + (pos.y-ylvl)*100 + pos.z*10000
+ local sources = {}
+ if #posrebuilds > 0 then
+ local single_light_queue = cozylights.single_light_queue
+ for i=1,#posrebuilds do
+ local posrebuild = posrebuilds[i]
+ local posrebuild_hash = posrebuild.x + posrebuild.y*100 + posrebuild.z*10000
+ if posrebuild_hash ~= pos_hash then
+ local node = minetest.get_node(posrebuild)
+ local rebuild_radius, _ = cozylights:calc_dims(cozylights.cozy_items[node.name])
+ local max_distance = rebuild_radius + radius
+ if max_distance > vector.distance(pos,posrebuild) then
+ if vector.in_area(vector.subtract(posrebuild,rebuild_radius), rebuild_minp, rebuild_maxp)
+ and vector.in_area(vector.add(posrebuild,rebuild_radius), rebuild_minp, rebuild_maxp)
+ then
+ sources[#sources+1] = {
+ pos=posrebuild,
+ cozy_item=cozylights.cozy_items[node.name]
+ }
+ else
+ cozylights.single_light_queue[#single_light_queue+1] = {
+ pos=posrebuilds[i],
+ cozy_item=cozylights.cozy_items[node.name]
+ }
+ end
+ end
+ end
+ end
+ end
+ if #sources > 0 then
+ cozylights.area_queue[#cozylights.area_queue+1]={
+ minp=rebuild_minp,
+ maxp=rebuild_maxp,
+ sources=sources
+ }
+ end
+
+ remt_total = remt_total + mf((os.clock() - t) * 1000)
+ remt_count = remt_count + 1
+ print("Av light removal time " .. mf(remt_total/remt_count) .. " ms. Sample of: "..remt_count)
+end
+
+--[[
+function cozylights:rebuild_light(pos, cozy_item,vm,a,data,param2data)
+ local radius, dim_levels = cozylights:calc_dims(cozy_item)
+ print("rebuilding light for position "..cozylights:dump(pos))
+ local sphere_surface = cozylights:get_sphere_surface(radius)
+ local ylvl = 1
+ local cid = data[a:index(pos.x,pos.y-1,pos.z)]
+ local cida = data[a:index(pos.x,pos.y+1,pos.z)]
+ if cid and cida then
+ if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
+ and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
+ then
+ ylvl = -1
+ end
+ else
+ return
+ end
+ pos.y = pos.y + ylvl
+ for _,pos2 in ipairs(sphere_surface) do
+ local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
+ if a:containsp(end_pos) then
+ cozylights:lightcast(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
+ end
+ end
+end]]
\ No newline at end of file
diff --git a/mods/cozylights/nodes.lua b/mods/cozylights/nodes.lua
new file mode 100644
index 00000000..54bba5a3
--- /dev/null
+++ b/mods/cozylights/nodes.lua
@@ -0,0 +1,38 @@
+-- All possible light levels
+for i=1, minetest.LIGHT_MAX do
+ minetest.register_node("cozylights:light"..i, {
+ description = "Light Source "..i,
+ paramtype = "light",
+ light_source = i,
+ --tiles ={"invisible.png"},
+ drawtype = "airlike",
+ walkable = false,
+ sunlight_propagates = true,
+ is_ground_content = false,
+ buildable_to = true,
+ pointable = false,
+ groups = {dig_immediate=3,not_in_creative_inventory=1},
+ floodable = true,
+ use_texture_alpha="clip",
+ })
+
+end
+
+-- two separate loops to keep content ids in order
+for i=1, minetest.LIGHT_MAX do
+ minetest.register_node("cozylights:light_debug"..i, {
+ description = "Light Source "..i,
+ paramtype = "light",
+ light_source = i,
+ tiles ={"default_glass.png"},
+ drawtype = "glasslike",
+ walkable = false,
+ sunlight_propagates = true,
+ is_ground_content = false,
+ buildable_to = false,
+ pointable = true,
+ groups = {dig_immediate=3,not_in_creative_inventory=1},
+ floodable = true,
+ use_texture_alpha="clip",
+ })
+end
\ No newline at end of file
diff --git a/mods/cozylights/settingtypes.txt b/mods/cozylights/settingtypes.txt
new file mode 100644
index 00000000..d6554e53
--- /dev/null
+++ b/mods/cozylights/settingtypes.txt
@@ -0,0 +1,31 @@
+[**Global Node Sources]
+
+# if none given, this default value will be used
+cozylights_default_size (Default chat command radius) int 40 5 120
+
+# max brightness of surrounding light. does not affect a light_source node base value. attention: if light is too bright, the scene can lose nuance
+cozylights_brightness_factor (Global ambient light source brightness modifier) float 3.0 -10.0 10.0
+
+# affects max radius of the light but only when its bright enough, if its very dim the setting will do nothing
+cozylights_reach_factor (Global ambient light source reach factor) float 4.0 0.0 10.0
+
+# how fast light dims further away from the source, higher means farther dim lights will persist for longer
+cozylights_dim_factor (Global ambient light source dim factor) float 9.0 0.0 10.0
+
+# -1 means wielded light is disabled
+# 0 means only one node is affected, so it basically acts like typical wielded light in Minetest
+# if it's more than 0 then it's a sphere in which light will spread
+cozylights_wielded_light_radius (Cozy wielded light radius) int 19 -1 30
+
+# sets all light sources to 1 so that the engine will not render anticlimactic squares for torches
+# and such. if a player removes cozylights from a world while this is set to true, fixmap mod for existing lights will be required, therefore default is set
+# to false, so you will need to enable it yourself after you decide that you like cozylights more.
+cozylights_override_engine_lights (Override engine light sources) bool false
+
+# makes all edges stop lights properly, cozylights algo is much faster without it enabled, so if for example
+# you need to first place a lot of lights all over the place, it would be easier to first place those lights and then run
+# /fixedges manually
+cozylights_always_fix_edges (Override engine light sources) bool false
+
+# if higher, then it will update slower and stress potato CPU less
+cozylights_step_time (Cozy Lights Global Step time) float 0.1 0.01 1.0
\ No newline at end of file
diff --git a/mods/cozylights/shared.lua b/mods/cozylights/shared.lua
new file mode 100644
index 00000000..604301e2
--- /dev/null
+++ b/mods/cozylights/shared.lua
@@ -0,0 +1,649 @@
+local sphere_surfaces = {[19]=nil}
+local c_light1 = minetest.get_content_id("cozylights:light1")
+local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
+c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
+local c_light14 = c_lights[14]
+local c_light_debug1 = c_light14 + 1
+local c_light_debug14 = c_light_debug1 + 13
+local c_air = minetest.get_content_id("air")
+local mf = math.floor
+
+function cozylights:clear(pos,size)
+ local t = os.clock()
+ local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
+ local count = 0
+ for i in a:iterp(minp, maxp) do
+ local cid = data[i]
+ if cid >= c_light1 and cid <= c_light_debug14 then
+ data[i] = c_air
+ param2data[i] = 0
+ count = count + 1
+ end
+ end
+ minetest.chat_send_all("cleared "..count.." cozy light nodes in area around pos: "..cozylights:dump(pos).." of radius: "..size)
+ if count> 0 then
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ end
+ return (os.clock() - t)
+end
+
+function cozylights:getVoxelManipData(pos, size)
+ local minp = vector.subtract(pos, size)
+ local maxp = vector.add(pos, size)
+ local vm = minetest.get_voxel_manip()
+ local emin, emax = vm:read_from_map(vector.subtract(minp, 1), vector.add(maxp, 1))
+ local data = vm:get_data()
+ local param2data = vm:get_param2_data()
+ local a = VoxelArea:new{
+ MinEdge = emin,
+ MaxEdge = emax
+ }
+ return minp,maxp,vm,data,param2data,a
+end
+
+function cozylights:setVoxelManipData(vm,data,param2data,update_liquids)
+ vm:set_data(data)
+ if param2data ~= nil then
+ vm:set_param2_data(param2data)
+ end
+ if update_liquids == true then
+ vm:update_liquids()
+ end
+ vm:write_to_map()
+end
+
+--todo: 6 directions of static slices or dynamic slices if its faster somehow(it wasnt so far)
+function cozylights:slice_cake(surface,radius)
+ local sliced = {}
+ for k,v in pairs(surface) do
+ -- full sphere except for a cone from center to max -y of 45 degrees or like pi/2 radians or something
+ if v.y > -radius*0.7071 then
+ table.insert(sliced,v)
+ end
+ end
+ return sliced
+end
+
+-- radius*radius = x*x + y*y + z*z
+function cozylights:get_sphere_surface(radius,sliced)
+ if sphere_surfaces[radius] == nil then
+ local sphere_surface = {}
+ local rad_pow2_min, rad_pow2_max = radius * (radius - 1), radius * (radius + 1)
+ for z = -radius, radius do
+ for y = -radius, radius do
+ for x = -radius, radius do
+ local pow2 = x * x + y * y + z * z
+ if pow2 >= rad_pow2_min and pow2 <= rad_pow2_max then
+ -- todo: could arrange these in a more preferable for optimization order
+ sphere_surface[#sphere_surface+1] = {x=x,y=y,z=z}
+ end
+ end
+ end
+ end
+ local t = {
+ full = sphere_surface
+ }
+ if radius < 30 then
+ t.minusyslice = cozylights:slice_cake(sphere_surface,radius) --typical wielded light
+ sphere_surfaces[radius] = t
+ if sliced == true then
+ return t.minusyslice
+ end
+ end
+ return sphere_surface
+ else
+ if sliced == true and sphere_surfaces[radius].minusyslice ~= nil then
+ return sphere_surfaces[radius].minusyslice
+ end
+ return sphere_surfaces[radius].full
+ end
+end
+
+function cozylights:calc_dims(cozy_item)
+
+ local brightness_mod = 0
+ local reach_mod = 0
+ local dim_mod = 0
+ if cozy_item.modifiers ~= nil then
+ brightness_mod = cozylights.coziest_table[cozy_item.modifiers].brightness
+ reach_mod = cozylights.coziest_table[cozy_item.modifiers].reach_factor
+ dim_mod = cozylights.coziest_table[cozy_item.modifiers].dim_factor
+ end
+ local max_light = mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod)
+ local r = mf(max_light*max_light/10*(cozylights.reach_factor+reach_mod))
+ --print("initial r: "..r)
+ local r_max = 0
+ local dim_levels = {}
+ local dim_factor = cozylights.dim_factor + dim_mod
+ for i = r , 1, -1 do
+ local dim = math.sqrt(math.sqrt(i)) * dim_factor
+ local light_i = max_light + 1 - mf(dim)
+ if light_i < 1 then
+ --light_i = 1
+ r_max = i
+ else
+ if light_i > 14 then
+ light_i = 14
+ end
+ dim_levels[i] = light_i
+ end
+
+ end
+ -- we cut the r only if max_r found is lower than r, so that we keep the ability to have huge radiuses
+ if r_max > 0 and r_max < r then
+ return r_max-1,dim_levels
+ end
+ return r,dim_levels
+end
+
+local cozycids_sunlight_propagates = {}
+-- ensure cozy position in memory
+-- default amount of lights sources: 194
+-- in default game with moreblocks mod: 5134
+--cozylights:prealloc(cozycids_sunlight_propagates, 194, true)
+--cozycids_sunlight_propagates = {}
+minetest.after(1, function()
+ cozycids_sunlight_propagates = cozylights.cozycids_sunlight_propagates
+ cozylights:finalize(cozycids_sunlight_propagates)
+ print(#cozycids_sunlight_propagates)
+ cozylights.cozycids_sunlight_propagates = {}
+ local version_welcome = minetest.settings:get("version_welcome")
+ if version_welcome ~= cozylights.version then
+ minetest.settings:set("version_welcome",cozylights.version)
+ minetest.chat_send_all(">.< Running Cozy Lights "..cozylights.version.." alpha. Some features are still missing or might not work properly and might be fixed tomorrow or next week."..
+ "\n>.< To learn more about what it can do check ContentDB page: https://content.minetest.net/packages/SingleDigitIQ/cozylights/"..
+ "\n>.< If you experience problems, appreciate if you report them on ContentDB, Minetest forum, Github or Discord."..
+ "\n>.< If you need more of original ideas and blazingly fast code in open source - leave a positive review on ContentDB or/and add to favorites."..
+ "\n>.< To open mod settings type in chat /cozysettings or /zs, hopefully tooltips are useful."..
+ "\n>.< This message displays only once per new downloaded update for Cozy Lights mod."..
+ "\n>.< Have fun :>"
+ )
+ end
+end)
+
+-- adjusting dirfloor might help with some nodes missing. probably the only acceptable way to to eliminate node
+-- misses and not sacrifice performance too much or at all
+local dirfloor = 0.5
+-- raycast but normal
+-- todo: if radius higher than i think 15, we need to somehow grab more nodes, without it it's not entirely accurate
+-- i hope a cheaply computed offset based on dir will do
+-- not to forget: what i mean by that is that + 0.5 in mf has to become a variable
+
+-- while we have the opportunity to cut the amount of same node reruns in this loop,
+-- we avoid that because luajit optimization breaks with one more branch and hashtable look up
+-- at least on my machine, and so it becomes slower to run and at the same time grabs more memory
+-- todo: actually check for the forth time the above is real
+function cozylights:lightcast(pos, dir, radius,data,param2data,a,dim_levels)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ for i = 1, radius do
+ local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ local dim = (dim_levels[i] - light_nerf) >= 1 and (dim_levels[i] - light_nerf) or 1
+ local light = c_lights[dim]
+ if light > cid or param2data[idx] == 0 then
+ data[idx] = light
+ param2data[idx] = dim
+ end
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ end
+end
+
+function cozylights:lightcast_erase(pos, dir, radius,data,param2data,a,dim_levels)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ for i = 1, radius do
+ local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ if cid >= c_light1 and cid <= c_light14 then
+ local dim = (dim_levels[i] - light_nerf) >= 0 and (dim_levels[i] - light_nerf) or 0
+ local light = dim > 0 and c_lights[dim] or c_air
+ if light < cid then
+ data[idx] = light
+ param2data[idx] = dim
+ end
+ elseif cid ~= c_air then
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ end
+end
+
+function cozylights:lightcast_override(pos, dir, radius,data,param2data,a,dim_levels)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ for i = 1, radius do
+ local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ end
+end
+
+function cozylights:lightcast_lighten(pos, dir, radius,data,param2data,a,dim_levels)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ for i = 1, radius do
+ local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ if c_lights[dim] > cid then
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2+0.5)
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ end
+end
+
+function cozylights:lightcast_darken(pos, dir, radius,data,param2data,a,dim_levels)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ for i = 1, radius do
+ local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ if cid >= c_light1 and cid <= c_light14 then
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ if c_lights[dim] < cid then
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2)
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ elseif cid ~= c_air then
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ end
+end
+
+function cozylights:lightcast_blend(pos, dir, radius,data,param2data,a,dim_levels)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ for i = 1, radius do
+ local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ local original_light = cid - c_light1 --param2data[idx]
+ dim = mf((dim + original_light)/2+0.5)
+ if dim < 1 then break end
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ end
+end
+
+-- removes some lights that light up the opposite side of an obstacle
+-- it is weird and inaccurate as of now, i can make it accurate the expensive way,
+-- still looking for a cheap way
+function cozylights:lightcast_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
+ local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ local halfrad, braking_brak = radius/2, false
+ local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
+ for i = 1, radius,2 do
+ local x,y,z = next_x, next_y, next_z
+ local idx = a:index(x,y,z)
+ for n = 1, 6 do
+ if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
+ braking_brak = true
+ break
+ end
+ end
+ if braking_brak == true then break end
+ x,y,z = nil,nil,nil
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ -- appears that hash lookup in a loop is as bad as math
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ if i < halfrad then
+ if not visited_pos[idx] then
+ visited_pos[idx] = true
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ local light = c_lights[dim]
+ if light > cid or param2data[idx] == 0 then
+ data[idx] = light
+ param2data[idx] = dim
+ end
+ end
+ else
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ local light = c_lights[dim]
+ if light > cid or param2data[idx] == 0 then
+ data[idx] = light
+ param2data[idx] = dim
+ end
+ end
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
+
+ --local next_idx = a:index(next_x,next_y,next_z)
+ --for n = 1, 6 do
+ -- if cozycids_sunlight_propagates[data[next_idx+dirs[n]]] == nil then
+ -- braking_brak = true
+ -- break
+ -- end
+ --end
+ --next_x,next_y,next_z = mf(dx*(i+2)+dirfloor)+px, mf(dy*(i+2)+dirfloor)+py, mf(dz*(i+2)+dirfloor)+pz
+
+ --local next_adj_indxs = {
+ -- a:index(next_x,y,z),
+ -- a:index(x,y,next_z),
+ -- a:index(x,next_y,z),
+ -- a:index(next_x,next_y,z),
+ -- a:index(x,next_y,next_z),
+ --}
+
+ --for _, j in pairs(next_adj_indxs) do
+ -- if cozycids_sunlight_propagates[data[j]] ~= true then
+ -- braking_brak = true
+ -- break
+ -- end
+ --end
+
+ end
+end
+
+function cozylights:lightcast_erase_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
+ local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ local halfrad, braking_brak = radius/2, false
+ local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
+ for i = 1, radius do
+ local x,y,z = next_x, next_y, next_z
+ local idx = a:index(x,y,z)
+ for n = 1, 6 do
+ if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
+ braking_brak = true
+ break
+ end
+ end
+ if braking_brak == true then break end
+ x,y,z = nil,nil,nil
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ -- appears that hash lookup in a loop is as bad as math
+ if cid >= c_light1 and cid <= c_light14 then
+ if i < halfrad then
+ if not visited_pos[idx] then
+ visited_pos[idx] = true
+ local dim = (dim_levels[i] - light_nerf) >= 0 and (dim_levels[i] - light_nerf) or 0
+ local light = dim > 0 and c_lights[dim] or c_air
+ if light < cid then
+ data[idx] = light
+ param2data[idx] = dim
+ end
+ end
+ else
+ local dim = (dim_levels[i] - light_nerf) >= 0 and (dim_levels[i] - light_nerf) or 0
+ local light = dim > 0 and c_lights[dim] or c_air
+ if light < cid then
+ data[idx] = light
+ param2data[idx] = dim
+ end
+ end
+ elseif cid ~= c_air then
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
+ end
+end
+
+function cozylights:lightcast_override_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
+ local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ local halfrad, braking_brak = radius/2, false
+ local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
+ for i = 1, radius do
+ local x = next_x
+ local y = next_y
+ local z = next_z
+ local idx = a:index(x,y,z)
+ for n = 1, 6 do
+ if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
+ braking_brak = true
+ break
+ end
+ end
+ if braking_brak == true then break end
+ x,y,z = nil,nil,nil -- they are probably still allocated though
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ -- appears that hash lookup in a loop is as bad as math
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ if i < halfrad then
+ if not visited_pos[idx] then
+ visited_pos[idx] = true
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ else
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
+ end
+end
+
+
+function cozylights:lightcast_lighten_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
+ local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ local halfrad, braking_brak = radius/2, false
+ local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
+ for i = 1, radius do
+ local x = next_x
+ local y = next_y
+ local z = next_z
+ local idx = a:index(x,y,z)
+ for n = 1, 6 do
+ if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
+ braking_brak = true
+ break
+ end
+ end
+ if braking_brak == true then break end
+ x,y,z = nil,nil,nil -- they are probably still allocated though
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ -- appears that hash lookup in a loop is as bad as math
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ if i < halfrad then
+ if not visited_pos[idx] then
+ visited_pos[idx] = true
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ if c_lights[dim] > cid then
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2+0.5)
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ end
+ else
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ if c_lights[dim] > cid then
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2+0.5)
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ end
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
+ end
+end
+
+
+function cozylights:lightcast_darken_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
+ local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ local halfrad, braking_brak = radius/2, false
+ local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
+ for i = 1, radius do
+ local x = next_x
+ local y = next_y
+ local z = next_z
+ local idx = a:index(x,y,z)
+ for n = 1, 6 do
+ if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
+ braking_brak = true
+ break
+ end
+ end
+ if braking_brak == true then break end
+ x,y,z = nil,nil,nil -- they are probably still allocated though
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ -- appears that hash lookup in a loop is as bad as math
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ if i < halfrad then
+ if not visited_pos[idx] then
+ visited_pos[idx] = true
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ if c_lights[dim] < cid then
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2)
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ end
+ else
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ if c_lights[dim] < cid then
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2)
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ end
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
+ end
+end
+
+
+function cozylights:lightcast_blend_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
+ local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local light_nerf = 0
+ local halfrad, braking_brak = radius/2, false
+ local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
+ for i = 1, radius do
+ local x = next_x
+ local y = next_y
+ local z = next_z
+ local idx = a:index(x,y,z)
+ for n = 1, 6 do
+ if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
+ braking_brak = true
+ break
+ end
+ end
+ if braking_brak == true then break end
+ x,y,z = nil,nil,nil -- they are probably still allocated though
+ local cid = data[idx]
+ if cozycids_sunlight_propagates[cid] == true then
+ -- appears that hash lookup in a loop is as bad as math
+ if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
+ if i < halfrad then
+ if not visited_pos[idx] then
+ visited_pos[idx] = true
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2+0.5)
+ if dim < 1 then break end
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ else
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ local original_light = cid - c_light1
+ dim = mf((dim + original_light)/2+0.5)
+ if dim < 1 then break end
+ data[idx] = c_lights[dim]
+ param2data[idx] = dim
+ end
+ else
+ light_nerf = light_nerf + 1
+ end
+ else
+ break
+ end
+ next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
+
+ end
+end
\ No newline at end of file
diff --git a/mods/cozylights/test/accuracy.lua b/mods/cozylights/test/accuracy.lua
new file mode 100644
index 00000000..06a4523e
--- /dev/null
+++ b/mods/cozylights/test/accuracy.lua
@@ -0,0 +1,95 @@
+dofile("../helpers.lua")
+dofile("../../../builtin/common/vector.lua")
+
+-- this exists to basically find sweet spot for dirfloor in fastest way i could come up with to spread light.
+-- dirfloor should change somehow cheaply according to radius maybe or
+-- according to ray angles, or, dirfloor should be split in x,y,z axis equivalents
+-- and those should be adjusted.
+-- some node misses start to appear from radius 6
+local dirfloor = 0.51
+
+local mf = math.floor
+
+-- radius*radius = x*x + y*y + z*z
+local function get_full_sphere(radius)
+ local sphere = {}
+ local count, offset, rad_pow2, stride_y = 0, 1+radius, radius * (radius + 1), radius+2
+ local stride_z = stride_y * stride_y
+ for z = -radius, radius do
+ for y = -radius, radius do
+ for x = -radius, radius do
+ local pow2 = x * x + y * y + z * z
+ if pow2 <= rad_pow2 then
+ local i = (z + offset) * stride_z + (y + offset) * stride_y + x + offset + 1
+ if sphere[i] ~= true then
+ sphere[i] = true
+ count = count + 1
+ end
+ end
+ end
+ end
+ end
+ return sphere, count
+end
+
+local function get_sphere_surface(radius)
+ local sphere_surface = {}
+ local rad_pow2_min, rad_pow2_max = radius * (radius - 1), radius * (radius + 1)
+ for z = -radius, radius do
+ for y = -radius, radius do
+ for x = -radius, radius do
+ local squared = x * x + y * y + z * z
+ if squared >= rad_pow2_min and squared <= rad_pow2_max then
+ sphere_surface[#sphere_surface+1] = {x=x,y=y,z=z}
+ end
+ end
+ end
+ end
+ return sphere_surface
+end
+
+local function raycast(dir, radius)
+ local ray = {}
+ local stride_z, stride_y = (radius+2)*radius+2, radius+2
+ local dx, dy, dz = dir.x, dir.y, dir.z
+ for i = 1, radius do
+ local x = mf(dx*i+dirfloor)
+ local y = mf(dy*i+dirfloor)
+ local z = mf(dz*i+dirfloor)
+ local idx = (z+1+radius)*stride_z+1+(y+1+radius)*stride_y+(x+1+radius)
+ if not ray[idx] then
+ ray[idx] = true
+ end
+ end
+ return ray
+end
+
+local function reconstruct_sphere(radius)
+ local pos = {x=0,y=0,z=0}
+ local sphere, sphere_len = get_full_sphere(radius)
+ local sphere_surface = get_sphere_surface(radius)
+ local reconstructed_sphere = {}
+ local reconstructed_sphere_len = 0
+ for _,pos2 in ipairs(sphere_surface) do
+ local ray = raycast(vector.direction(pos, pos2),radius)
+ for i,_ in pairs(ray) do
+ if not reconstructed_sphere[i] then
+ reconstructed_sphere[i] = true
+ reconstructed_sphere_len = reconstructed_sphere_len + 1
+ end
+ end
+ end
+
+ print("#sphere: "..sphere_len)
+ print("#reconstructed_sphere: "..reconstructed_sphere_len)
+ --print(cozylights:dump(sphere))
+ --print(cozylights:dump(reconstructed_sphere))
+end
+
+for i=1,1 do
+ dirfloor = dirfloor - 0.01
+ print("running with dirfloor: "..dirfloor)
+ reconstruct_sphere(1)
+end
+
+
diff --git a/mods/cozylights/textures/attribution b/mods/cozylights/textures/attribution
new file mode 100644
index 00000000..98eeba04
--- /dev/null
+++ b/mods/cozylights/textures/attribution
@@ -0,0 +1,3 @@
+default_glass.png is by Krock (CC0 1.0)
+
+my debug textures are WTFPL if anything
\ No newline at end of file
diff --git a/mods/cozylights/textures/default_glass.png b/mods/cozylights/textures/default_glass.png
new file mode 100644
index 00000000..74d6a025
Binary files /dev/null and b/mods/cozylights/textures/default_glass.png differ
diff --git a/mods/cozylights/textures/light_brush.png b/mods/cozylights/textures/light_brush.png
new file mode 100644
index 00000000..2d14ee0b
Binary files /dev/null and b/mods/cozylights/textures/light_brush.png differ
diff --git a/mods/cozylights/util/get_optional_depends.sh b/mods/cozylights/util/get_optional_depends.sh
new file mode 100644
index 00000000..6f296eff
--- /dev/null
+++ b/mods/cozylights/util/get_optional_depends.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# This script is naive: does not try to find mod.conf recursevily, so it sometimes can miss something,
+# and also it can add something unrelated, that is just some logic for light_sources and not a nodedef,
+# and probably some ancient mod' file extension won't be picked up if it's a thing, aside from gorillions of
+# other problems. Does enough so far
+
+match="light_source"
+
+games_directory="../../../games"
+mods_directory="../../"
+game_files=$(grep -l -R --include="*.lua" $match $games_directory)
+mod_files=$(grep -l -R --include="*.lua" $match $mods_directory)
+files=("${game_files[@]}""${mod_files[@]}")
+mod_names=""
+i=0
+for file in $files
+do
+ directory=$(dirname $file)
+ mod_conf=$(find $directory -name "*.conf")
+ if [[ $mod_conf != "" ]]; then
+ dir_name_comma="$(basename $directory),"
+ if [[ $mod_names != *$dir_name_comma* ]]; then
+ let i++;
+ mod_names="${mod_names} $dir_name_comma"
+ fi
+ fi
+done
+echo "mod_names array:" $mod_names
+echo "length:" $i
\ No newline at end of file
diff --git a/mods/cozylights/wield_light.lua b/mods/cozylights/wield_light.lua
new file mode 100644
index 00000000..ba7d764a
--- /dev/null
+++ b/mods/cozylights/wield_light.lua
@@ -0,0 +1,262 @@
+local c_air = minetest.get_content_id("air")
+local c_light1 = minetest.get_content_id("cozylights:light1")
+
+local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
+ c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
+
+local gent_total = 0
+local gent_count = 0
+local mf = math.floor
+--local cozycids_sunlight_propagates = cozylights.cozycids_sunlight_propagates
+
+local function destroy_stale_wielded_light(data,param2data,a,cozyplayer)
+ local c_light1 = c_lights[1]
+ local c_light14 = c_lights[14]
+ for j,p in ipairs(cozyplayer.prev_wielded_lights) do
+ if a and a:containsp(p) then
+ local idx = a:indexp(p)
+ local cid = data[idx]
+ if cid >= c_light1 and cid <= c_light14 then
+ if param2data[idx] > 0 and param2data[idx] <= 14 then
+ data[idx] = c_light1 + param2data[idx] - 1
+ else
+ data[idx] = c_air
+ end
+ end
+ else
+ local node = minetest.get_node(p)
+ if string.find(node.name, "cozylights:light") then
+ if node.param2 == 0 then
+ minetest.set_node(p,{name="air"})
+ else
+ minetest.set_node(p,{name="cozylights:light"..node.param2})
+ end
+ end
+ end
+ end
+ cozyplayer.prev_wielded_lights = {}
+end
+
+--- Like normal raycast but only covers surfaces, faster for large distances, somewhat less accurate
+local function lightcast_lite(pos, dir, dirs, radius,data, param2data, a,dim_levels,cozyplayer)
+ local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
+ local c_light14 = c_lights[14]
+ local light_nerf = 0
+ for i = 1, radius do
+ local x = mf(dx*i+0.5) + px
+ local y = mf(dy*i+0.5) + py
+ local z = mf(dz*i+0.5) + pz
+ local idx = a:index(x,y,z)
+ local cid = data[idx]
+ if cid and (cid == c_air or (cid >= c_light1 and cid <= c_light14)) then
+ for n = 1, 6 do
+ local adj_idx = idx+dirs[n]
+ local adj_cid = data[adj_idx]
+ if adj_cid and ((adj_cid < c_light1 and adj_cid ~= c_air)or adj_cid > c_light14) then
+ local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
+ local light = c_lights[dim]
+ if light > cid then
+ data[idx] = light
+ table.insert(cozyplayer.prev_wielded_lights, {x=x,y=y,z=z})
+ if cid == c_air and param2data[idx] > 0 then
+ param2data[idx] = 0
+ end
+ end
+ break
+ end
+ end
+ else
+ break
+ end
+ end
+end
+
+function cozylights:wielded_light_cleanup(player,cozyplayer,radius)
+ local pos = vector.round(player:getpos())
+ local vm = minetest.get_voxel_manip()
+ local emin, emax
+ local last_pos = cozyplayer.last_pos
+ local distance = vector.distance(pos,last_pos)
+ if distance < 20 then
+ local pos1 = {
+ x=pos.x < last_pos.x and pos.x or last_pos.x,
+ y=pos.y < last_pos.y and pos.y or last_pos.y,
+ z=pos.z < last_pos.z and pos.z or last_pos.z,
+ }
+ local pos2 = {
+ x=pos.x > last_pos.x and pos.x or last_pos.x,
+ y=pos.y > last_pos.y and pos.y or last_pos.y,
+ z=pos.z > last_pos.z and pos.z or last_pos.z,
+ }
+ emin, emax = vm:read_from_map(vector.subtract(pos1, radius+1), vector.add(pos2, radius+1))
+ else
+ emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
+ end
+ local data = vm:get_data()
+ local a = VoxelArea:new{
+ MinEdge = emin,
+ MaxEdge = emax
+ }
+ local param2data = vm:get_param2_data()
+ destroy_stale_wielded_light(data,param2data,a,cozyplayer)
+
+ cozylights:setVoxelManipData(vm,data,nil,true)
+end
+
+local max_wield_light_radius = cozylights.max_wield_light_radius
+
+function cozylights:set_wielded_light_radius(_radius)
+ max_wield_light_radius = _radius
+ minetest.settings:set("cozylights_wielded_light_radius",_radius)
+ cozylights.max_wield_light_radius = _radius
+end
+
+--ffi.cdef([[
+--typedef struct {float x, y, z;} v3float;
+--typedef struct {int16_t x, y, z;} v3;
+--typedef struct {uint16_t* data; uint8_t* param2data;} vm_data;
+--vm_data l_ttt(
+-- v3* sphere_surface, int sphere_surface_length, v3 pos, v3 minp, v3 maxp, uint16_t radius, uint16_t* data, uint8_t* param2data,
+-- uint8_t* dim_levels, bool* cozycids_sunlight, int c_air, uint16_t* c_lights
+--);
+--]])
+--local ctest = ffi.load(cozylights.modpath.."/liblight.so")
+
+function cozylights:draw_wielded_light(pos, last_pos, cozy_item,vel,cozyplayer,vm,a,data,param2data,emin,emax)
+ local t = os.clock()
+ local update_needed = 0
+ local radius, dim_levels = cozylights:calc_dims(cozy_item)
+ radius = radius > max_wield_light_radius and max_wield_light_radius or radius
+ if radius == 0 then
+ destroy_stale_wielded_light(data,param2data,a,cozyplayer)
+ local node = minetest.get_node(pos)
+ if node.name == "air" or string.match(node.name,"cozylights:") then
+ local brightness_mod = cozy_item.modifiers ~= nil and cozylights.coziest_table[cozy_item.modifiers].brightness or 0
+ local max_light = mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) > 0 and mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) or 0
+ max_light = max_light > 14 and 14 or max_light
+ local cid = minetest.get_content_id("cozylights:light"..max_light)
+ if cid > minetest.get_content_id(node.name) then
+ minetest.set_node(pos,{name="cozylights:light"..max_light,param2=node.param2})
+ cozyplayer.prev_wielded_lights[#cozyplayer.prev_wielded_lights+1] = pos
+ end
+ else
+ pos.y = pos.y - 1
+ local n_name = minetest.get_node(pos).name
+ if n_name == "air" or string.match(n_name,"cozylights:") then
+ local brightness_mod = cozy_item.modifiers ~= nil and cozylights.coziest_table[cozy_item.modifiers].brightness or 0
+ local max_light = mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) > 0 and mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) or 0
+ max_light = max_light > 14 and 14 or max_light
+ local cid = minetest.get_content_id("cozylights:light"..max_light)
+ if cid > minetest.get_content_id(node.name) then
+ minetest.set_node(pos,{name="cozylights:light"..max_light,param2=node.param2})
+ cozyplayer.prev_wielded_lights[#cozyplayer.prev_wielded_lights+1] = pos
+ end
+ end
+ end
+ return
+ end
+ local possible_pos = vector.add(pos,vel)
+ local node = minetest.get_node(possible_pos)
+ if node.name == "air" or string.match(node.name, "cozylights:light") then
+ pos = possible_pos
+ end
+
+ if vm == nil then
+ vm = minetest.get_voxel_manip()
+ local distance = vector.distance(pos,last_pos)
+ if distance < 20 then
+ local pos1 = {
+ x=pos.x < last_pos.x and pos.x or last_pos.x,
+ y=pos.y < last_pos.y and pos.y or last_pos.y,
+ z=pos.z < last_pos.z and pos.z or last_pos.z,
+ }
+ local pos2 = {
+ x=pos.x > last_pos.x and pos.x or last_pos.x,
+ y=pos.y > last_pos.y and pos.y or last_pos.y,
+ z=pos.z > last_pos.z and pos.z or last_pos.z,
+ }
+ emin, emax = vm:read_from_map(vector.subtract(pos1, radius+1), vector.add(pos2, radius+1))
+ else
+ emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
+ end
+ data = vm:get_data()
+ param2data = vm:get_param2_data()
+ a = VoxelArea:new{
+ MinEdge = emin,
+ MaxEdge = emax
+ }
+ update_needed = 1
+ end
+ destroy_stale_wielded_light(data,param2data,a,cozyplayer)
+
+ local c_light14 = c_lights[14]
+ local sphere_surface = cozylights:get_sphere_surface(radius)
+ local px = pos.x
+ local py = pos.y
+ local pz = pos.z
+ local y_below = py - 1
+ local y_above = py + 1
+ local cidb = data[a:index(px,y_below,pz)]
+ local cida = data[a:index(px,y_above,pz)]
+ if cidb and cida then
+ if (cidb == c_air or (cidb >= c_light1 and cidb <= c_light14))
+ and cida ~= c_air and (cida < c_light1 or cida > c_light14)
+ then
+ py = py - 1
+ end
+ else
+ return
+ end
+ local zstride, ystride = a.zstride, a.ystride
+ local dirs = { -1*ystride, 1*ystride,-1,1,-1*zstride,1*zstride}
+ --[[--cdata experiments, so if we offload heavy lifting on c, it will actually be slower by 20%
+ --not even a bit faster, so i d rather not continue on this
+ --because vm:set_data works with lua state and expects lua table,
+ --and interpreting c types back to lua table seems to be ridiculously expensive to bother
+ --basically lua is useless and helpless without lua state
+ minetest.chat_send_all("jit.status() "..cozylights:dump(jit.status()))
+ local csphere_surface = ffi.new("v3struct["..(#sphere_surface+1).."]", sphere_surface)
+ local cpos = ffi.new("v3struct", pos)
+ local cemin = ffi.new("v3struct",emin)
+ local cemax = ffi.new("v3struct",emax)
+ local cradius = ffi.new("int",radius)
+ local testcdata = ffi.new("uint16_t["..(#data).."]")
+ local cdim_levels = ffi.new("uint16_t["..(#dim_levels+1).."]", dim_levels)
+ local cc_air = ffi.new("int",c_air)
+ local cc_lights = ffi.new("uint16_t["..(#c_lights+1).."]", c_lights)
+ for i = 1, #data do
+ testcdata[i-1] = ffi.new("uint16_t",data[i])
+ end
+ local cparam2data = ffi.new("uint16_t["..#param2data.."]")
+ for i = 1, #param2data do
+ cparam2data[i-1] = ffi.new("uint16_t",param2data[i])
+ end
+ local ccozycids = ffi.new("bool["..#cozycids_sunlight_propagates.."]",cozycids_sunlight_propagates)
+ local length = ffi.new("int",#sphere_surface)
+ local idk = (ctest.l_ttt(csphere_surface,length, cpos,cemin,cemax,cradius,testcdata,cparam2data,cdim_levels,ccozycids,cc_air,cc_lights))
+ idk = idk.data
+ if idk ~= nil then
+ for i=0,#data do
+ local incoming = tonumber(idk[i])
+ if data[i+1] ~= incoming then
+ data[i+1] = incoming
+ table.insert(cozyplayer.prev_wielded_lights, a:position(i+1))
+ end
+ end
+ end
+ for i = 1, #param2data do
+ param2data[i] = tonumber(cparam2data[i-1])
+ end]]
+
+ for i,pos2 in ipairs(sphere_surface) do
+ lightcast_lite(pos, vector.direction(pos,{x=px+pos2.x,y=py+pos2.y,z=pz+pos2.z}),dirs,radius,data,param2data,a,dim_levels,cozyplayer)
+ end
+ if update_needed == 1 then
+ cozylights:setVoxelManipData(vm,data,param2data,true)
+ end
+ cozyplayer.last_wield_radius = radius
+ gent_total = gent_total + mf((os.clock() - t) * 1000)
+ gent_count = gent_count + 1
+ print("Av wield illum time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
+end
+
diff --git a/mods/cucina_vegana/recipes.lua b/mods/cucina_vegana/recipes.lua
index e3cc0a05..0d524e5a 100644
--- a/mods/cucina_vegana/recipes.lua
+++ b/mods/cucina_vegana/recipes.lua
@@ -205,7 +205,7 @@ minetest.register_craft({
minetest.register_craft({
output = "cucina_vegana:pizza_funghi_raw",
recipe = { {"", "group:food_oil", "cucina_vegana:rosemary"},
- {"flowers:mushroom_brown", "cucina_vegana:imitation_meat", "flowers:mushroom_brown"},
+ {"group:food_mushroom", "cucina_vegana:imitation_meat", "group:food_mushroom"},
{"", "group:pizza_dough", ""}
},
replacements = {
diff --git a/mods/cucina_vegana/recipes_support.lua b/mods/cucina_vegana/recipes_support.lua
index 29426021..3f66b02c 100644
--- a/mods/cucina_vegana/recipes_support.lua
+++ b/mods/cucina_vegana/recipes_support.lua
@@ -216,14 +216,14 @@ if minetest.get_modpath("bbq") then
minetest.register_craft( {
output = "bbq:stuffed_chop_raw 3",
type = "shapeless",
- recipe = {"group:food_onion", "group:food_bread", "flowers:mushroom_brown", "mobs:pork_raw", "default:apple"}
+ recipe = {"group:food_onion", "group:food_bread", "group:food_mushroom", "mobs:pork_raw", "default:apple"}
})
--Stuffed Mushroom Craft Recipe
minetest.register_craft( {
output = "bbq:stuffed_mushroom_raw 2",
type = "shapeless",
- recipe = {"group:food_tomato", "group:food_bread", "flowers:mushroom_brown"}
+ recipe = {"group:food_tomato", "group:food_bread", "group:food_mushroom"}
})
--Stuffed Pepper Craft Recipe
diff --git a/mods/emote/.github/workflows/luacheck.yml b/mods/emote/.github/workflows/luacheck.yml
new file mode 100644
index 00000000..097abcda
--- /dev/null
+++ b/mods/emote/.github/workflows/luacheck.yml
@@ -0,0 +1,17 @@
+name: luacheck
+
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: apt
+ run: sudo apt-get install -y luarocks
+ - name: luacheck install
+ run: luarocks install --local luacheck
+ - name: luacheck run
+ run: $HOME/.luarocks/bin/luacheck -q ./
diff --git a/mods/emote/.luacheckrc b/mods/emote/.luacheckrc
new file mode 100644
index 00000000..a5bbac5d
--- /dev/null
+++ b/mods/emote/.luacheckrc
@@ -0,0 +1,17 @@
+unused_args = false
+allow_defined_top = true
+
+read_globals = {
+ "DIR_DELIM",
+ "minetest", "core",
+ "dump",
+ "vector", "nodeupdate",
+ "VoxelManip", "VoxelArea",
+ "PseudoRandom", "ItemStack",
+ "intllib",
+}
+
+globals = {
+ "player_api",
+}
+
diff --git a/mods/emote/LICENSE.txt b/mods/emote/LICENSE.txt
new file mode 100644
index 00000000..4362b491
--- /dev/null
+++ b/mods/emote/LICENSE.txt
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ , 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/mods/emote/README.md b/mods/emote/README.md
new file mode 100644
index 00000000..551c5596
--- /dev/null
+++ b/mods/emote/README.md
@@ -0,0 +1,69 @@
+Emote - a player emote API
+##########################
+
+This mod aims to provide an API for player model animations such
+as sitting, waving, lying down, as well as some providing chat
+commands for the player to use these "emotes".
+
+## API
+
+The emote API consists of several functions that allow mods to
+manipulate the emote state of each player.
+
+`bool emote.start(player, emotestring)`
+
+Start the named emote for the named player. Returns false if
+the emote is unknown or the emote could not be started. Returns
+true otherwise.
+
+`emote.stop(player)`
+
+Stops any emote for the named player.
+
+`emote.list()`
+
+Lists known emotestring values.
+
+`emote.attach_to_node(player, pos, locked)`
+
+Attach the player to the node at pos. The attachment will be made using the
+parameters provided in the `emote` table in the nodedef:
+```
+nodedef.emote = {
+ emotestring = "sit",
+ eye_offset = {x = 0, y = 0, z = 0},
+ player_offset = {x = 0, y = 1/2, z = 0},
+ look_horizontal_offset = 0,
+}
+```
+
+if `locked` is `true`, then the player is fixed to the node and can only
+move until he presses `jump`. While sitting the player can look around but
+his character does not turn.
+
+The player offset vector will be rotated to account for the node facedir.
+
+## Commands
+
+The emotes are all usable by players using chat commands:
+/lay, /sleep, /sit, /point, /freeze, etc.
+
+## TODO
+
+The API currently only allows unattached emotes (ones where the
+player can just move and cancel the emote). The API needs to
+provide an additional function to allow attached emotes with
+rotation and offset so that players can easily sit on chairs,
+lie on beds or emote-interact with machines (e.g. point emote
+when interacting with a node).
+
+## Can I sit on stair blocks with this?
+
+The patch file `sit-on-stairs.patch` in this project is an example
+patch for minetest_game that will allow a player to right-click stair
+nodes and sit on them as if they were seats.
+
+## License
+
+Copyright (C) 2016 - Auke Kok
+LGPL-2.1
diff --git a/mods/emote/api.lua b/mods/emote/api.lua
new file mode 100644
index 00000000..6134578c
--- /dev/null
+++ b/mods/emote/api.lua
@@ -0,0 +1,149 @@
+local S = emote.S
+local facedir_to_look_horizontal = emote.util.facedir_to_look_horizontal
+local vector_rotate_xz = emote.util.vector_rotate_xz
+
+emote.emotes = {}
+
+emote.attached_to_node = {}
+emote.emoting = {}
+
+-- API functions
+
+function emote.register_emote(name, def)
+ emote.emotes[name] = def
+
+ minetest.register_chatcommand(name, {
+ description = S("Makes your character perform the @1 emote", name),
+ func = function(playername)
+ local player = minetest.get_player_by_name(playername)
+ if emote.start(player, name) then
+ if not emote.settings.announce_in_chat then
+ return true, S("You @1", name)
+ end
+ else
+ if not emote.settings.announce_in_chat then
+ return false, S("You failed to @1", name)
+ end
+ end
+ end,
+ })
+end
+
+function emote.start(player, emote_name)
+ if not minetest.is_player(player) then
+ emote.emoting[player] = nil
+ return
+ end
+
+ local emote_def = emote.emotes[emote_name]
+
+ if not emote_def then
+ return false
+ end
+
+ local player_name = player:get_player_name()
+ player_api.set_animation(player, emote_def.anim_name, emote_def.speed)
+ if emote_name == "stand" then
+ emote.emoting[player] = nil
+ player_api.player_attached[player_name] = nil
+ else
+ emote.emoting[player] = true
+ player_api.player_attached[player_name] = true
+ end
+
+ if emote_def.eye_offset then
+ player:set_eye_offset(emote_def.eye_offset, emote_def.eye_offset)
+
+ else
+ player:set_eye_offset()
+ end
+
+ if emote.settings.announce_in_chat then
+ minetest.chat_send_all(("* %s %s"):format(player_name, emote_def.description))
+ end
+
+ if emote_def.stop_after then
+ minetest.after(emote_def.stop_after, emote.stop, player)
+ end
+
+ return true
+end
+
+function emote.stop(player)
+ emote.start(player, "stand")
+end
+
+function emote.list()
+ local r = {}
+ for emote_name, _ in pairs(emote.emotes) do
+ table.insert(r, emote_name)
+ end
+ return r
+end
+
+function emote.attach_to_node(player, pos, locked)
+ local node = minetest.get_node(pos)
+ if node.name == "ignore" then
+ return false
+ end
+
+ if emote.attached_to_node[player] then
+ return
+ end
+
+ local def = minetest.registered_nodes[node.name].emote or {}
+
+ local emotedef = {
+ eye_offset = def.eye_offset or {x = 0, y = 1/2, z = 0},
+ player_offset = def.player_offset or {x = 0, y = 0, z = 0},
+ look_horizontal_offset = def.look_horizontal_offset or 0,
+ emotestring = def.emotestring or "sit",
+ }
+
+ local look_horizontal = facedir_to_look_horizontal(node.param2)
+ local offset = vector_rotate_xz(emotedef.player_offset, look_horizontal)
+ local rotation = look_horizontal + emotedef.look_horizontal_offset
+ local new_pos = vector.add(pos, offset)
+
+ emote.start(player, emotedef.emotestring)
+
+ if locked then
+ local object = minetest.add_entity(new_pos, "emote:attacher")
+ if object then
+ object:get_luaentity():init(player)
+ object:set_yaw(rotation)
+
+ player:set_attach(object, "", emotedef.eye_offset, minetest.facedir_to_dir(node.param2))
+
+ emote.attached_to_node[player] = object
+ end
+
+ else
+ player:set_pos(new_pos)
+ player:set_eye_offset(emotedef.eye_offset, {x = 0, y = 0, z = 0})
+ end
+
+ player:set_look_horizontal(rotation)
+end
+
+function emote.attach_to_entity(player, emotestring, obj)
+ -- not implemented yet.
+end
+
+function emote.detach(player)
+ if emote.attached_to_node[player] then
+ emote.attached_to_node[player]:detach()
+ end
+ -- check if attached?
+ player:set_eye_offset(vector.new(), vector.new())
+ emote.stop(player)
+end
+
+minetest.register_globalstep(function()
+ for player in pairs(emote.emoting) do
+ local ctrl = player:get_player_control()
+ if ctrl and (ctrl.jump or ctrl.up or ctrl.down or ctrl.left or ctrl.right) then
+ emote.stop(player)
+ end
+ end
+end)
diff --git a/mods/emote/entity.lua b/mods/emote/entity.lua
new file mode 100644
index 00000000..5f697f10
--- /dev/null
+++ b/mods/emote/entity.lua
@@ -0,0 +1,42 @@
+
+-- entity for locked emotes (attached to nodes, etc)
+local attacher = {
+ description = "Attachment entity for emotes",
+ physical = false,
+ visual = "upright_sprite",
+ visual_size = {x = 1/16, y = 1/16},
+ spritediv = {x = 1/16, y = 1/16},
+ collisionbox = {-1/16, -1/16, -1/16, 1/16, 1/16, 1/16},
+ textures = {"emote_blank.png"},
+ static_save = false,
+ init = function(self, player)
+ self.player = player
+ end,
+}
+
+function attacher:on_step()
+ if not minetest.is_player(self.player) then
+ self.object:remove()
+ return
+ end
+
+ local ctrl = self.player:get_player_control()
+ if ctrl and ctrl.jump then
+ self:detach()
+ end
+end
+
+function attacher:detach()
+ emote.attached[self.player] = nil
+
+ if not minetest.is_player(self.player) then
+ return
+ end
+
+ self.player:set_detach()
+ self.object:remove()
+
+ emote.stop(self.player)
+end
+
+minetest.register_entity("emote:attacher", attacher)
diff --git a/mods/emote/init.lua b/mods/emote/init.lua
new file mode 100644
index 00000000..05edbd69
--- /dev/null
+++ b/mods/emote/init.lua
@@ -0,0 +1,85 @@
+local modname = minetest.get_current_modname()
+local modpath = minetest.get_modpath(modname)
+local S = minetest.get_translator(modname)
+
+emote = {
+ modname = modname,
+ modpath = modpath,
+
+ S = S,
+
+ log = function(level, messagefmt, ...)
+ return minetest.log(level, ("[%s] %s"):format(modname, messagefmt:format(...)))
+ end,
+
+ dofile = function(...)
+ return dofile(table.concat({modpath, ...}, DIR_DELIM) .. ".lua")
+ end,
+}
+
+emote.dofile("settings")
+emote.dofile("util")
+emote.dofile("api")
+emote.dofile("entity")
+
+local model = player_api.registered_models["character.b3d"]
+
+emote.register_emote("stand", {
+ anim_name = "stand",
+ speed = 30,
+ description = S("stands up")
+})
+
+emote.register_emote("sit", {
+ anim_name = "sit",
+ speed = 30,
+ description = S("sits"),
+})
+
+emote.register_emote("lay", {
+ anim_name = "lay",
+ speed = 30,
+ description = S("lies down"),
+})
+
+emote.register_emote("sleep", { -- alias for lay
+ anim_name = "lay",
+ speed = 30,
+ description = S("falls asleep"),
+})
+
+model.animations.wave = {x = 192, y = 196, override_local = true}
+emote.register_emote("wave", {
+ anim_name = "wave",
+ speed = 15,
+ stop_after = 4,
+ description = S("waves")
+})
+
+model.animations.point = {x = 196, y = 196, override_local = true}
+emote.register_emote("point", {
+ anim_name = "point",
+ speed = 30,
+ description = S("points")
+})
+
+model.animations.freeze = {x = 205, y = 205, override_local = true}
+emote.register_emote("freeze", {
+ anim_name = "freeze",
+ speed = 30,
+ description = S("freezes")
+})
+
+--[[
+-- testing tool - punch any node to test attachment code
+]]--
+minetest.register_tool("emote:sleep", {
+ description = "use me on a bed bottom",
+ groups = {not_in_creative_inventory = 1},
+ on_use = function(itemstack, user, pointed_thing)
+ -- the delay here is weird, but the client receives a mouse-up event
+ -- after the punch and switches back to "stand" animation, undoing
+ -- the animation change we're doing.
+ minetest.after(0.5, emote.attach_to_node, user, pointed_thing.under)
+ end
+})
diff --git a/mods/emote/mod.conf b/mods/emote/mod.conf
new file mode 100644
index 00000000..e3e99e3d
--- /dev/null
+++ b/mods/emote/mod.conf
@@ -0,0 +1,6 @@
+name = emote
+description = Provides player emotes and an api to make the player model sit, lie down and perform other actions.
+depends = player_api
+release = 24880
+author = sofar
+title = Emote
diff --git a/mods/emote/screenshot.png b/mods/emote/screenshot.png
new file mode 100644
index 00000000..3bfdea90
Binary files /dev/null and b/mods/emote/screenshot.png differ
diff --git a/mods/emote/settings.lua b/mods/emote/settings.lua
new file mode 100644
index 00000000..075e1cbd
--- /dev/null
+++ b/mods/emote/settings.lua
@@ -0,0 +1,5 @@
+
+
+emote.settings = {
+ announce_in_chat = minetest.settings:get_bool("emote.announce_in_chat", false),
+}
diff --git a/mods/emote/settingtypes.txt b/mods/emote/settingtypes.txt
new file mode 100644
index 00000000..c3aa1282
--- /dev/null
+++ b/mods/emote/settingtypes.txt
@@ -0,0 +1,3 @@
+
+emote.announce_in_chat (announce emote in chat) bool false
+
diff --git a/mods/emote/sit-on-stairs.patch b/mods/emote/sit-on-stairs.patch
new file mode 100644
index 00000000..cf38650e
--- /dev/null
+++ b/mods/emote/sit-on-stairs.patch
@@ -0,0 +1,20 @@
+diff --git a/mods/stairs/init.lua b/mods/stairs/init.lua
+index 191c78d..5759ce5 100644
+--- a/mods/stairs/init.lua
++++ b/mods/stairs/init.lua
+@@ -77,6 +77,15 @@ function stairs.register_stair(subname, recipeitem, groups, images, description,
+
+ return minetest.item_place(itemstack, placer, pointed_thing, param2)
+ end,
++ emote = {
++ emotestring = "sit",
++ eye_offset = {x = 0, y = 9, z = 0},
++ player_offset = {x = 3/16, y = 1/16, z = 0},
++ look_horizontal_offset = math.pi,
++ },
++ on_rightclick = function(pos, node, clicker)
++ emote.attach_to_node(clicker, pos, true)
++ end,
+ })
+
+ -- for replace ABM
diff --git a/mods/emote/textures/emote_blank.png b/mods/emote/textures/emote_blank.png
new file mode 100644
index 00000000..af367b99
Binary files /dev/null and b/mods/emote/textures/emote_blank.png differ
diff --git a/mods/emote/util.lua b/mods/emote/util.lua
new file mode 100644
index 00000000..571f15dc
--- /dev/null
+++ b/mods/emote/util.lua
@@ -0,0 +1,27 @@
+local util = {}
+
+-- helper functions
+function util.facedir_to_look_horizontal(dir)
+ if dir == 0 then
+ return 0
+ elseif dir == 1 then
+ return math.pi * 3/2
+ elseif dir == 2 then
+ return math.pi
+ elseif dir == 3 then
+ return math.pi / 2
+ else
+ return nil
+ end
+end
+
+function util.vector_rotate_xz(vec, angle)
+ local a = angle - (math.pi * 3/2)
+ return {
+ x = (vec.z * math.sin(a)) - (vec.x * math.cos(a)),
+ y = vec.y,
+ z = (vec.z * math.cos(a)) - (vec.x * math.sin(a))
+ }
+end
+
+emote.util = util
diff --git a/mods/ethereal/food.lua b/mods/ethereal/food.lua
index c3b3ef24..0120bfa8 100644
--- a/mods/ethereal/food.lua
+++ b/mods/ethereal/food.lua
@@ -249,8 +249,8 @@ ethereal.add_eatable("ethereal:hearty_stew", 10)
minetest.register_craft({
output = "ethereal:hearty_stew",
recipe = {
- {"group:food_onion","flowers:mushroom_brown", "group:food_tuber"},
- {"","flowers:mushroom_brown", ""},
+ {"group:food_onion","group:food_mushroom", "group:food_tuber"},
+ {"","group:food_mushroom", ""},
{"","group:food_bowl", ""}
}
})
@@ -262,8 +262,8 @@ if fredo then
minetest.register_craft({
output = "ethereal:hearty_stew",
recipe = {
- {"group:food_onion","flowers:mushroom_brown", "group:food_beans"},
- {"","flowers:mushroom_brown", ""},
+ {"group:food_onion","group:food_mushroom", "group:food_beans"},
+ {"","group:food_mushroom", ""},
{"","group:food_bowl", ""}
}
})
diff --git a/mods/fmod/.cdb.json b/mods/fmod/.cdb.json
new file mode 100644
index 00000000..522bceb4
--- /dev/null
+++ b/mods/fmod/.cdb.json
@@ -0,0 +1,11 @@
+{
+ "type": "MOD",
+ "name": "fmod",
+ "title": "fmod",
+ "license": "LGPL-3.0-or-later",
+ "media_license": "CC-BY-SA-4.0",
+ "repo": "https://github.com/fluxionary/minetest-fmod.git",
+ "website": "https://github.com/fluxionary/minetest-fmod",
+ "issue_tracker": "https://github.com/fluxionary/minetest-fmod/issues",
+ "short_description": "flux's mod boilerplate"
+}
diff --git a/mods/fmod/.check_date.sh b/mods/fmod/.check_date.sh
new file mode 100644
index 00000000..831a6ddd
--- /dev/null
+++ b/mods/fmod/.check_date.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+grep $(date -u -I) mod.conf
+exit $?
diff --git a/mods/fmod/.editorconfig b/mods/fmod/.editorconfig
new file mode 100644
index 00000000..33568faf
--- /dev/null
+++ b/mods/fmod/.editorconfig
@@ -0,0 +1,14 @@
+# See https://editorconfig.org/
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{lua,luacheckrc}]
+indent_style = tab
diff --git a/mods/fmod/.github/FUNDING.yml b/mods/fmod/.github/FUNDING.yml
new file mode 100644
index 00000000..c39ac9d9
--- /dev/null
+++ b/mods/fmod/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: fluxionary
diff --git a/mods/fmod/.github/workflows/pre-commit.yml b/mods/fmod/.github/workflows/pre-commit.yml
new file mode 100644
index 00000000..d1faea17
--- /dev/null
+++ b/mods/fmod/.github/workflows/pre-commit.yml
@@ -0,0 +1,34 @@
+name: pre-commit
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: update
+ run: sudo apt-get update -y
+
+ - uses: actions/checkout@master
+ - uses: actions/setup-python@master
+
+ - name: install luarocks
+ run: sudo apt-get install -y luarocks
+
+ - name: add luarocks path
+ run: echo "$HOME/.luarocks/bin" >> $GITHUB_PATH
+
+ - name: luacheck install
+ run: luarocks install --local luacheck
+
+ - name: install cargo
+ run: sudo apt-get install -y cargo
+
+ - name: install stylua
+ run: cargo install stylua
+
+ - name: Install pre-commit
+ run: pip3 install pre-commit
+
+ - name: Run pre-commit
+ run: pre-commit run --all-files
diff --git a/mods/fmod/.luacheckrc b/mods/fmod/.luacheckrc
new file mode 100644
index 00000000..3ac57f67
--- /dev/null
+++ b/mods/fmod/.luacheckrc
@@ -0,0 +1,655 @@
+std = "lua51+luajit+minetest+fmod"
+unused_args = false
+max_line_length = 120
+
+stds.minetest = {
+ read_globals = {
+ "DIR_DELIM",
+ "dump",
+ "dump2",
+ "INIT",
+
+ 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.fmod = {
+ globals = {
+ "fmod",
+ },
+ read_globals = {
+ },
+}
diff --git a/mods/fmod/.pre-commit-config.yaml b/mods/fmod/.pre-commit-config.yaml
new file mode 100644
index 00000000..6d4489f9
--- /dev/null
+++ b/mods/fmod/.pre-commit-config.yaml
@@ -0,0 +1,41 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.3.0
+ hooks:
+ - id: fix-byte-order-marker
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - id: mixed-line-ending
+ args: [ --fix=lf ]
+
+ - repo: local
+ hooks:
+ - id: detect_debug
+ name: detect debug
+ language: pygrep
+ entry: DEBUG
+ pass_filenames: true
+ exclude: .pre-commit-config.yaml
+ fail_fast: true
+ - id: date_version
+ name: date version
+ language: script
+ entry: .check_date.sh
+ files: mod.conf
+ always_run: true
+ fail_fast: true
+ - id: stylua
+ name: stylua
+ language: system
+ entry: stylua
+ pass_filenames: true
+ types: [ file, lua ]
+ fail_fast: true
+ - id: luacheck
+ name: luacheck
+ language: system
+ entry: luacheck
+ pass_filenames: true
+ types: [ file, lua ]
+ args: [ -q ]
+ fail_fast: true
diff --git a/mods/fmod/LICENSE.txt b/mods/fmod/LICENSE.txt
new file mode 100644
index 00000000..b6ff27cc
--- /dev/null
+++ b/mods/fmod/LICENSE.txt
@@ -0,0 +1,168 @@
+this license is for the code.
+any non-code media included in this repository is covered by the contents of MEDIA_LICENSE.txt.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/mods/fmod/MEDIA_LICENSE.txt b/mods/fmod/MEDIA_LICENSE.txt
new file mode 100644
index 00000000..7d4f96c5
--- /dev/null
+++ b/mods/fmod/MEDIA_LICENSE.txt
@@ -0,0 +1,427 @@
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/mods/fmod/README.md b/mods/fmod/README.md
new file mode 100644
index 00000000..ca3fab52
--- /dev/null
+++ b/mods/fmod/README.md
@@ -0,0 +1,81 @@
+# fmod
+
+flux's mod boilerplate
+
+## what?
+
+i use this to create a common basic API that all of my mods share. it grabs a lot of mod metadata from e.g. mod.conf
+and settingtypes.txt and automatically makes those values available.
+
+this mod is primarily for my own use, but i'd be elated if other people find it useful. suggestions are welcome, but
+note that my own patterns come first in any suggested changes. the more mods i create which depend on this (there's
+over 70 and counting, though only 50 or 60 are released), the harder it will be to change fundamental features. but
+if i've made major mistake somehow, please let me know sooner rather than later!
+
+## public API
+
+* `modname = fmod.create(fork)`
+
+ creates the boilerplate.
+
+ `fork` is an optional parameter for other people to use if they fork a mod.
+
+the api which is created looks like this:
+
+```lua
+local f = string.format
+local modname = minetest.get_current_modname()
+local S = minetest.get_translator(modname)
+local modpath = minetest.get_modpath(modname)
+local mod_conf = Settings(modpath .. DIR_DELIM .. "settingtypes.txt")
+
+modname = {
+ modname = modname,
+ modpath = modpath,
+ title = mod_conf:get("title") or modname,
+ description = mod_conf:get("description"),
+ author = mod_conf:get("author"),
+ license = mod_conf:get("license"),
+ version = mod_conf:get("version"),
+ fork = fork or "flux", -- fork is the argument to `fmod.create(fork)`
+
+ S = S,
+
+ has = build_has(mod_conf), -- this reads mod.conf and creates a set from optional_depends
+ settings = get_settings(modname), -- this reads and parses settingtypes.txt, and populates w/ values from
+ -- minetest.conf, or otherwise the defaults specified in settingtypes.txt
+
+ check_version = function(required)
+ assert(mod_conf:get("version") >= required, f("%s requires a newer version of %s; please update it", minetest.get_current_modname(), modname))
+ end,
+
+ check_minetest_version = function(major, minor, patch, other, reason)
+ assert(..., "check that the required version of the game engine is present. \"other\" is reserved for " ..
+ "future use, once i figure out how to deal w/ engine forks."
+ )
+ end,
+
+ log = function(level, messagefmt, ...)
+ return minetest.log(level, f("[%s] %s", modname, f(messagefmt, ...)))
+ end,
+
+ chat_send_player = function(player, messagefmt, ...)
+ minetest.chat_send_player(player, f("[%s] %s", modname, S(messagefmt, ...)))
+ end,
+
+ chat_send_all = function(message, ...)
+ minetest.chat_send_all(f("[%s] %s", modname, S(message, ...)))
+ end,
+
+ dofile = function(...)
+ return dofile(table.concat({modpath, ...}, DIR_DELIM) .. ".lua")
+ end,
+
+ async_dofile = function(...) -- load a file into the async environment
+ return minetest.register_async_dofile(table.concat({ modpath, ... }, DIR_DELIM) .. ".lua")
+ end,
+}
+```
+
+note that fmod itself is created w/ this same boilerplate; therefore, i can indicate that certain mods require an
+up-to-date version of fmod to operate.
diff --git a/mods/fmod/build_has.lua b/mods/fmod/build_has.lua
new file mode 100644
index 00000000..691872ee
--- /dev/null
+++ b/mods/fmod/build_has.lua
@@ -0,0 +1,12 @@
+return function(mod_conf)
+ local optional_depends = mod_conf:get("optional_depends")
+ if not optional_depends then
+ return {}
+ end
+ local has = {}
+ for _, mod in ipairs(optional_depends:split()) do
+ mod = mod:trim()
+ has[mod] = minetest.get_modpath(mod) and true or false
+ end
+ return has
+end
diff --git a/mods/fmod/get_settings.lua b/mods/fmod/get_settings.lua
new file mode 100644
index 00000000..8a0024e6
--- /dev/null
+++ b/mods/fmod/get_settings.lua
@@ -0,0 +1,150 @@
+-- https://github.com/minetest/minetest/blob/master/builtin/settingtypes.txt
+-- https://github.com/minetest/minetest/blob/master/builtin/mainmenu/settings/settingtypes.lua
+
+local f = string.format
+
+local function get_lines_from_file(filename)
+ local fh = io.open(filename, "r")
+ if not fh then
+ return
+ end
+ local lines = fh:read("*all"):split("\n")
+ fh:close()
+ return lines
+end
+
+local function strip_readable_name(text)
+ if text:sub(1, 1) ~= "(" then
+ error(f("%q %s", text, text))
+ end
+ local depth = 1
+ local i = 2
+ while depth > 0 do
+ if text:sub(i, i) == ")" then
+ depth = depth - 1
+ elseif text:sub(i, i) == "(" then
+ depth = depth + 1
+ end
+ i = i + 1
+ end
+ return text:sub(i):trim()
+end
+
+local function starts_with(s, start)
+ return s:sub(1, #start) == start
+end
+
+local function parse_line(modname, line)
+ if line:match("^%s*#") or line:match("^%s*%[") or line:match("^%s*$") then
+ return
+ end
+ line = line:trim()
+ local full_name, rest = unpack(line:split("%s+", false, 1, true))
+ if not (full_name and rest) then
+ return
+ end
+ local secure = false
+ if starts_with(full_name, "secure.") then
+ secure = true
+ full_name = full_name:sub(#"secure." + 1)
+ end
+ local modname2, short_name = unpack(full_name:split("[:%.]", false, 1, true))
+ assert(modname2 == modname, f("invalid setting name %s", full_name))
+ rest = strip_readable_name(rest)
+ local datatype, default, params
+ datatype, rest = unpack(rest:split("%s", true, 1, true))
+ rest = rest or ""
+ if datatype == "string" then
+ if rest:sub(1, 1) == '"' and rest:sub(#rest, #rest) == '"' then
+ -- this is not actually according to spec settingtypes.txt, but there's no good way to specify that the
+ -- default value is a single space, so we invent our own syntax
+ default = rest:sub(2, #rest - 1)
+ elseif rest:sub(1, 2) == '\\"' then
+ default = rest:sub(2)
+ else
+ default = rest
+ end
+ params = ""
+ else
+ default, params = unpack(rest:split("%s+", false, 1, true))
+ end
+
+ full_name = (secure and "secure." or "") .. full_name
+ return full_name, short_name, datatype, default, params
+end
+
+local getters = {
+ -- TODO there's other setting types, but i don't use them and no-one else uses this mod
+ int = function(full_name, default, params)
+ return tonumber(minetest.settings:get(full_name)) or tonumber(default)
+ end,
+ string = function(full_name, default, params)
+ return minetest.settings:get(full_name) or default
+ end,
+ bool = function(full_name, default, params)
+ return minetest.settings:get_bool(full_name, minetest.is_yes(default))
+ end,
+ float = function(full_name, default, params)
+ return tonumber(minetest.settings:get(full_name)) or tonumber(default)
+ end,
+ enum = function(full_name, default, params)
+ return minetest.settings:get(full_name) or default
+ end,
+ path = function(full_name, default, params)
+ return minetest.settings:get(full_name) or default or ""
+ end,
+ filepath = function(full_name, default, params)
+ return minetest.settings:get(full_name) or default or ""
+ end,
+ key = function(full_name, default, params)
+ return minetest.settings:get(full_name) or default
+ end,
+ flags = function(full_name, default, params)
+ return (minetest.settings:get(full_name) or default):split()
+ end,
+ v3f = function(full_name, default, params)
+ return minetest.string_to_pos(minetest.settings:get(full_name) or default)
+ end,
+}
+
+return function(modname)
+ local modpath = minetest.get_modpath(modname)
+ local settingtypes_lines = get_lines_from_file(modpath .. DIR_DELIM .. "settingtypes.txt")
+
+ if not settingtypes_lines then
+ return
+ end
+
+ local settings = {}
+ for _, line in ipairs(settingtypes_lines) do
+ local full_name, short_name, datatype, default, params = parse_line(modname, line)
+ if full_name then
+ local getter = getters[datatype]
+ if getter then
+ settings[short_name] = getter(full_name, default, params)
+ else
+ error("TODO: implement parsing settings of type " .. datatype)
+ end
+ end
+ end
+
+ local listeners_by_key = {}
+
+ return setmetatable({
+ _subscribe_for_modification = function(self, key, func)
+ local listeners = listeners_by_key[key] or {}
+ table.insert(listeners, func)
+ listeners_by_key[key] = listeners
+ end,
+ }, {
+ __index = function(self, key)
+ return settings[key]
+ end,
+ __newindex = function(self, key, value)
+ settings[key] = value
+ for _, func in ipairs(listeners_by_key[key] or {}) do
+ func(value)
+ end
+ end,
+ })
+end
diff --git a/mods/fmod/init.lua b/mods/fmod/init.lua
new file mode 100644
index 00000000..ba4f05aa
--- /dev/null
+++ b/mods/fmod/init.lua
@@ -0,0 +1,169 @@
+local f = string.format
+
+local get_current_modname = minetest.get_current_modname
+local get_modpath = minetest.get_modpath
+
+local our_modname = get_current_modname()
+local our_modpath = get_modpath(our_modname)
+
+local build_has = dofile(our_modpath .. DIR_DELIM .. "build_has.lua")
+local get_settings = dofile(our_modpath .. DIR_DELIM .. "get_settings.lua")
+local parse_version = dofile(our_modpath .. DIR_DELIM .. "parse_version.lua")
+
+local function create(fork, extra_private_state)
+ local modname = get_current_modname()
+ local modpath = get_modpath(modname)
+ local S = minetest.get_translator(modname)
+ local F = minetest.formspec_escape
+
+ local mod_conf = Settings(modpath .. DIR_DELIM .. "mod.conf")
+ assert(modname == mod_conf:get("name"), "mod name mismatch")
+
+ local version = parse_version(mod_conf:get("version"))
+
+ local private_state = {
+ -- minetest.request_insecure_environment() can't be used here
+ -- minetest.request_http_api() can't be used here
+ mod_storage = INIT == "game" and minetest.get_mod_storage(),
+ }
+
+ if extra_private_state then
+ for k, v in pairs(extra_private_state) do
+ private_state[k] = v
+ end
+ end
+
+ return {
+ modname = modname,
+ modpath = modpath,
+
+ title = mod_conf:get("title") or modname,
+ description = mod_conf:get("description"),
+ author = mod_conf:get("author"),
+ license = mod_conf:get("license"),
+ media_license = mod_conf:get("media_license"),
+ website = mod_conf:get("website") or mod_conf:get("url"),
+ version = version,
+ fork = fork or "flux",
+
+ S = S,
+ FS = function(...)
+ return F(S(...))
+ end,
+
+ has = build_has(mod_conf),
+ settings = get_settings(modname),
+
+ check_version = function(required, reason)
+ if type(required) == "table" then
+ required = os.time(required)
+ end
+ local calling_modname = minetest.get_current_modname() or "UNKNOWN"
+ if reason then
+ assert(
+ version >= required,
+ f(
+ "%s requires a newer version of %s because %q; please update it.\n"
+ .. "go to the main game menu, click the content tab, browse online content, and update all.\n"
+ .. "(have %s, require %s)",
+ calling_modname,
+ modname,
+ reason,
+ version,
+ required
+ )
+ )
+ else
+ assert(
+ version >= required,
+ f(
+ "%s requires a newer version of %s; please update it.\n"
+ .. "go to the main game menu, click the content tab, browse online content, and update all.\n"
+ .. "(have %s, require %s)",
+ calling_modname,
+ modname,
+ version,
+ required
+ )
+ )
+ end
+ end,
+
+ check_minetest_version = function(major, minor, patch, other, reason)
+ local mt_version = minetest.get_version()
+ -- TODO: figure out how to allow various "projects" (e.g. Minetest, Multicraft) properly
+ -- TODO: we perhaps want to allow depending on multiple projects, so this is complicated.
+ local mt_major, mt_minor, mt_patch = mt_version.string:match("^(%d+)%.(%d+)%.(%d+)")
+ if not (mt_major and mt_minor and mt_patch) then
+ error(
+ f(
+ "%s is not compatible w/ minetest %s because we don't understand the version",
+ modname,
+ mt_version.string
+ )
+ )
+ end
+ mt_major = tonumber(mt_major)
+ mt_minor = tonumber(mt_minor)
+ mt_patch = tonumber(mt_patch)
+ local check = true
+ if mt_major < major then
+ check = false
+ elseif mt_major == major then
+ if mt_minor < minor then
+ check = false
+ elseif mt_minor == minor then
+ if mt_patch < patch then
+ check = false
+ end
+ end
+ end
+ if not check then
+ if reason then
+ error(
+ f("%s requires at least minetest %i.%i.%i because it %q", modname, major, minor, patch, reason)
+ )
+ else
+ error(f("%s requires at least minetest %i.%i.%i", modname, major, minor, patch))
+ end
+ end
+ end,
+
+ log = function(level, messagefmt, ...)
+ return minetest.log(level, f("[%s] %s", modname, f(messagefmt, ...)))
+ end,
+
+ chat_send_player = INIT == "game" and function(player, messagefmt, ...)
+ if player.get_player_name then
+ player = player:get_player_name()
+ end
+
+ minetest.chat_send_player(player, f("[%s] %s", modname, S(messagefmt, ...)))
+ end,
+
+ chat_send_all = INIT == "game" and function(message, ...)
+ minetest.chat_send_all(f("[%s] %s", modname, S(message, ...)))
+ end,
+
+ dofile = function(...)
+ assert(modname == get_current_modname(), "attempt to call dofile from external mod")
+ local filename = table.concat({ modpath, ... }, DIR_DELIM) .. ".lua"
+ local loader = assert(loadfile(filename))
+ return loader(private_state)
+ end,
+
+ async_dofile = function(...)
+ assert(modname == get_current_modname(), "attempt to call async_dofile from external mod")
+ local filename = table.concat({ modpath, ... }, DIR_DELIM) .. ".lua"
+ return minetest.register_async_dofile(filename)
+ end,
+ },
+ private_state
+end
+
+fmod = create()
+fmod.create = create
+
+if INIT == "game" then
+ fmod.async_dofile("init")
+end
diff --git a/mods/fmod/mod.conf b/mods/fmod/mod.conf
new file mode 100644
index 00000000..1c88d37f
--- /dev/null
+++ b/mods/fmod/mod.conf
@@ -0,0 +1,11 @@
+name = fmod
+title = fmod
+description = flux's mod boilerplate
+website = https://content.minetest.net/packages/rheo/fmod/
+author = rheo
+license = LGPL-3.0-or-later
+media_license = CC-BY-SA-4.0
+version = 2024-04-03
+min_minetest_version = 5.8.0
+supported_games = *
+release = 24515
diff --git a/mods/fmod/parse_version.lua b/mods/fmod/parse_version.lua
new file mode 100644
index 00000000..c1ca253f
--- /dev/null
+++ b/mods/fmod/parse_version.lua
@@ -0,0 +1,28 @@
+local f = string.format
+
+return function(version)
+ local y, m, d, h, mi, s
+ y, m, d = version:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
+ if y and m and d then
+ return os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d) })
+ end
+
+ y, m, d, s = version:match("^(%d%d%d%d)-(%d%d)-(%d%d)[%.%s](%d+)$")
+ if y and m and d and s then
+ return os.time({ year = tonumber(y), month = tonumber(m), day = tonumber(d), sec = tonumber(s) })
+ end
+
+ y, m, d, h, mi, s = version:match("^(%d%d%d%d)-(%d%d)-(%d%d)[T ](%d%d):(%d%d):(%d%d)$")
+ if y and m and d and h and mi and s then
+ return os.time({
+ year = tonumber(y),
+ month = tonumber(m),
+ day = tonumber(d),
+ hour = tonumber(h),
+ min = tonumber(mi),
+ sec = tonumber(s),
+ })
+ end
+
+ error(f("can't parse version %q", version))
+end
diff --git a/mods/fmod/screenshot.png b/mods/fmod/screenshot.png
new file mode 100644
index 00000000..023c0348
Binary files /dev/null and b/mods/fmod/screenshot.png differ
diff --git a/mods/futil/.cdb.json b/mods/futil/.cdb.json
new file mode 100644
index 00000000..37f63526
--- /dev/null
+++ b/mods/futil/.cdb.json
@@ -0,0 +1,11 @@
+{
+ "type": "MOD",
+ "name": "futil",
+ "title": "futil",
+ "short_description": "flux's utility mod",
+ "repo": "https://github.com/fluxionary/minetest-futil.git",
+ "website": "https://github.com/fluxionary/minetest-futil",
+ "issue_tracker": "https://github.com/fluxionary/minetest-futil/issues",
+ "license": "LGPL-3.0-or-later",
+ "media_license": "CC-BY-SA-4.0"
+}
diff --git a/mods/futil/.check_date.sh b/mods/futil/.check_date.sh
new file mode 100644
index 00000000..831a6ddd
--- /dev/null
+++ b/mods/futil/.check_date.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+grep $(date -u -I) mod.conf
+exit $?
diff --git a/mods/futil/.editorconfig b/mods/futil/.editorconfig
new file mode 100644
index 00000000..33568faf
--- /dev/null
+++ b/mods/futil/.editorconfig
@@ -0,0 +1,14 @@
+# See https://editorconfig.org/
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{lua,luacheckrc}]
+indent_style = tab
diff --git a/mods/futil/.github/FUNDING.yml b/mods/futil/.github/FUNDING.yml
new file mode 100644
index 00000000..c39ac9d9
--- /dev/null
+++ b/mods/futil/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: fluxionary
diff --git a/mods/futil/.github/workflows/pre-commit.yml b/mods/futil/.github/workflows/pre-commit.yml
new file mode 100644
index 00000000..d1faea17
--- /dev/null
+++ b/mods/futil/.github/workflows/pre-commit.yml
@@ -0,0 +1,34 @@
+name: pre-commit
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: update
+ run: sudo apt-get update -y
+
+ - uses: actions/checkout@master
+ - uses: actions/setup-python@master
+
+ - name: install luarocks
+ run: sudo apt-get install -y luarocks
+
+ - name: add luarocks path
+ run: echo "$HOME/.luarocks/bin" >> $GITHUB_PATH
+
+ - name: luacheck install
+ run: luarocks install --local luacheck
+
+ - name: install cargo
+ run: sudo apt-get install -y cargo
+
+ - name: install stylua
+ run: cargo install stylua
+
+ - name: Install pre-commit
+ run: pip3 install pre-commit
+
+ - name: Run pre-commit
+ run: pre-commit run --all-files
diff --git a/mods/futil/.gitignore b/mods/futil/.gitignore
new file mode 100644
index 00000000..7cd56c2d
--- /dev/null
+++ b/mods/futil/.gitignore
@@ -0,0 +1 @@
+*.unfinished.*
diff --git a/mods/futil/.luacheckrc b/mods/futil/.luacheckrc
new file mode 100644
index 00000000..0ca9038f
--- /dev/null
+++ b/mods/futil/.luacheckrc
@@ -0,0 +1,658 @@
+std = "lua51+luajit+minetest+futil"
+unused_args = false
+max_line_length = 120
+
+stds.minetest = {
+ read_globals = {
+ "DIR_DELIM",
+ "dump",
+ "dump2",
+ "INIT",
+
+ 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 = {},
+ in_area = {},
+ 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 = {},
+ },
+ },
+ "Raycast",
+ 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 = {other_fields = true},
+ auth_reload = {},
+ ban_player = {},
+ builtin_auth_handler = {other_fields = true},
+ bulk_set_node = {},
+ calculate_knockback = {},
+ callback_origins = {other_fields = true},
+ cancel_shutdown_requests = {},
+ chat_send_all = {},
+ chat_send_player = {},
+ chatcommands = {other_fields = true},
+ 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 = {other_fields = true},
+ create_detached_inventory = {},
+ create_detached_inventory_raw = {},
+ create_schematic = {},
+ debug = {},
+ decode_base64 = {},
+ decompress = {},
+ delete_area = {},
+ delete_particlespawner = {},
+ deserialize = {},
+ detached_inventories = {other_fields = true},
+ 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 = {other_fields = true},
+ emerge_area = {},
+ encode_base64 = {},
+ encode_png = {},
+ env = {other_fields = true},
+ explode_scrollbar_event = {},
+ explode_table_event = {},
+ explode_textlist_event = {},
+ facedir_to_dir = {},
+ features = {other_fields = true},
+ 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 = {other_fields = true},
+ mkdir = {},
+ mod_channel_join = {},
+ mvdir = {},
+ node_dig = {},
+ node_punch = {},
+ nodedef_default = {other_fields = true},
+ noneitemdef_default = {},
+ notify_authentication_modified = {},
+ object_refs = {other_fields = true},
+ 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 = {other_fields = true},
+ 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 = {other_fields = true},
+ 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.futil = {
+ globals = {
+ "futil",
+ },
+ read_globals = {
+ "fmod",
+ },
+}
diff --git a/mods/futil/.pre-commit-config.yaml b/mods/futil/.pre-commit-config.yaml
new file mode 100644
index 00000000..6d4489f9
--- /dev/null
+++ b/mods/futil/.pre-commit-config.yaml
@@ -0,0 +1,41 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.3.0
+ hooks:
+ - id: fix-byte-order-marker
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - id: mixed-line-ending
+ args: [ --fix=lf ]
+
+ - repo: local
+ hooks:
+ - id: detect_debug
+ name: detect debug
+ language: pygrep
+ entry: DEBUG
+ pass_filenames: true
+ exclude: .pre-commit-config.yaml
+ fail_fast: true
+ - id: date_version
+ name: date version
+ language: script
+ entry: .check_date.sh
+ files: mod.conf
+ always_run: true
+ fail_fast: true
+ - id: stylua
+ name: stylua
+ language: system
+ entry: stylua
+ pass_filenames: true
+ types: [ file, lua ]
+ fail_fast: true
+ - id: luacheck
+ name: luacheck
+ language: system
+ entry: luacheck
+ pass_filenames: true
+ types: [ file, lua ]
+ args: [ -q ]
+ fail_fast: true
diff --git a/mods/futil/API.md b/mods/futil/API.md
new file mode 100644
index 00000000..dea83d01
--- /dev/null
+++ b/mods/futil/API.md
@@ -0,0 +1,469 @@
+# WARNING
+
+this is *VERY OUT OF DATE*. this is a mod for my (flux's) personal use, and maintaining documentation outside the code
+isn't worth the time. if other people start using this, i'll reconsider that position.
+
+## classes
+
+* `futil.class1(super)`
+ a simple class w/ optional inheritance
+* `futil.class(...)`
+ a less simple class w/ multiple inheritance and `is_a` support
+
+## data structures
+
+* `futil.Deque`
+
+ a [deque](https://en.wikipedia.org/wiki/Double-ended_queue). supported methods:
+ * `Deque:size()`
+ * `Deque:push_front(value)`
+ * `Deque:push_back(value)`
+ * `Deque:pop_front()`
+ * `Deque:pop_back()`
+
+* `futil.PairingHeap`
+
+ a [pairing heap](https://en.wikipedia.org/wiki/Pairing_heap). supported methods:
+ * `PairingHeap:size()`
+ * `PairingHeap:peek_max()`
+ * `PairingHeap:delete(value)`
+ * `PairingHeap:delete_max()`
+ * `PairingHeap:get_priority(value)`
+ * `PairingHeap:set_priority(value, priority)`
+
+* `futil.DefaultTable`
+
+ a table in which missing keys are automatically filled in. example usage:
+ ```lua
+ local default_table = futil.DefaultTable(function(key) return {} end)
+ default_table.foo.bar = 100 -- foo is automatically created as a table
+ ```
+
+## general routines
+
+* `futil.check_call(func)`
+
+ wraps `func` in a pcall. if no error occurs, returns the results. otherwise, logs and returns nil.
+
+* `futil.memoize1(f)`
+
+ memoize a single-argument function
+
+* `futil.memoize_dumpable(f)`
+
+ memoize a function if the arguments produce a unique result when `dump()`-ed
+
+* `futil.memoize1_modstorage(id, func)`
+
+ memoize a function and store the results in modstorage, so they persist between sessions.
+
+* `futil.truncate(s, max_length, suffix)`
+
+ if the string is longer than max_length, truncate it and append suffix. suffix is optional, defaults to "..."
+
+* `futil.lc_cmp(a, b)`
+
+ case-insensitive comparator
+
+* `futil.table.set_all(t1, t2)`
+
+ sets all key/value pairs of t2 in t1
+
+* `futil.table.pairs_by_value(t, sort_function)`
+
+ iterator which returns key/value pairs, sorted by value
+
+* `futil.table.pairs_by_key(t, sort_function)`
+
+ iterator which returns key/value pairs, sorted by key
+
+* `futil.table.size(t)`
+
+ gets the size of a table
+
+* `futil.table.is_empty(t)`
+
+ returns true if the table is empty
+
+* `futil.equals(a, b)`
+
+ returns true if the tables (or other values) are equivalent. do not use w/ recursive structures.
+ currently does not inspect metatables.
+
+* `futil.table.count_elements(t)`
+
+ given a table in which some values may repeat, returns a table mapping values to their count.
+
+* `futil.table.sets_intersect(set1, set2)`
+
+ returns true if `set1` and `set2` have any keys in common.
+
+* `futil.table.iterate(t)`
+
+ iterates the values of an array-like table
+
+* `futil.table.reversed(t)`
+
+ returns a reversed copy of the table.
+
+* `futil.table.contains(t, value)`
+
+ returns `true` if value is in table
+
+* `futil.table.keys(t)`
+
+ returns a table of the keys in the given tables.
+
+* `futil.table.values(t)`
+
+ returns a table of the values in the given tables.
+
+* `futil.table.sort_keys(t, sort_function)`
+
+ returns a table of the sorted keys of the given table.
+
+* `futil.wait(n)`
+
+ busy-waits n microseconds
+
+* `futil.file_exists(path)`
+
+ returns true if the path points to a file that can be opened
+
+* `futil.load_file(filename)`
+
+ returns the contents of the file if it exists, otherwise nil.
+
+* `futil.write_file(filename, contents)`
+
+ writes to a file. returns true if success, false if not.
+
+* `futil.path_concat(...)`
+
+ concatenates part of a file path.
+
+* `futil.path_split(path)`
+
+ splits a path into parts.
+
+* `futil.string.truncate(s, max_length, suffix)`
+
+ truncate a string if it is longer than max_length, adding suffix (default "...").
+
+* `futil.string.lc_cmp(a, b)`
+
+ compares the lower-case values of strings a and b.
+
+* `futil.seconds_to_interval(time)`
+
+ transforms a time (in seconds) to a format like "\[\[\[\[:]:]:]:]"p
+
+* `futil.format_utc(timestamp)`
+
+ formats a timestamp in UTC.
+
+### predicates
+
+* `futil.is_nil(v)`
+
+ true if v is `nil`
+
+* `futil.is_boolean(v)`
+
+ true if `v` is a boolean.
+
+* `futil.is_number(v)`
+
+ true if `v` is a number.
+
+* `futil.is_string(v)`
+
+ true if `v` is a string.
+
+* `futil.is_userdata(v)`
+
+ true if `v` is userdata.
+
+* `futil.is_function(v)`
+
+ true if `v` is a function.
+
+* `futil.is_thread(v)`
+
+ true if `v` is a thread.
+
+* `futil.is_table(v)`
+
+ true if `v` is a table.
+
+### functional
+
+* `futil.functional.noop()`
+
+ the NOTHING function does nothing.
+
+* `futil.functional.identity(x)`
+
+ returns x
+
+* `futil.functional.izip(...)`
+
+ [zips](https://docs.python.org/3/library/functions.html#zip) iterators.
+
+* `futil.functional.zip(...)`
+
+ [zips](https://docs.python.org/3/library/functions.html#zip) tables.
+
+* `futil.functional.imap(func, ...)`
+
+ maps a function to a sequence of iterators. the first arg to func is the first element of each iterator, etc.
+
+* `futil.functional.map(func, ...)`
+
+ maps a function to a sequence of tables. the first arg to func is the first element of each table, etc.
+
+* `futil.functional.apply(func, t)`
+
+ for all keys `k`, set `t[k] = func(t[k])`
+
+* `futil.functional.reduce(func, t, initial)`
+
+ applies binary function `func` to successive elements in t and a "total". supply `initial` if possibly `#t == 0`.
+ e.g. `local sum = function(values) return reduce(function(a, b) return a + b end, values, 0) end`.
+
+* `futil.functional.partial(func, ...)`
+
+ curries `func`. `partial(func, a, b, c)(d, e, f) == func(a, b, c, d, e, f)
+
+* `futil.functional.compose(a, b)`
+
+ binary operator which composes two functions. `compose(a, b)(x) == a(b(x))`
+
+* `futil.functional.ifilter(pred, i)`
+
+ returns an interator which returns the values of iterator `i` which match predicate `pred`
+
+* `futil.functional.filter(pred, t)`
+
+ returns an interator which returns the values of table `t` which match predicate `pred`
+
+* `futil.functional.iall(i)`
+
+ given an iterator, returns true if all non-nil values of the iterator are not false.
+
+* `futil.functional.all(t)`
+
+ given a table, returns true if the table doesn't contain any `false` values
+
+* `futil.functional.iany(i)`
+
+ given an iterator, returns true if the iterator produces any non-false values.
+
+* `futil.functional.any(t)`
+
+ given a table, returns true if it contains any non-false values.
+
+### iterators
+
+* `futil.iterators.range(...)`
+
+ * one arg: return an iterator from 1 to x.
+ * two args: return an iterator from x to y
+ * three args: return an iterator from x to y, incrementing by z
+
+* `iterators.repeat_(value, times)`
+
+ * times = nil: return `value` forever
+ * times = positive number: return `value` `times` times
+
+* `futil.iterators.chain(...)`
+
+ given a sequence of iterators, return an iterator which will return the values from each in turn.
+
+* `futil.iterators.count(start, step)`
+
+ returns an infinite iterator which counts from start by step. if step is not specified, counts by 1.
+
+* `futil.iterators.values(t)`
+
+ returns an iterator of the values in the table.
+
+* `futil.list(iterator)`
+
+ given an iterator, returns a table of the values of the iterator.
+
+* `futil.list_multiple(iterator)`
+
+ given an iterator which returns multiple values on each step, create a table of tables of those values.
+
+### math
+
+* `futil.math.idiv(a, b)`
+
+ returns the whole part of a division and the remainder, e.g. `math.floor(a/b), a%b`.
+
+* futil.math.bound(m, v, M)
+
+ if v is less than m, return m. if v is greater than M, return M. else return v.
+
+* futil.math.in_bounds(m, v, M)
+
+ return true if m <= v and v <= M
+
+* futil.math.is_integer(v)
+
+ returns true if v is an integer.
+
+* futil.math.is_u8(i)
+
+ returns true if i is a valid unsigned 8 bit value.
+
+* futil.math.is_u16(i)
+
+ returns true if i is an unsigned 16 bit value.
+
+* `futil.math.sum(t, initial)`
+
+ given a table, get the sum of the values in the table. initial is the value from which to start counting.
+ if initial is nil and the table is empty, will return nil.
+
+* `futil.math.isum(i, initial)`
+
+ like the above, but given an iterator.
+
+## minetest-specific routines
+
+* `futil.add_groups(itemstring, new_groups)`
+
+ `new_groups` should be a table of groups to add to the item's existing groups
+
+* `futil.remove_groups(itemstring, ...)`
+
+ `...` should be a list of groups to remove from the item's existing groups
+
+* `futil.get_items_with_group(group)`
+
+ returns a list of itemstrings which belong to the specified group
+
+* `futil.get_location_string(inv)`
+
+ given an `InvRef`, get a location string suitable for use in formspec
+
+* `futil.resolve_item(item)`
+
+ given an itemstring or `ItemStack`, follows aliases until it finds the real item.
+ returns an itemstring.
+
+* `futil.items_equals(item1, item2)`
+
+ returns true if two itemstrings/stacks represent identical stacks.
+
+* `futil.get_blockpos(pos)`
+
+ converts a position vector into a blockpos
+
+* `futil.get_block_bounds(blockpos)`
+
+ gets the bound vectors of a blockpos
+
+* `futil.formspec_pos(pos)`
+
+ convert a position into a string suitable for use in formspecs
+
+* `futil.iterate_area(minp, maxp)`
+
+ creates an iterator for every point in the volume between minp and maxp
+
+* `futil.iterate_volume(pos, radius)`
+
+ like the above, given a position and radius (L∞ metric)
+
+* `futil.serialize(x)`
+
+ turns a simple lua data structure (e.g. a table no userdata or functions) into a string
+
+* `futil.deserialize(data)`
+
+ the reverse of the above. not safe; do not use w/ untrusted data
+
+* `futil.strip_translation(msg)`
+
+ strips minetest's translation escape sequences from a message
+
+* `futil.get_safe_short_description(item)`
+
+ gets a short description which won't contain unmatched translation escapes
+
+* `futil.escape_texture(texturestring)`
+
+ escapes a texture modifier, for use within another modifier
+
+* `futil.get_horizontal_speed(player)`
+
+ get's a player's horizontal speed.
+
+* `futil.is_on_ground(player)`
+
+ returns true if a player is standing on the ground.
+ NOTE: this is currently unfinished, and doesn't report correctly if a player is standing on things with complex
+ collision boxes which are rotated via `paramtype2="facedir"` or similar.
+
+### fake inventory
+this is useful for testing multiple actions on an inventory without having to worry about changing the inventory or
+reverting it. this is a better solution than a detached inventory, as actions on a detached inventory are still sent
+to clients. fake inventories support all the regular methods of a
+[minetest inventory object](https://github.com/minetest/minetest/blob/master/doc/lua_api.md#invref),
+with some additions.
+
+* `futil.FakeInventory()`
+
+ create a fake inventory.
+
+* `futil.FakeInventory.create_copy(inv)`
+
+ copy all the inventory lists from inv into a new fake inventory. will also create a copy of another fake inventory.
+
+* `futil.FakeInventory.room_for_all(inv, listname, items)`
+
+ create a copy of inv, then tests if all items in the list `items` can be inserted into listname.
+
+### globalstep
+
+implements common boilerplate for globalsteps which are intended to execute every so often.
+
+```lua
+futil.register_globalstep({
+ period = 1, -- the globalstep should be run every seconds
+ catchup = "single", -- whether to "catch up" if lag prevents the callback from running
+ -- if not specified, no catchup will be attempted.
+ -- if "single", the callback will be run at most once per server-step until we've caught up.
+ -- if "full", will re-run the callback within the current step until we've caught up.
+ func = function(dtime) end, -- code to execute
+})
+```
+
+### hud manager
+
+code to manage HUDs
+
+```lua
+local hud = futil.define_hud("my_hud", {
+ period = nil, -- if a number is given, will automatically update the hud for all players every seconds.
+ name_field = nil, -- which hud field to use to store an identifier. this should be a field not used by the given
+ -- hud_elem_type. defaults to "name", which is good for most types. waypoints are an exception.
+ get_hud_def = function(player) return {} end, -- return the expected hud definition for the player.
+ -- if nil is returned, the hud will be removed.
+ enabled_by_default = false, -- whether the hud should be enabled by default.
+})
+
+local player = minetest.get_player_by_name("flux")
+if hud:toggle_enabled(player) then
+ print("hud now enabled")
+else
+ print("hud now disabled")
+end
+
+print("hud is " .. (hud:is_enabled(player) and "enabled" or "disabled"))
+
+hud:update(player) -- calls hud.get_hud_def(player) and updates the players hud
+```
diff --git a/mods/futil/LICENSE.txt b/mods/futil/LICENSE.txt
new file mode 100644
index 00000000..b6ff27cc
--- /dev/null
+++ b/mods/futil/LICENSE.txt
@@ -0,0 +1,168 @@
+this license is for the code.
+any non-code media included in this repository is covered by the contents of MEDIA_LICENSE.txt.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/mods/futil/MEDIA_LICENSE.txt b/mods/futil/MEDIA_LICENSE.txt
new file mode 100644
index 00000000..7d4f96c5
--- /dev/null
+++ b/mods/futil/MEDIA_LICENSE.txt
@@ -0,0 +1,427 @@
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/mods/futil/README.md b/mods/futil/README.md
new file mode 100644
index 00000000..f7415259
--- /dev/null
+++ b/mods/futil/README.md
@@ -0,0 +1,5 @@
+# futil - flux's utility mod
+
+a bunch of simple lua routines and data structures. this is a library which is used by other mods, which doesn't change
+any behavior or add any features itself. it mostly provides the creator (flux) w/ a toolkit for solving common problems
+without having to re-implement the same code in multiple mods.
diff --git a/mods/futil/data_structures/bitarray.lua b/mods/futil/data_structures/bitarray.lua
new file mode 100644
index 00000000..71ffa7aa
--- /dev/null
+++ b/mods/futil/data_structures/bitarray.lua
@@ -0,0 +1,125 @@
+-- pack bits into doubles
+-- this is quite slow compared to how you'd do this in c or something.
+-- there's https://bitop.luajit.org/api.html but that seems limited to 32 bits. we can use the full 53 bit mantissa.
+
+local BITS_PER_NUMBER = 53
+
+local f = string.format
+
+local m_floor = math.floor
+local s_byte = string.byte
+local s_char = string.char
+
+local BitArray = futil.class1()
+
+function BitArray:_init(size_or_bitmap)
+ if type(size_or_bitmap) == "number" then
+ local data = {}
+ self._size = size_or_bitmap
+ for i = 1, math.ceil(size_or_bitmap / BITS_PER_NUMBER) do
+ data[i] = 0
+ end
+ self._data = data
+ elseif type(size_or_bitmap) == "table" then
+ if size_or_bitmap.is_a and size_or_bitmap:is_a(BitArray) then
+ self._size = size_or_bitmap._size
+ self._data = table.copy(size_or_bitmap._data)
+ end
+ end
+ if not self._data then
+ error("bitmap must be initialized w/ a size or another bitmap")
+ end
+end
+
+function BitArray:__eq(other)
+ if self._size ~= other._size then
+ return false
+ end
+ for i = 1, self._size do
+ if self._data[i] ~= other._data[i] then
+ return false
+ end
+ end
+ return true
+end
+
+local function get_bit(n, j)
+ n = n % (2 ^ j)
+ return m_floor(n / (2 ^ (j - 1))) == 1
+end
+
+function BitArray:get(k)
+ if type(k) ~= "number" then
+ return nil
+ elseif k <= 0 or k > self._size then
+ return nil
+ end
+ local i = math.ceil(k / BITS_PER_NUMBER)
+ local n = self._data[i]
+ local j = ((k - 1) % BITS_PER_NUMBER) + 1
+ return get_bit(n, j)
+end
+
+local function set_bit(n, j, v)
+ local current = get_bit(n, j)
+ if current == v then
+ return n
+ elseif v then
+ return n + (2 ^ (j - 1))
+ else
+ return n - (2 ^ (j - 1))
+ end
+end
+
+function BitArray:set(k, v)
+ if type(v) == "number" then
+ if v < 0 or v > 1 then
+ error(f("invalid argument %s", v))
+ end
+ v = v == 1
+ elseif type(v) ~= "boolean" then
+ error(f("invalid argument of type %s", type(v)))
+ end
+ local i = math.ceil(k / BITS_PER_NUMBER)
+ local n = self._data[i]
+ local j = ((k - 1) % BITS_PER_NUMBER) + 1
+ self._data[i] = set_bit(n, j, v)
+end
+
+function BitArray:serialize()
+ local data = self._data
+ local parts = {}
+ for i = 1, #data do
+ local datum = data[i]
+ parts[i] = s_char(datum % 256)
+ .. s_char(m_floor(datum / 256) % 256)
+ .. s_char(m_floor(datum / (256 ^ 2)) % 256)
+ .. s_char(m_floor(datum / (256 ^ 3)) % 256)
+ .. s_char(m_floor(datum / (256 ^ 4)) % 256)
+ .. s_char(m_floor(datum / (256 ^ 5)) % 256)
+ .. s_char(m_floor(datum / (256 ^ 6)) % 256)
+ end
+ return table.concat(parts, "")
+end
+
+function BitArray.deserialize(s)
+ if type(s) ~= "string" then
+ error(f("invalid argument of type %s", type(s)))
+ elseif #s % 7 ~= 0 then
+ error(f("invalid serialized string (wrong length)"))
+ end
+ local ba = BitArray(#s / 7)
+ local i = 1
+ for a = 1, #s, 7 do
+ local bs = {}
+ for j = 0, 6 do
+ bs[j + 1] = s_byte(s:sub(a + j, a + j))
+ end
+ ba._data[i] = bs[1]
+ + 256 * (bs[2] + 256 * (bs[3] + 256 * (bs[4] + 256 * (bs[5] + 256 * (bs[6] + 256 * bs[7])))))
+ i = i + 1
+ end
+ return ba
+end
+
+futil.BitArray = BitArray
diff --git a/mods/futil/data_structures/default_table.lua b/mods/futil/data_structures/default_table.lua
new file mode 100644
index 00000000..3aa5e9f0
--- /dev/null
+++ b/mods/futil/data_structures/default_table.lua
@@ -0,0 +1,11 @@
+-- https://www.lua.org/pil/13.4.3.html
+
+function futil.DefaultTable(initializer)
+ return setmetatable({}, {
+ __index = function(t, k)
+ local v = initializer(k)
+ t[k] = v
+ return v
+ end,
+ })
+end
diff --git a/mods/futil/data_structures/deque.lua b/mods/futil/data_structures/deque.lua
new file mode 100644
index 00000000..57c30313
--- /dev/null
+++ b/mods/futil/data_structures/deque.lua
@@ -0,0 +1,88 @@
+-- inspired by https://www.lua.org/pil/11.4.html
+
+local Deque = futil.class1()
+
+function Deque:_init(def)
+ self._a = 0
+ self._z = -1
+ self._m = def and def.max_size
+end
+
+function Deque:size()
+ return self._z - self._a + 1
+end
+
+function Deque:push_front(value)
+ local max_size = self._m
+ if max_size and (self._z - self._a + 1) >= max_size then
+ return false
+ end
+ local a = self._a - 1
+ self._a = a
+ self[a] = value
+ return true, a
+end
+
+function Deque:peek_front()
+ return self[self._a]
+end
+
+function Deque:pop_front()
+ local a = self._a
+ if a > self._z then
+ return nil
+ end
+ local value = self[a]
+ self[a] = nil
+ self._a = a + 1
+ return value
+end
+
+function Deque:push_back(value)
+ local max_size = self._m
+ if max_size and (self._z - self._a + 1) >= max_size then
+ return false
+ end
+ local z = self._z + 1
+ self._z = z
+ self[z] = value
+ return true, z
+end
+
+function Deque:peek_back()
+ return self[self._z]
+end
+
+function Deque:pop_back()
+ local z = self._z
+ if self._a > z then
+ return nil
+ end
+ local value = self[z]
+ self[z] = nil
+ self._z = z + 1
+ return value
+end
+
+-- this iterator is kinda wonky, and the behavior may be changed in the future.
+-- unexpected behavior may result from modifying a deque *while* iterating it.
+-- note that you *cannot* iterate the deque directly using `pairs()` because of e.g. "_a" and "_z"
+function Deque:iterate()
+ local i = self._a - 1
+ return function()
+ i = i + 1
+ return self[i]
+ end
+end
+
+function Deque:clear()
+ for k in pairs(self) do
+ if type(k) == "number" then
+ self[k] = nil
+ end
+ end
+ self._a = 0
+ self._z = -1
+end
+
+futil.Deque = Deque
diff --git a/mods/futil/data_structures/init.lua b/mods/futil/data_structures/init.lua
new file mode 100644
index 00000000..222dacad
--- /dev/null
+++ b/mods/futil/data_structures/init.lua
@@ -0,0 +1,7 @@
+futil.dofile("data_structures", "bitarray")
+futil.dofile("data_structures", "default_table")
+futil.dofile("data_structures", "deque")
+futil.dofile("data_structures", "pairing_heap")
+futil.dofile("data_structures", "point_search_tree")
+futil.dofile("data_structures", "set")
+futil.dofile("data_structures", "sparse_graph") -- requires default_table and set
diff --git a/mods/futil/data_structures/pairing_heap.lua b/mods/futil/data_structures/pairing_heap.lua
new file mode 100644
index 00000000..82d5947c
--- /dev/null
+++ b/mods/futil/data_structures/pairing_heap.lua
@@ -0,0 +1,156 @@
+-- https://en.wikipedia.org/wiki/Pairing_heap
+-- https://www.cs.cmu.edu/~sleator/papers/pairing-heaps.pdf
+-- https://www.cise.ufl.edu/~sahni/dsaaj/enrich/c13/pairing.htm
+
+local inf = math.huge
+
+local function add_child(node1, node2)
+ node2.parent = node1
+ node2.sibling = node1.child
+ node1.child = node2
+end
+
+local function meld(node1, node2)
+ if node1 == nil or node1.value == nil then
+ return node2
+ elseif node2 == nil or node2.value == nil then
+ return node1
+ elseif node1.priority > node2.priority then
+ add_child(node1, node2)
+ return node1
+ else
+ add_child(node2, node1)
+ return node2
+ end
+end
+
+local function merge_pairs(node)
+ if node.value == nil or not node.sibling then
+ return
+ end
+
+ local sibling = node.sibling
+ local siblingsibling = sibling.sibling
+
+ node.sibling = nil
+ sibling.sibling = nil
+
+ node = meld(node, sibling)
+
+ if siblingsibling then
+ return meld(node, merge_pairs(siblingsibling))
+ else
+ return node
+ end
+end
+
+local function cut(node)
+ local parent = node.parent
+
+ if parent.child == node then
+ parent.child = node.sibling
+ else
+ parent = parent.child
+ local sibling = parent.sibling
+ while sibling ~= node do
+ parent = sibling
+ sibling = parent.sibling
+ end
+ parent.sibling = node.sibling
+ end
+
+ node.parent = nil
+ node.sibling = nil
+end
+
+local function need_to_move(node, new_priority)
+ local cur_priority = node.priority
+ if cur_priority < new_priority then
+ -- priority increase, make sure we don't dominate our parent
+ local parent = node.parent
+ return (parent and new_priority > parent.priority)
+ elseif cur_priority > new_priority then
+ -- priority decrease, make sure our children don't dominate us
+ local child = node.child
+ while child and child ~= node do
+ if child.priority > new_priority then
+ return true
+ end
+ child = child.sibling
+ end
+ return false
+ else
+ return false
+ end
+end
+
+local PairingHeap = futil.class1()
+
+function PairingHeap:_new()
+ self._nodes_by_value = {}
+ self._size = 0
+end
+
+function PairingHeap:size()
+ return self._size
+end
+
+function PairingHeap:peek()
+ local hn = self._max_node
+
+ if not hn then
+ error("empty")
+ end
+
+ return hn.value, hn.priority
+end
+
+function PairingHeap:remove(value)
+ self:set_priority(value, inf)
+ return self:pop()
+end
+
+function PairingHeap:pop()
+ local max = self._max_node
+
+ if not max then
+ error("empty")
+ end
+
+ local child = max.child
+ if child then
+ self._max_node = merge_pairs(child)
+ end
+
+ local value = max._value
+
+ self._nodes_by_value[value] = nil
+ self._size = self._size - 1
+
+ return value
+end
+
+function PairingHeap:get_priority(value)
+ return self._nodes_by_value[value].priority
+end
+
+function PairingHeap:set_priority(value, priority)
+ local cur_node = self._nodes_by_value[value]
+ if cur_node then
+ local need_to = need_to_move(cur_node, priority)
+
+ if need_to then
+ cut(cur_node)
+ self._max_node = meld(cur_node, self._max_node)
+ else
+ cur_node.priority = priority
+ end
+ else
+ local node = { value = value, priority = priority }
+ self._nodes_by_value[value] = node
+ self._max_node = meld(self._max_node, node)
+ self._size = self._size + 1
+ end
+end
+
+futil.PairingHeap = PairingHeap
diff --git a/mods/futil/data_structures/point_search_tree.lua b/mods/futil/data_structures/point_search_tree.lua
new file mode 100644
index 00000000..2c328115
--- /dev/null
+++ b/mods/futil/data_structures/point_search_tree.lua
@@ -0,0 +1,268 @@
+--[[
+a data structure which can efficiently retrieve values within specific rectangular regions of 3d space.
+
+the closest relevant descriptions of this problem and solution are in the following:
+https://en.wikipedia.org/wiki/Min/max_kd-tree
+https://medium.com/omarelgabrys-blog/geometric-applications-of-bsts-e58f0a5019f3
+
+creation is O(n log n)
+finding objects in a region is O(m + log n) if there's m objects in the region.
+
+the hope here is that this will provide a faster alternative to `minetest.get_objects_in_area()`. that currently
+iterates over *all* active objects in the world, which can be slow when there's ten thousand or more of objects in the
+world, and you are only interested in a few of them. in particular, the your-land server usually has 5-8 thousand
+objects loaded at once, and can have hundreds of mobs calling `get_objects_in_area` every couple server steps. perftop
+definitively implicated this routine as being a major source of lag. the question, now, is what to do about it.
+
+the current implementation doesn't allow for insertion, deletion, or changes in location. if your points move around,
+you're gonna have to rebuild the whole tree from scratch, which currently limits the usefulness.
+
+TODO: read this and incorporate if applicable: https://arxiv.org/abs/1410.5420
+
+== footnotes about the algorithms ==
+currently, we're hard-coding the usage of the [median of medians](https://en.wikipedia.org/wiki/Median_of_medians)
+algorithm as the pivot strategy, as this resulted in unexpectedly dramatic improvements over random selection in
+some informal performance tests i did.
+
+== footnotes about results ==
+currently, performance can range from taking 1/20th of the time of the engine call, to 100 times as long. this
+makes me realize that this is worth pursuing, but probably this will need to be ported to c++ to consistently provide
+a benefit. but, i'll absolutely need to figure out a self-balancing strategy before that'll be appropriate.
+]]
+local sort = table.sort
+
+local in_area = vector.in_area
+ or function(pos, pmin, pmax)
+ local x, y, z = pos.x, pos.y, pos.z
+ return pmin.x <= x and x <= pmax.x and pmin.y <= y and y <= pmax.y and pmin.z <= z and z <= pmax.z
+ end
+
+local axes = { "x", "y", "z" }
+local POS = 1
+local VALUE = 2
+
+local Leaf = futil.class1()
+
+function Leaf:_init(pos_and_value)
+ self[POS] = pos_and_value[POS]
+ self[VALUE] = pos_and_value[VALUE]
+end
+
+local Node = futil.class1()
+
+function Node:_init(min, max, left, right)
+ self.min = min
+ self.max = max
+ self.left = left
+ self.right = right
+end
+
+local PointSearchTree = futil.class1()
+
+futil.min_median_max = {}
+
+function futil.min_median_max.sort(t, indexer)
+ if indexer then
+ sort(t, function(a, b)
+ return indexer(a) < indexer(b)
+ end)
+ else
+ sort(t)
+ end
+ return t[1], math.floor(#t / 2), t[#t]
+end
+
+function futil.min_median_max.gen_select(pivot_alg)
+ return function(t, indexer)
+ local median = futil.selection.select(t, pivot_alg, function(a, b)
+ return indexer(a) < indexer(b)
+ end)
+ local min = indexer(t[1])
+ local max = min
+ for i = 2, #t do
+ local v = indexer(t[i])
+ if v < min then
+ min = v
+ elseif v > max then
+ max = v
+ end
+ end
+ return min, median, max
+ end
+end
+
+local function bisect(pos_and_values, axis_i, min_median_max)
+ if #pos_and_values == 1 then
+ return Leaf(pos_and_values[1])
+ end
+
+ local axis = axes[axis_i]
+
+ local min, median, max = min_median_max(pos_and_values, function(i)
+ return i[POS][axis]
+ end)
+
+ local next_axis_i = (axis_i % #axes) + 1
+ return Node(
+ min,
+ max,
+ bisect({ unpack(pos_and_values, 1, median) }, next_axis_i, min_median_max),
+ bisect({ unpack(pos_and_values, median + 1) }, next_axis_i, min_median_max)
+ )
+end
+
+function PointSearchTree:_init(pos_and_values, min_median_max)
+ pos_and_values = pos_and_values or {}
+ min_median_max = min_median_max or futil.min_median_max.gen_select(futil.selection.pivot.median_of_medians)
+ self._len = #pos_and_values
+ if #pos_and_values > 0 then
+ self._root = bisect(pos_and_values, 1, min_median_max)
+ end
+end
+
+-- -DLUAJIT_ENABLE_LUA52COMPAT
+function PointSearchTree:__len()
+ return self._len
+end
+
+function PointSearchTree:dump()
+ local function getlines(node, axis_i)
+ local axis = axes[axis_i]
+ if not node then
+ return {}
+ elseif node:is_a(Leaf) then
+ return { minetest.pos_to_string(node[POS], 1) }
+ else
+ local lines = {}
+ for _, line in ipairs(getlines(node.left, (axis_i % #axes) + 1)) do
+ lines[#lines + 1] = string.format("%s=[%.1f,%.1f] %s", axis, node.min, node.max, line)
+ end
+ for _, line in ipairs(getlines(node.right, (axis_i % #axes) + 1)) do
+ lines[#lines + 1] = string.format("%s=[%.1f,%.1f] %s", axis, node.min, node.max, line)
+ end
+ return lines
+ end
+ end
+
+ return table.concat(getlines(self._root, 1), "\n")
+end
+
+local function make_iterator(pmin, pmax, predicate, accumulate)
+ local function iterate(node, axis_i)
+ local next_axis_i = (axis_i % 3) + 1
+ local next_axis = axes[next_axis_i]
+
+ local left = node.left
+ if left then
+ if left:is_a(Leaf) then
+ if predicate(left) then
+ accumulate(left)
+ end
+ elseif pmin[next_axis] <= left.max then
+ iterate(left, next_axis_i)
+ end
+ end
+
+ local right = node.right
+ if right then
+ if right:is_a(Leaf) then
+ if predicate(right) then
+ accumulate(right)
+ end
+ elseif right.min <= pmax[next_axis] then
+ iterate(right, next_axis_i)
+ end
+ end
+ end
+ return iterate
+end
+
+function PointSearchTree:iterate_values_in_area(pmin, pmax)
+ if not self._root then
+ return function() end
+ end
+
+ pmin, pmax = vector.sort(pmin, pmax)
+
+ if self._root.max < pmin.x or pmax.x < self._root.min then
+ return function() end
+ end
+
+ return coroutine.wrap(function()
+ make_iterator(pmin, pmax, function(leaf)
+ return in_area(leaf[POS], pmin, pmax)
+ end, function(leaf)
+ coroutine.yield(leaf[POS], leaf[VALUE])
+ end)(self._root, 1)
+ end)
+end
+
+function PointSearchTree:get_values_in_area(pmin, pmax)
+ local via = {}
+ if not self._root then
+ return via
+ end
+
+ pmin, pmax = vector.sort(pmin, pmax)
+
+ if self._root.max < pmin.x or pmax.x < self._root.min then
+ return via
+ end
+
+ make_iterator(pmin, pmax, function(leaf)
+ return in_area(leaf[POS], pmin, pmax)
+ end, function(leaf)
+ via[#via + 1] = leaf[VALUE]
+ end)(self._root, 1)
+
+ return via
+end
+
+function PointSearchTree:iterate_values_inside_radius(center, radius)
+ if not self._root then
+ return function() end
+ end
+
+ local pmin = vector.subtract(center, radius)
+ local pmax = vector.add(center, radius)
+
+ if self._root.max < pmin.x or pmax.x < self._root.min then
+ return function() end
+ end
+
+ local v_distance = vector.distance
+
+ return coroutine.wrap(function()
+ make_iterator(pmin, pmax, function(leaf)
+ return v_distance(center, leaf[POS]) <= radius
+ end, function(leaf)
+ coroutine.yield(leaf[POS], leaf[VALUE])
+ end)(self._root, 1)
+ end)
+end
+
+function PointSearchTree:get_values_inside_radius(center, radius)
+ local vir = {}
+ if not self._root then
+ return vir
+ end
+
+ local pmin = vector.subtract(center, radius)
+ local pmax = vector.add(center, radius)
+
+ if self._root.max < pmin.x or pmax.x < self._root.min then
+ return vir
+ end
+
+ local v_distance = vector.distance
+
+ make_iterator(pmin, pmax, function(leaf)
+ return v_distance(center, leaf[POS]) <= radius
+ end, function(leaf)
+ vir[#vir + 1] = leaf[VALUE]
+ end)(self._root, 1)
+
+ return vir
+end
+
+futil.PointSearchTree = PointSearchTree
diff --git a/mods/futil/data_structures/set.lua b/mods/futil/data_structures/set.lua
new file mode 100644
index 00000000..269cf606
--- /dev/null
+++ b/mods/futil/data_structures/set.lua
@@ -0,0 +1,267 @@
+-- based more-or-less on python's set
+
+local f = string.format
+
+local Set = futil.class1()
+
+local function is_a_set(thing)
+ return type(thing) == "table" and type(thing.is_a) == "function" and thing:is_a(Set)
+end
+
+function Set:_init(t_or_i)
+ self._size = 0
+ self._set = {}
+ if t_or_i then
+ if type(t_or_i.is_a) == "function" then
+ if t_or_i:is_a(Set) then
+ self._set = table.copy(t_or_i._set)
+ self._size = t_or_i._size
+ else
+ for v in t_or_i:iterate() do
+ self:add(v)
+ end
+ end
+ elseif type(t_or_i) == "table" then
+ for i = 1, #t_or_i do
+ self:add(t_or_i[i])
+ end
+ elseif type(t_or_i) == "function" or getmetatable(t_or_i).__call then
+ for v in t_or_i do
+ self:add(v)
+ end
+ else
+ error(f("unknown argument of type %s", type(t_or_i)))
+ end
+ end
+end
+
+-- turn a table like {foo=true, bar=true} into a Set
+function Set.convert(t)
+ local set = Set()
+ set._set = t
+ set._size = futil.table.size(t)
+ return set
+end
+
+-- -DLUAJIT_ENABLE_LUA52COMPAT
+function Set:__len()
+ return self._size
+end
+
+function Set:len()
+ return self._size
+end
+
+function Set:size()
+ return self._size
+end
+
+function Set:is_empty()
+ return self._size == 0
+end
+
+function Set:__tostring()
+ local elements = {}
+ for element in pairs(self._set) do
+ elements[#elements + 1] = f("%q", element)
+ end
+ return f("Set({%s})", table.concat(elements, ", "))
+end
+
+function Set:__eq(other)
+ if not is_a_set(other) then
+ return false
+ end
+ for k in pairs(self._set) do
+ if not other._set[k] then
+ return false
+ end
+ end
+ return self._size == other._size
+end
+
+function Set:contains(element)
+ return self._set[element] == true
+end
+
+function Set:add(element)
+ if not self:contains(element) then
+ self._set[element] = true
+ self._size = self._size + 1
+ end
+end
+
+function Set:remove(element)
+ if not self:contains(element) then
+ error(f("set does not contain %s", element))
+ end
+ self._set[element] = nil
+ self._size = self._size - 1
+end
+
+function Set:discard(element)
+ if self:contains(element) then
+ self._set[element] = nil
+ self._size = self._size - 1
+ end
+end
+
+function Set:clear()
+ self._set = {}
+ self._size = 0
+end
+
+function Set:iterate()
+ return futil.table.ikeys(self._set)
+end
+
+function Set:intersects(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ local smaller, bigger
+ if other:size() < self:size() then
+ smaller = other
+ bigger = self
+ else
+ smaller = self
+ bigger = other
+ end
+ for element in smaller:iterate() do
+ if bigger:contains(element) then
+ return true
+ end
+ end
+ return false
+end
+
+function Set:isdisjoint(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ local smaller, bigger
+ if other:size() < self:size() then
+ smaller = other
+ bigger = self
+ else
+ smaller = self
+ bigger = other
+ end
+ for element in smaller:iterate() do
+ if bigger:contains(element) then
+ return false
+ end
+ end
+ return true
+end
+
+function Set:issubset(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ if self:size() > other:size() then
+ return false
+ end
+ for element in self:iterate() do
+ if not other:contains(element) then
+ return false
+ end
+ end
+ return true
+end
+
+function Set:__le(other)
+ return self:issubset(other)
+end
+
+function Set:__lt(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ if self:size() >= other:size() then
+ return false
+ end
+ for element in self:iterate() do
+ if not other:contains(element) then
+ return false
+ end
+ end
+ return true
+end
+
+function Set:issuperset(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ return other:issubset(self)
+end
+
+function Set:update(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ for element in other:iterate() do
+ self:add(element)
+ end
+end
+
+function Set:union(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ local union = Set(self)
+ union:update(other)
+ return union
+end
+
+function Set:__add(other)
+ return self:union(other)
+end
+
+function Set:intersection_update(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ for element in self:iterate() do
+ if not other:contains(element) then
+ self:remove(element)
+ end
+ end
+end
+
+function Set:intersection(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ local intersection = Set()
+ for element in self:iterate() do
+ if other:contains(element) then
+ intersection:add(element)
+ end
+ end
+ return intersection
+end
+
+function Set:difference_update(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ for element in other:iterate() do
+ self:discard(element)
+ end
+end
+
+function Set:difference(other)
+ if not is_a_set(other) then
+ other = Set(other)
+ end
+ local difference = Set(self)
+ difference:difference_update(other)
+ return difference
+end
+
+function Set:__sub(other)
+ return self:difference(other)
+end
+
+futil.Set = Set
diff --git a/mods/futil/data_structures/sparse_graph.lua b/mods/futil/data_structures/sparse_graph.lua
new file mode 100644
index 00000000..b5acc20d
--- /dev/null
+++ b/mods/futil/data_structures/sparse_graph.lua
@@ -0,0 +1,32 @@
+local f = string.format
+
+local SparseGraph = futil.class1()
+
+function SparseGraph:_init(size)
+ self._size = size or 0
+ self._adj_by_vertex = futil.DefaultTable(function()
+ return futil.Set()
+ end)
+end
+
+function SparseGraph:size()
+ return self._size
+end
+
+function SparseGraph:add_vertex()
+ self._size = self._size + 1
+end
+
+function SparseGraph:add_edge(a, b)
+ assert(1 <= a and a <= self._size, f("invalid vertex a %s", a))
+ assert(1 <= b and b <= self._size, f("invalid vertex b %s", b))
+ self._adj_by_vertex[a]:add(b)
+end
+
+function SparseGraph:has_edge(a, b)
+ assert(1 <= a and a <= self._size, f("invalid vertex a %s", a))
+ assert(1 <= b and b <= self._size, f("invalid vertex b %s", b))
+ return self._adj_by_vertex[a]:contains(b)
+end
+
+futil.SparseGraph = SparseGraph
diff --git a/mods/futil/init.lua b/mods/futil/init.lua
new file mode 100644
index 00000000..136d3552
--- /dev/null
+++ b/mods/futil/init.lua
@@ -0,0 +1,11 @@
+fmod.check_version({ year = 2023, month = 7, day = 14 }) -- async dofile
+
+futil = fmod.create()
+
+futil.dofile("util", "init")
+futil.dofile("data_structures", "init") -- depends on util
+futil.dofile("minetest", "init") -- depends on util and data_structures
+
+if INIT == "game" then
+ futil.async_dofile("init")
+end
diff --git a/mods/futil/minetest/box.lua b/mods/futil/minetest/box.lua
new file mode 100644
index 00000000..472ff446
--- /dev/null
+++ b/mods/futil/minetest/box.lua
@@ -0,0 +1,186 @@
+-- box definition below node boxes: https://github.com/minetest/minetest/blob/master/doc/lua_api.md#node-boxes
+
+local x1 = 1
+local y1 = 2
+local z1 = 3
+local x2 = 4
+local y2 = 5
+local z2 = 6
+
+function futil.boxes_intersect(box1, box2)
+ return not (
+ (box1[x2] < box2[x1] or box2[x2] < box1[x1])
+ or (box1[y2] < box2[y1] or box2[y2] < box1[y1])
+ or (box1[z2] < box2[z1] or box2[z2] < box1[z1])
+ )
+end
+
+function futil.box_offset(box, number_or_vector)
+ if type(number_or_vector) == "number" then
+ return {
+ box[1] + number_or_vector,
+ box[2] + number_or_vector,
+ box[3] + number_or_vector,
+ box[4] + number_or_vector,
+ box[5] + number_or_vector,
+ box[6] + number_or_vector,
+ }
+ else
+ return {
+ box[1] + number_or_vector.x,
+ box[2] + number_or_vector.y,
+ box[3] + number_or_vector.z,
+ box[4] + number_or_vector.x,
+ box[5] + number_or_vector.y,
+ box[6] + number_or_vector.z,
+ }
+ end
+end
+
+function futil.is_box(box)
+ if type(box) == "table" and #box == 6 then
+ for _, x in ipairs(box) do
+ if type(x) ~= "number" then
+ return false
+ end
+ end
+ return box[1] <= box[4] and box[2] <= box[5] and box[3] <= box[6]
+ end
+ return false
+end
+
+function futil.is_boxes(boxes)
+ if type(boxes) ~= "table" or #boxes == 0 then
+ return false
+ end
+
+ for _, box in ipairs(boxes) do
+ if not futil.is_box(box) then
+ return false
+ end
+ end
+
+ return true
+end
+
+-- given a set of boxes, return a single box that covers all of them
+function futil.cover_boxes(boxes)
+ if not futil.is_boxes(boxes) then
+ return { 0, 0, 0, 0, 0, 0 }
+ end
+
+ local cover = boxes[1]
+ for i = 2, #boxes do
+ for j = 1, 3 do
+ cover[j] = math.min(cover[j], boxes[i][j])
+ end
+ for j = 4, 6 do
+ cover[j] = math.max(cover[j], boxes[i][j])
+ end
+ end
+
+ return cover
+end
+
+--[[
+for nodes:
+ A nodebox is defined as any of:
+
+ {
+ -- A normal cube; the default in most things
+ type = "regular"
+ }
+ {
+ -- A fixed box (or boxes) (facedir param2 is used, if applicable)
+ type = "fixed",
+ fixed = box OR {box1, box2, ...}
+ }
+ {
+ -- A variable height box (or boxes) with the top face position defined
+ -- by the node parameter 'leveled = ', or if 'paramtype2 == "leveled"'
+ -- by param2.
+ -- Other faces are defined by 'fixed = {}' as with 'type = "fixed"'.
+ type = "leveled",
+ fixed = box OR {box1, box2, ...}
+ }
+ {
+ -- A box like the selection box for torches
+ -- (wallmounted param2 is used, if applicable)
+ type = "wallmounted",
+ wall_top = box,
+ wall_bottom = box,
+ wall_side = box
+ }
+ {
+ -- A node that has optional boxes depending on neighboring nodes'
+ -- presence and type. See also `connects_to`.
+ type = "connected",
+ fixed = box OR {box1, box2, ...}
+ connect_top = box OR {box1, box2, ...}
+ connect_bottom = box OR {box1, box2, ...}
+ connect_front = box OR {box1, box2, ...}
+ connect_left = box OR {box1, box2, ...}
+ connect_back = box OR {box1, box2, ...}
+ connect_right = box OR {box1, box2, ...}
+ -- The following `disconnected_*` boxes are the opposites of the
+ -- `connect_*` ones above, i.e. when a node has no suitable neighbor
+ -- on the respective side, the corresponding disconnected box is drawn.
+ disconnected_top = box OR {box1, box2, ...}
+ disconnected_bottom = box OR {box1, box2, ...}
+ disconnected_front = box OR {box1, box2, ...}
+ disconnected_left = box OR {box1, box2, ...}
+ disconnected_back = box OR {box1, box2, ...}
+ disconnected_right = box OR {box1, box2, ...}
+ disconnected = box OR {box1, box2, ...} -- when there is *no* neighbor
+ disconnected_sides = box OR {box1, box2, ...} -- when there are *no*
+ -- neighbors to the sides
+ }
+
+for objects:
+ collisionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, -- default
+ selectionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, rotate = false },
+ -- { xmin, ymin, zmin, xmax, ymax, zmax } in nodes from object position.
+ -- Collision boxes cannot rotate, setting `rotate = true` on it has no effect.
+ -- If not set, the selection box copies the collision box, and will also not rotate.
+ -- If `rotate = false`, the selection box will not rotate with the object itself, remaining fixed to the axes.
+ -- If `rotate = true`, it will match the object's rotation and any attachment rotations.
+ -- Raycasts use the selection box and object's rotation, but do *not* obey attachment rotations
+]]
+
+futil.default_collision_box = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }
+
+function futil.node_collision_box_to_object_collisionbox(collision_box)
+ if type(collision_box) ~= "table" then
+ return table.copy(futil.default_collision_box)
+ elseif collision_box.type == "regular" then
+ return table.copy(futil.default_collision_box)
+ elseif collision_box.type == "fixed" or collision_box.type == "leveled" or collision_box.type == "connected" then
+ if futil.is_box(collision_box.fixed) then
+ return collision_box.fixed
+ elseif futil.is_boxes(collision_box.fixed) then
+ return futil.cover_boxes(collision_box.fixed)
+ else
+ return table.copy(futil.default_collision_box)
+ end
+ elseif collision_box.type == "wallmounted" then
+ local boxes = {}
+ if collision_box.wall_top then
+ table.insert(boxes, collision_box.wall_top)
+ end
+ if collision_box.wall_bottom then
+ table.insert(boxes, collision_box.wall_bottom)
+ end
+ if collision_box.wall_side then
+ table.insert(boxes, collision_box.wall_side)
+ end
+ return futil.cover_boxes(boxes)
+ else
+ return table.copy(futil.default_collision_box)
+ end
+end
+
+function futil.node_selection_box_to_object_selectionbox(selection_box, rotate)
+ local selectionbox = futil.node_collision_box_to_object_collisionbox(selection_box)
+ selectionbox.rotate = rotate or false
+ return selectionbox
+end
diff --git a/mods/futil/minetest/dedupe.lua b/mods/futil/minetest/dedupe.lua
new file mode 100644
index 00000000..2b59843e
--- /dev/null
+++ b/mods/futil/minetest/dedupe.lua
@@ -0,0 +1,31 @@
+-- utilities to dedupe messages
+local last_by_func = {}
+function futil.dedupe(func, ...)
+ local cur = { ... }
+ if futil.equals(last_by_func[func], cur) then
+ return
+ end
+ last_by_func[func] = cur
+ return func(...)
+end
+
+local last_by_player_name_by_func = futil.DefaultTable(function()
+ return {}
+end)
+function futil.dedupe_by_player(func, player, ...)
+ local cur = { ... }
+ local last_by_player_name = last_by_player_name_by_func[func]
+ local player_name
+
+ if type(player) == "string" then
+ player_name = player
+ else
+ player_name = player:get_player_name()
+ end
+
+ if futil.equals(last_by_player_name[player_name], cur) then
+ return
+ end
+ last_by_player_name[player_name] = cur
+ return func(player, ...)
+end
diff --git a/mods/futil/minetest/dump.lua b/mods/futil/minetest/dump.lua
new file mode 100644
index 00000000..671169c0
--- /dev/null
+++ b/mods/futil/minetest/dump.lua
@@ -0,0 +1,111 @@
+-- adapted from https://github.com/minetest/minetest/blob/master/builtin/common/misc_helpers.lua
+-- but tables are sorted
+
+local function sorter(a, b)
+ local ta, tb = type(a), type(b)
+ if ta ~= tb then
+ return ta < tb
+ end
+ if ta == "function" or ta == "userdata" or ta == "thread" or ta == "table" then
+ return tostring(a) < tostring(b)
+ else
+ return a < b
+ end
+end
+
+local keywords = {
+ ["and"] = true,
+ ["break"] = true,
+ ["do"] = true,
+ ["else"] = true,
+ ["elseif"] = true,
+ ["end"] = true,
+ ["false"] = true,
+ ["for"] = true,
+ ["function"] = true,
+ ["goto"] = true, -- Lua 5.2
+ ["if"] = true,
+ ["in"] = true,
+ ["local"] = true,
+ ["nil"] = true,
+ ["not"] = true,
+ ["or"] = true,
+ ["repeat"] = true,
+ ["return"] = true,
+ ["then"] = true,
+ ["true"] = true,
+ ["until"] = true,
+ ["while"] = true,
+}
+
+local function is_valid_identifier(str)
+ if not str:find("^[a-zA-Z_][a-zA-Z0-9_]*$") or keywords[str] then
+ return false
+ end
+ return true
+end
+
+local function basic_dump(o)
+ local tp = type(o)
+ if tp == "number" then
+ return tostring(o)
+ elseif tp == "string" then
+ return string.format("%q", o)
+ elseif tp == "boolean" then
+ return tostring(o)
+ elseif tp == "nil" then
+ return "nil"
+ -- Uncomment for full function dumping support.
+ -- Not currently enabled because bytecode isn't very human-readable and
+ -- dump's output is intended for humans.
+ --elseif tp == "function" then
+ -- return string.format("loadstring(%q)", string.dump(o))
+ elseif tp == "userdata" then
+ return tostring(o)
+ else
+ return string.format("<%s>", tp)
+ end
+end
+
+function futil.dump(o, indent, nested, level)
+ local t = type(o)
+ if not level and t == "userdata" then
+ -- when userdata (e.g. player) is passed directly, print its metatable:
+ return "userdata metatable: " .. futil.dump(getmetatable(o))
+ end
+ if t ~= "table" then
+ return basic_dump(o)
+ end
+
+ -- Contains table -> true/nil of currently nested tables
+ nested = nested or {}
+ if nested[o] then
+ return ""
+ end
+ nested[o] = true
+ indent = indent or "\t"
+ level = level or 1
+
+ local ret = {}
+ local dumped_indexes = {}
+ for i, v in ipairs(o) do
+ ret[#ret + 1] = futil.dump(v, indent, nested, level + 1)
+ dumped_indexes[i] = true
+ end
+ for k, v in futil.table.pairs_by_key(o, sorter) do
+ if not dumped_indexes[k] then
+ if type(k) ~= "string" or not is_valid_identifier(k) then
+ k = "[" .. futil.dump(k, indent, nested, level + 1) .. "]"
+ end
+ v = futil.dump(v, indent, nested, level + 1)
+ ret[#ret + 1] = k .. " = " .. v
+ end
+ end
+ nested[o] = nil
+ if indent ~= "" then
+ local indent_str = "\n" .. string.rep(indent, level)
+ local end_indent_str = "\n" .. string.rep(indent, level - 1)
+ return string.format("{%s%s%s}", indent_str, table.concat(ret, "," .. indent_str), end_indent_str)
+ end
+ return "{" .. table.concat(ret, ", ") .. "}"
+end
diff --git a/mods/futil/minetest/fake_inventory.lua b/mods/futil/minetest/fake_inventory.lua
new file mode 100644
index 00000000..096f7124
--- /dev/null
+++ b/mods/futil/minetest/fake_inventory.lua
@@ -0,0 +1,271 @@
+local FakeInventory = futil.class1()
+
+local function copy_list(list)
+ local copy = {}
+ for i = 1, #list do
+ copy[i] = ItemStack(list[i])
+ end
+ return copy
+end
+
+function FakeInventory:_init()
+ self._lists = {}
+end
+
+function FakeInventory.create_copy(inv)
+ local fake_inv = FakeInventory()
+ for listname, contents in pairs(inv:get_lists()) do
+ fake_inv:set_size(listname, inv:get_size(listname))
+ fake_inv:set_width(listname, inv:get_width(listname))
+ fake_inv:set_list(listname, contents)
+ end
+ return fake_inv
+end
+
+function FakeInventory.room_for_all(inv, listname, items)
+ local fake_inv = FakeInventory.create_copy(inv)
+ for i = 1, #items do
+ local item = items[i]
+ local remainder = fake_inv:add_item(listname, item)
+ if not remainder:is_empty() then
+ return false
+ end
+ end
+ return true
+end
+
+function FakeInventory:is_empty(listname)
+ local list = self._lists[listname]
+ if not list then
+ return true
+ end
+ for _, stack in ipairs(list) do
+ if not stack:is_empty() then
+ return false
+ end
+ end
+ return true
+end
+
+function FakeInventory:get_size(listname)
+ local list = self._lists[listname]
+ if not list then
+ return 0
+ end
+ return #list
+end
+
+function FakeInventory:set_size(listname, size)
+ if size == 0 then
+ self._lists[listname] = nil
+ return
+ end
+
+ local list = self._lists[listname] or {}
+
+ while #list < size do
+ list[#list + 1] = ItemStack()
+ end
+
+ for i = size + 1, #list do
+ list[i] = nil
+ end
+
+ self._lists[listname] = list
+end
+
+function FakeInventory:get_width(listname)
+ local list = self._lists[listname] or {}
+ return list.width or 0
+end
+
+function FakeInventory:set_width(listname, width)
+ local list = self._lists[listname] or {}
+ list.width = width
+ self._lists[listname] = list
+end
+
+function FakeInventory:get_stack(listname, i)
+ local list = self._lists[listname]
+ if not list or i > #list then
+ return ItemStack()
+ end
+ return ItemStack(list[i])
+end
+
+function FakeInventory:set_stack(listname, i, stack)
+ local list = self._lists[listname]
+ if not list or i > #list then
+ return
+ end
+ list[i] = ItemStack(stack)
+end
+
+function FakeInventory:get_list(listname)
+ local list = self._lists[listname]
+ if not list then
+ return
+ end
+ return copy_list(list)
+end
+
+function FakeInventory:set_list(listname, list)
+ local ourlist = self._lists[listname]
+ if not ourlist then
+ return
+ end
+
+ for i = 1, #ourlist do
+ ourlist[i] = ItemStack(list[i])
+ end
+end
+
+function FakeInventory:get_lists()
+ local lists = {}
+ for listname, list in pairs(self._lists) do
+ lists[listname] = copy_list(list)
+ end
+ return lists
+end
+
+function FakeInventory:set_lists(lists)
+ for listname, list in pairs(lists) do
+ self:set_list(listname, list)
+ end
+end
+
+-- add item somewhere in list, returns leftover `ItemStack`.
+function FakeInventory:add_item(listname, new_item)
+ local list = self._lists[listname]
+ new_item = ItemStack(new_item)
+ if new_item:is_empty() or not list or #list == 0 then
+ return new_item
+ end
+
+ -- first try to find if it could be added to some existing items
+ for _, our_stack in ipairs(list) do
+ if not our_stack:is_empty() then
+ new_item = our_stack:add_item(new_item)
+ if new_item:is_empty() then
+ return new_item
+ end
+ end
+ end
+
+ -- then try to add it to empty slots
+ for _, our_stack in ipairs(list) do
+ new_item = our_stack:add_item(new_item)
+ if new_item:is_empty() then
+ break
+ end
+ end
+
+ return new_item
+end
+
+-- returns `true` if the stack of items can be fully added to the list
+function FakeInventory:room_for_item(listname, stack)
+ local list = self._lists[listname]
+ if not list then
+ return false
+ end
+
+ stack = ItemStack(stack)
+ local copy = copy_list(list)
+ for _, our_stack in ipairs(copy) do
+ stack = our_stack:add_item(stack)
+ if stack:is_empty() then
+ break
+ end
+ end
+
+ return stack:is_empty()
+end
+
+-- take as many items as specified from the list, returns the items that were actually removed (as an `ItemStack`)
+-- note that any item metadata is ignored, so attempting to remove a specific unique item this way will likely remove
+-- the wrong one -- to do that use `set_stack` with an empty `ItemStack`.
+function FakeInventory:remove_item(listname, stack)
+ local removed = ItemStack()
+ stack = ItemStack(stack)
+
+ local list = self._lists[listname]
+ if not list or stack:is_empty() then
+ return removed
+ end
+
+ local name = stack:get_name()
+ local count_remaining = stack:get_count()
+ local taken = 0
+
+ for i = #list, 1, -1 do
+ local our_stack = list[i]
+ if our_stack:get_name() == name then
+ local n = our_stack:take_item(count_remaining):get_count()
+ count_remaining = count_remaining - n
+ taken = taken + n
+ end
+
+ if count_remaining == 0 then
+ break
+ end
+ end
+
+ stack:set_count(taken)
+
+ return stack
+end
+
+-- returns `true` if the stack of items can be fully taken from the list.
+-- If `match_meta` is false, only the items' names are compared (default: `false`).
+function FakeInventory:contains_item(listname, stack, match_meta)
+ local list = self._lists[listname]
+ if not list then
+ return false
+ end
+
+ stack = ItemStack(stack)
+
+ if match_meta then
+ local name = stack:get_name()
+ local wear = stack:get_wear()
+ local meta = stack:get_meta()
+ local needed_count = stack:get_count()
+
+ for _, our_stack in ipairs(list) do
+ if our_stack:get_name() == name and our_stack:get_wear() == wear and our_stack:get_meta():equals(meta) then
+ local n = our_stack:peek_item(needed_count):get_count()
+ needed_count = needed_count - n
+ end
+ if needed_count == 0 then
+ break
+ end
+ end
+
+ return needed_count == 0
+ else
+ local name = stack:get_name()
+ local needed_count = stack:get_count()
+
+ for _, our_stack in ipairs(list) do
+ if our_stack:get_name() == name then
+ local n = our_stack:peek_item(needed_count):get_count()
+ needed_count = needed_count - n
+ end
+ if needed_count == 0 then
+ break
+ end
+ end
+
+ return needed_count == 0
+ end
+end
+
+function FakeInventory:get_location()
+ return {
+ type = "undefined",
+ subtype = "FakeInventory",
+ }
+end
+
+futil.FakeInventory = FakeInventory
diff --git a/mods/futil/minetest/globalstep.lua b/mods/futil/minetest/globalstep.lua
new file mode 100644
index 00000000..b9c4d219
--- /dev/null
+++ b/mods/futil/minetest/globalstep.lua
@@ -0,0 +1,88 @@
+--[[
+execute the globalstep after the specified period. the actual amount of time elapsed is passed to the function,
+and will always be greater than or equal to the length of the period.
+futil.register_globalstep({
+ period = 1,
+ func = function(elapsed) end,
+})
+
+execute the globalstep after the specified period. if more time has elapsed than the period specified, the remainder
+will be counted against the next cycle, allowing the execution to "catch up". the expected time between executions
+will tend towards the specified period. IMPORTANT: do not specify a period which is less than the length of the
+dedicated server step.
+futil.register_globalstep({
+ period = 1,
+ catchup = "single"
+ func = function(period) end,
+})
+
+execute the globalstep after the specified period. if more time has elapsed than the period specified, the callback
+will be executed repeatedly until the elapsed time is less than the period, and the remainder will still be counted
+against the next cycle.
+futil.register_globalstep({
+ period = 1,
+ catchup = "full"
+ func = function(period) end,
+})
+
+this is just a light wrapper over a normal minetest globalstep callback, and is only provided for completeness.
+futil.register_globalstep({
+ func = function(dtime) end,
+})
+]]
+local f = string.format
+
+local dedicated_server_step = tonumber(minetest.settings:get("dedicated_server_step")) or 0.09
+
+function futil.register_globalstep(def)
+ if def.period then
+ local elapsed = 0
+ if def.catchup == "full" then
+ assert(def.period > 0, "full catchup will cause an infinite loop if period is 0")
+ minetest.register_globalstep(function(dtime)
+ elapsed = elapsed + dtime
+ if elapsed < def.period then
+ return
+ end
+ elapsed = elapsed - def.period
+ def.func(def.period)
+ while elapsed > def.period do
+ elapsed = elapsed - def.period
+ def.func(def.period)
+ end
+ end)
+ elseif def.catchup == "single" or def.catchup == true then
+ assert(
+ def.period >= dedicated_server_step,
+ f(
+ "if period (%s) is less than dedicated_server_step (%s), single catchup will never fully catch up.",
+ def.period,
+ dedicated_server_step
+ )
+ )
+ minetest.register_globalstep(function(dtime)
+ elapsed = elapsed + dtime
+ if elapsed < def.period then
+ return
+ end
+ elapsed = elapsed - def.period
+ def.func(def.period)
+ end)
+ else
+ -- no catchup, just reset
+ minetest.register_globalstep(function(dtime)
+ elapsed = elapsed + dtime
+ if elapsed < def.period then
+ return
+ end
+ def.func(elapsed)
+ elapsed = 0
+ end)
+ end
+ else
+ -- we do nothing useful
+ minetest.register_globalstep(function(dtime)
+ def.func(dtime)
+ end)
+ end
+end
diff --git a/mods/futil/minetest/group.lua b/mods/futil/minetest/group.lua
new file mode 100644
index 00000000..76ebf8a4
--- /dev/null
+++ b/mods/futil/minetest/group.lua
@@ -0,0 +1,61 @@
+function futil.add_groups(itemstring, new_groups)
+ local def = minetest.registered_items[itemstring]
+ if not def then
+ error(("attempting to override unknown item %s"):format(itemstring))
+ end
+ local groups = table.copy(def.groups or {})
+ futil.table.set_all(groups, new_groups)
+ minetest.override_item(itemstring, { groups = groups })
+end
+
+function futil.remove_groups(itemstring, ...)
+ local def = minetest.registered_items[itemstring]
+ if not def then
+ error(("attempting to override unknown item %s"):format(itemstring))
+ end
+ local groups = table.copy(def.groups or {})
+ for _, group in ipairs({ ... }) do
+ groups[group] = nil
+ end
+ minetest.override_item(itemstring, { groups = groups })
+end
+
+function futil.get_items_with_group(group)
+ if futil.items_by_group then
+ return futil.items_by_group[group] or {}
+ end
+
+ local items = {}
+
+ for item in pairs(minetest.registered_items) do
+ if minetest.get_item_group(item, group) > 0 then
+ table.insert(items, item)
+ end
+ end
+
+ return items
+end
+
+function futil.get_item_with_group(group)
+ return futil.get_items_with_group(group)[1]
+end
+
+function futil.generate_items_by_group()
+ local items_by_group = {}
+
+ for item, def in pairs(minetest.registered_items) do
+ for group in pairs(def.groups or {}) do
+ local items = items_by_group[group] or {}
+ table.insert(items, item)
+ items_by_group[group] = items
+ end
+ end
+
+ futil.items_by_group = items_by_group
+end
+
+if INIT == "game" then
+ -- it's not 100% safe to assume items and groups can't change after this point.
+ -- but please, don't do that :\
+ minetest.register_on_mods_loaded(futil.generate_items_by_group)
+end
diff --git a/mods/futil/minetest/hud_ephemeral.lua b/mods/futil/minetest/hud_ephemeral.lua
new file mode 100644
index 00000000..c19ecea8
--- /dev/null
+++ b/mods/futil/minetest/hud_ephemeral.lua
@@ -0,0 +1,100 @@
+local f = string.format
+
+local current_id = 0
+
+local function get_next_id()
+ current_id = current_id + 1
+ return current_id
+end
+
+local EphemeralHud = futil.class1()
+
+function EphemeralHud:_init(player, hud_def)
+ self._player_name = player:get_player_name()
+ if (hud_def.type or hud_def.hud_elem_type) == "waypoint" then
+ self._id_field = "text2"
+ else
+ self._id_field = "name"
+ end
+ self._id = f("ephemeral_hud:%s:%i", hud_def[self._id_field] or "", get_next_id())
+ hud_def[self._id_field] = self._id
+ self._hud_id = player:hud_add(hud_def)
+end
+
+function EphemeralHud:is_active()
+ if not self._hud_id then
+ return false
+ end
+ local player = minetest.get_player_by_name(self._player_name)
+ if not player then
+ self._hud_id = nil
+ return false
+ end
+ local hud_def = player:hud_get(self._hud_id)
+ if not hud_def then
+ self._hud_id = nil
+ return false
+ end
+ if hud_def[self._id_field] ~= self._id then
+ self._hud_id = nil
+ return false
+ end
+
+ return true
+end
+
+function EphemeralHud:change(new_hud_def)
+ if not self:is_active() then
+ futil.log("warning", "[ephemeral hud] cannot update an inactive hud")
+ return false
+ end
+ local player = minetest.get_player_by_name(self._player_name)
+ local old_hud_def = player:hud_get(self._hud_id)
+ for key, value in pairs(new_hud_def) do
+ if key == "hud_elem_type" then
+ if value ~= (old_hud_def.type or old_hud_def.hud_elem_type) then
+ error("cannot change hud_elem_type")
+ end
+ elseif key == "type" then
+ if value ~= (old_hud_def.type or old_hud_def.hud_elem_type) then
+ error("cannot change type")
+ end
+ elseif key == self._id_field then
+ if value ~= self._id then
+ error(f("cannot change the value of %q, as this is an ID", self._id_field))
+ end
+ else
+ if key == "position" or key == "scale" or key == "align" or key == "offset" then
+ value = futil.vector.v2f_to_float_32(value)
+ end
+
+ if not futil.equals(old_hud_def[key], value) then
+ player:hud_change(self._hud_id, key, value)
+ end
+ end
+ end
+ return true
+end
+
+function EphemeralHud:remove()
+ if not self:is_active() then
+ futil.log("warning", "[ephemeral hud] cannot remove an inactive hud")
+ return false
+ end
+ local player = minetest.get_player_by_name(self._player_name)
+ player:hud_remove(self._hud_id)
+ self._hud_id = nil
+end
+
+futil.EphemeralHud = EphemeralHud
+
+-- note: sometimes HUDs can fail to get created. if so, the HUD object returned here will be "inactive".
+function futil.create_ephemeral_hud(player, timeout, hud_def)
+ local hud = EphemeralHud(player, hud_def)
+ minetest.after(timeout, function()
+ if hud:is_active() then
+ hud:remove()
+ end
+ end)
+ return hud
+end
diff --git a/mods/futil/minetest/hud_manager.lua b/mods/futil/minetest/hud_manager.lua
new file mode 100644
index 00000000..04d9ab06
--- /dev/null
+++ b/mods/futil/minetest/hud_manager.lua
@@ -0,0 +1,172 @@
+--[[
+local my_hud = futil.define_hud("my_mod:my_hud", {
+ period = 1,
+ catchup = nil, -- not currently supported
+ name_field = nil, -- in case you want to override the id field
+ enabled_by_default = nil, -- set to true to enable by default
+ get_hud_data = function()
+ -- get data that's identical for all players
+ -- passed to get_hud_def
+ end,
+ get_hud_def = function(player, data)
+ return {}
+ end,
+})
+
+my_hud:toggle_enabled(player)
+]]
+
+local f = string.format
+
+local ManagedHud = futil.class1()
+
+function ManagedHud:_init(hud_name, def)
+ self.name = hud_name
+
+ self._name_field = def.name_field or ((def.type or def.hud_elem_type) == "waypoint" and "text2" or "name")
+ self._period = def.period
+ self._get_hud_data = def.get_hud_data
+ self._get_hud_def = def.get_hud_def
+ self._enabled_by_default = def.enabled_by_default
+
+ self._hud_id_by_player_name = {}
+
+ self._hud_enabled_key = f("hud_manager:%s_enabled", hud_name)
+ self._hud_name = f("hud_manager:%s", hud_name)
+end
+
+function ManagedHud:is_enabled(player)
+ local meta = player:get_meta()
+ local value = meta:get(self._hud_enabled_key)
+ if value == nil then
+ return self._enabled_by_default
+ else
+ return minetest.is_yes(value)
+ end
+end
+
+function ManagedHud:set_enabled(player, value)
+ local meta = player:get_meta()
+ if minetest.is_yes(value) then
+ meta:set_string(self._hud_enabled_key, "y")
+ else
+ meta:set_string(self._hud_enabled_key, "n")
+ end
+end
+
+function ManagedHud:toggle_enabled(player)
+ local meta = player:get_meta()
+ local enabled = not self:is_enabled(player)
+ if enabled then
+ meta:set_string(self._hud_enabled_key, "y")
+ else
+ meta:set_string(self._hud_enabled_key, "n")
+ end
+ return enabled
+end
+
+function ManagedHud:update(player, data)
+ local is_enabled = self:is_enabled(player)
+ local player_name = player:get_player_name()
+ local hud_id = self._hud_id_by_player_name[player_name]
+ local old_hud_def
+ if hud_id then
+ old_hud_def = player:hud_get(hud_id)
+ if old_hud_def and old_hud_def[self._name_field] == self._hud_name then
+ if not is_enabled then
+ player:hud_remove(hud_id)
+ self._hud_id_by_player_name[player_name] = nil
+ return
+ end
+ else
+ -- hud_id is bad
+ hud_id = nil
+ old_hud_def = nil
+ end
+ end
+
+ if is_enabled then
+ local new_hud_def = self._get_hud_def(player, data)
+ if not new_hud_def then
+ if hud_id then
+ player:hud_remove(hud_id)
+ self._hud_id_by_player_name[player_name] = nil
+ end
+ return
+ elseif new_hud_def[self._name_field] and new_hud_def[self._name_field] ~= self._hud_name then
+ error(f("you cannot specify the value of the %q field, this is generated", self._name_field))
+ end
+
+ if old_hud_def then
+ for k, v in pairs(new_hud_def) do
+ if k == "position" or k == "scale" or k == "align" or k == "offset" then
+ v = futil.vector.v2f_to_float_32(v)
+ end
+
+ if not futil.equals(old_hud_def[k], v) and k ~= "type" and k ~= "hud_elem_type" then
+ player:hud_change(hud_id, k, v)
+ end
+ end
+ else
+ new_hud_def[self._name_field] = self._hud_name
+ hud_id = player:hud_add(new_hud_def)
+ end
+ end
+
+ self._hud_id_by_player_name[player_name] = hud_id
+end
+
+futil.defined_huds = {}
+
+function futil.define_hud(hud_name, def)
+ if futil.defined_huds[hud_name] then
+ error(f("hud %s already exists", hud_name))
+ end
+ local hud = ManagedHud(hud_name, def)
+ futil.defined_huds[hud_name] = hud
+ return hud
+end
+
+-- TODO: register_hud instead of define_hud, plus alias the old
+
+local function update_hud(hud, players)
+ local data
+ if hud._get_hud_data then
+ local is_any_enabled = false
+ for i = 1, #players do
+ if hud:is_enabled(players[i]) then
+ is_any_enabled = true
+ break
+ end
+ end
+ if is_any_enabled then
+ data = hud._get_hud_data()
+ end
+ end
+ for i = 1, #players do
+ hud:update(players[i], data)
+ end
+end
+
+-- TODO refactor to use futil.register_globalstep for each hud, to allow use of catchup mechanics
+-- ... why would HUD updates need catchup mechanics?
+local elapsed_by_hud_name = {}
+minetest.register_globalstep(function(dtime)
+ local players = minetest.get_connected_players()
+ if #players == 0 then
+ return
+ end
+ for hud_name, hud in pairs(futil.defined_huds) do
+ if hud._period then
+ local elapsed = (elapsed_by_hud_name[hud_name] or 0) + dtime
+ if elapsed < hud._period then
+ elapsed_by_hud_name[hud_name] = elapsed
+ else
+ elapsed_by_hud_name[hud_name] = 0
+ update_hud(hud, players)
+ end
+ else
+ update_hud(hud, players)
+ end
+ end
+end)
diff --git a/mods/futil/minetest/image.lua b/mods/futil/minetest/image.lua
new file mode 100644
index 00000000..65ced329
--- /dev/null
+++ b/mods/futil/minetest/image.lua
@@ -0,0 +1,171 @@
+local f = string.format
+
+local function is_vertical_frames(animation)
+ return (animation.type == "vertical_frames" and animation.aspect_w and animation.aspect_h)
+end
+
+local function get_single_frame(animation, image_name)
+ return ("[combine:%ix%i^[noalpha^[colorize:#FFF:255^[mask:%s"):format(
+ animation.aspect_w,
+ animation.aspect_h,
+ image_name
+ )
+end
+
+local function is_sheet_2d(animation)
+ return (animation.type == "sheet_2d" and animation.frames_w and animation.frames_h)
+end
+
+local function get_sheet_2d(animation, image_name)
+ return ("%s^[sheet:%ix%i:0,0"):format(image_name, animation.frames_w, animation.frames_h)
+end
+
+local get_image_from_tile = futil.memoize1(function(tile)
+ if type(tile) == "string" then
+ return tile
+ elseif type(tile) == "table" then
+ local image_name
+
+ if type(tile.image) == "string" then
+ image_name = tile.image
+ elseif type(tile.name) == "string" then
+ image_name = tile.name
+ end
+
+ if image_name then
+ local animation = tile.animation
+ if animation then
+ if is_vertical_frames(animation) then
+ return get_single_frame(animation, image_name)
+ elseif is_sheet_2d(animation) then
+ return get_sheet_2d(animation, image_name)
+ end
+ end
+
+ return image_name
+ end
+ end
+
+ return "unknown_node.png"
+end)
+
+local function get_image_cube(tiles)
+ if #tiles >= 6 then
+ return minetest.inventorycube(
+ get_image_from_tile(tiles[1] or "no_texture.png"),
+ get_image_from_tile(tiles[6] or "no_texture.png"),
+ get_image_from_tile(tiles[3] or "no_texture.png")
+ )
+ elseif #tiles == 5 then
+ return minetest.inventorycube(
+ get_image_from_tile(tiles[1] or "no_texture.png"),
+ get_image_from_tile(tiles[5] or "no_texture.png"),
+ get_image_from_tile(tiles[3] or "no_texture.png")
+ )
+ elseif #tiles == 4 then
+ return minetest.inventorycube(
+ get_image_from_tile(tiles[1] or "no_texture.png"),
+ get_image_from_tile(tiles[4] or "no_texture.png"),
+ get_image_from_tile(tiles[3] or "no_texture.png")
+ )
+ elseif #tiles == 3 then
+ return minetest.inventorycube(
+ get_image_from_tile(tiles[1] or "no_texture.png"),
+ get_image_from_tile(tiles[3] or "no_texture.png"),
+ get_image_from_tile(tiles[3] or "no_texture.png")
+ )
+ elseif #tiles == 2 then
+ return minetest.inventorycube(
+ get_image_from_tile(tiles[1] or "no_texture.png"),
+ get_image_from_tile(tiles[2] or "no_texture.png"),
+ get_image_from_tile(tiles[2] or "no_texture.png")
+ )
+ elseif #tiles == 1 then
+ return minetest.inventorycube(
+ get_image_from_tile(tiles[1] or "no_texture.png"),
+ get_image_from_tile(tiles[1] or "no_texture.png"),
+ get_image_from_tile(tiles[1] or "no_texture.png")
+ )
+ end
+
+ return "no_texture.png"
+end
+
+local function is_normal_node(drawtype)
+ return (
+ drawtype == "normal"
+ or drawtype == "allfaces"
+ or drawtype == "allfaces_optional"
+ or drawtype == "glasslike"
+ or drawtype == "glasslike_framed"
+ or drawtype == "glasslike_framed_optional"
+ or drawtype == "liquid"
+ )
+end
+
+local cache = {}
+
+function futil.get_wield_image(item)
+ if type(item) == "string" then
+ item = ItemStack(item)
+ end
+
+ if item:is_empty() then
+ return "blank.png"
+ end
+
+ local def = item:get_definition()
+ if not def then
+ return "unknown_item.png"
+ end
+
+ local itemstring = item:to_string()
+ local cached = cache[itemstring]
+ if cached then
+ return cached
+ end
+
+ local meta = item:get_meta()
+ local color = meta:get("color") or def.color
+
+ local image = "no_texture.png"
+
+ if def.wield_image and def.wield_image ~= "" then
+ local parts = { def.wield_image }
+ if color then
+ parts[#parts + 1] = f("[colorize:%s:alpha", futil.escape_texture(color))
+ end
+ if def.wield_overlay then
+ parts[#parts + 1] = def.wield_overlay
+ end
+ image = table.concat(parts, "^")
+ elseif def.inventory_image and def.inventory_image ~= "" then
+ local parts = { def.inventory_image }
+ if color then
+ parts[#parts + 1] = f("[colorize:%s:alpha", futil.escape_texture(color))
+ end
+ if def.inventory_overlay then
+ parts[#parts + 1] = def.inventory_overlay
+ end
+ image = table.concat(parts, "^")
+ elseif def.type == "node" then
+ if def.drawtype == "nodebox" or def.drawtype == "mesh" then
+ image = "no_texture.png"
+ else
+ local tiles = def.tiles
+ if type(tiles) == "string" then
+ image = get_image_from_tile(tiles)
+ elseif type(tiles) == "table" then
+ if is_normal_node(def.drawtype) then
+ image = get_image_cube(tiles)
+ else
+ image = get_image_from_tile(tiles[1])
+ end
+ end
+ end
+ end
+
+ cache[itemstring] = image
+
+ return image
+end
diff --git a/mods/futil/minetest/init.lua b/mods/futil/minetest/init.lua
new file mode 100644
index 00000000..7f6ae42a
--- /dev/null
+++ b/mods/futil/minetest/init.lua
@@ -0,0 +1,24 @@
+futil.dofile("minetest", "box")
+futil.dofile("minetest", "dedupe")
+futil.dofile("minetest", "dump")
+futil.dofile("minetest", "fake_inventory")
+futil.dofile("minetest", "group")
+futil.dofile("minetest", "image")
+futil.dofile("minetest", "item")
+futil.dofile("minetest", "registration")
+futil.dofile("minetest", "serialization")
+futil.dofile("minetest", "set_look_dir")
+futil.dofile("minetest", "strip_translation")
+futil.dofile("minetest", "texture")
+futil.dofile("minetest", "time")
+futil.dofile("minetest", "vector")
+
+if INIT == "game" then
+ futil.dofile("minetest", "globalstep")
+ futil.dofile("minetest", "hud_ephemeral")
+ futil.dofile("minetest", "hud_manager")
+ futil.dofile("minetest", "inventory")
+ futil.dofile("minetest", "object")
+ futil.dofile("minetest", "object_properties")
+ futil.dofile("minetest", "raycast")
+end
diff --git a/mods/futil/minetest/inventory.lua b/mods/futil/minetest/inventory.lua
new file mode 100644
index 00000000..46781d9b
--- /dev/null
+++ b/mods/futil/minetest/inventory.lua
@@ -0,0 +1,40 @@
+function futil.get_location_string(inv)
+ local location = inv:get_location()
+ if location.type == "node" then
+ return ("nodemeta:%i,%i,%i"):format(location.pos.x, location.pos.y, location.pos.z)
+ elseif location.type == "player" then
+ return ("player:%s"):format(location.name)
+ elseif location.type == "detached" then
+ return ("detached:%s"):format(location.name)
+ else
+ error(("unexpected location? %s"):format(dump(location)))
+ end
+end
+
+-- InvRef:remove_item() ignores metadata, and sometimes that's wrong
+-- for logic, see InventoryList::removeItem in inventory.cpp
+function futil.remove_item_with_meta(inv, listname, itemstack)
+ itemstack = ItemStack(itemstack)
+ if itemstack:is_empty() then
+ return ItemStack()
+ end
+ local removed = ItemStack()
+ for i = 1, inv:get_size(listname) do
+ local invstack = inv:get_stack(listname, i)
+ if
+ invstack:get_name() == itemstack:get_name()
+ and invstack:get_wear() == itemstack:get_wear()
+ and invstack:get_meta() == itemstack:get_meta()
+ then
+ local still_to_remove = itemstack:get_count() - removed:get_count()
+ local leftover = removed:add_item(invstack:take_item(still_to_remove))
+ -- if we've requested to remove more than the stack size, ignore the limit
+ removed:set_count(removed:get_count() + leftover:get_count())
+ inv:set_stack(listname, i, invstack)
+ if removed:get_count() == itemstack:get_count() then
+ break
+ end
+ end
+ end
+ return removed
+end
diff --git a/mods/futil/minetest/item.lua b/mods/futil/minetest/item.lua
new file mode 100644
index 00000000..81a6b222
--- /dev/null
+++ b/mods/futil/minetest/item.lua
@@ -0,0 +1,133 @@
+local f = string.format
+
+-- if allow_unregistered is false or absent, if the original item or its alias is not a registered item, will return nil
+function futil.resolve_item(item_or_string, allow_unregistered)
+ local item_stack = ItemStack(item_or_string)
+ local name = item_stack:get_name()
+
+ local seen = { [name] = true }
+
+ local alias = minetest.registered_aliases[name]
+ while alias do
+ name = alias
+ seen[name] = true
+ alias = minetest.registered_aliases[name]
+ if seen[alias] then
+ error(f("alias cycle on %s", name))
+ end
+ end
+
+ if minetest.registered_items[name] or allow_unregistered then
+ item_stack:set_name(name)
+ return item_stack:to_string()
+ end
+end
+
+function futil.resolve_itemstack(item_or_string)
+ return ItemStack(futil.resolve_item(item_or_string, true))
+end
+
+if ItemStack().equals then
+ -- https://github.com/minetest/minetest/pull/12771
+ function futil.items_equals(item1, item2)
+ item1 = type(item1) == "userdata" and item1 or ItemStack(item1)
+ item2 = type(item2) == "userdata" and item2 or ItemStack(item2)
+
+ return item1 == item2
+ end
+else
+ local equals = futil.equals
+
+ function futil.items_equals(item1, item2)
+ item1 = type(item1) == "userdata" and item1 or ItemStack(item1)
+ item2 = type(item2) == "userdata" and item2 or ItemStack(item2)
+
+ return equals(item1:to_table(), item2:to_table())
+ end
+end
+
+-- TODO: probably this should have a 3nd argument to handle tool and tool_group stuff
+function futil.get_primary_drop(stack, filter)
+ stack = ItemStack(stack)
+
+ local name = stack:get_name()
+ local meta = stack:get_meta()
+ local palette_index = tonumber(meta:get_int("palette_index"))
+ local def = stack:get_definition()
+
+ if palette_index then
+ -- https://github.com/mt-mods/unifieddyes/blob/36c8bb5f5b8a0485225d2547c8978291ff710291/api.lua#L70-L90
+ local del_color
+
+ if def.paramtype2 == "color" and palette_index == 240 and def.palette == "unifieddyes_palette_extended.png" then
+ del_color = true
+ elseif
+ def.paramtype2 == "colorwallmounted"
+ and palette_index == 0
+ and def.palette == "unifieddyes_palette_colorwallmounted.png"
+ then
+ del_color = true
+ elseif
+ def.paramtype2 == "colorfacedir"
+ and palette_index == 0
+ and string.find(def.palette, "unifieddyes_palette_")
+ then
+ del_color = true
+ end
+
+ if del_color then
+ meta:set_string("palette_index", "")
+ palette_index = nil
+ end
+ end
+
+ local drop = def.drop
+
+ if drop == nil then
+ stack:set_count(1)
+ return stack
+ elseif drop == "" then
+ return nil
+ elseif type(drop) == "string" then
+ drop = ItemStack(drop)
+ drop:set_count(1)
+ return drop
+ elseif type(drop) == "table" then
+ local most_common_item
+ local inherit_color = false
+ local rarity = math.huge
+
+ if not drop.items then
+ error(f("unexpected drop table for %s: %s", stack:to_string(), dump(drop)))
+ end
+
+ for _, items in ipairs(drop.items) do
+ if (items.rarity or 1) < rarity then
+ for item in ipairs(items.items) do
+ if (not filter) or filter(item) then
+ most_common_item = item
+ inherit_color = items.inherit_color or false
+ rarity = items.rarity
+ break
+ end
+ end
+ end
+ end
+
+ if not most_common_item then
+ return
+ end
+
+ most_common_item = ItemStack(most_common_item)
+ most_common_item:set_count(1)
+
+ if inherit_color and palette_index then
+ local meta2 = most_common_item:get_meta()
+ meta2:set_int("palette_index", palette_index)
+ end
+
+ return most_common_item
+ else
+ error(f("invalid drop of %s? %q", dump(name, drop)))
+ end
+end
diff --git a/mods/futil/minetest/object.lua b/mods/futil/minetest/object.lua
new file mode 100644
index 00000000..b05ee555
--- /dev/null
+++ b/mods/futil/minetest/object.lua
@@ -0,0 +1,200 @@
+local v_new = vector.new
+
+-- if object is attached, get the velocity of the object it is attached to
+function futil.get_velocity(object)
+ local parent = object:get_attach()
+ while parent do
+ object = parent
+ parent = object:get_attach()
+ end
+ return object:get_velocity()
+end
+
+function futil.get_horizontal_speed(object)
+ local velocity = futil.get_velocity(object)
+ velocity.y = 0
+ return vector.length(velocity)
+end
+
+local function insert_connected(boxes, something)
+ if futil.is_box(something) then
+ table.insert(boxes, something)
+ elseif futil.is_boxes(something) then
+ table.insert_all(boxes, something)
+ end
+end
+
+local function get_boxes(cb)
+ if not cb then
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ end
+
+ if cb.type == "regular" then
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ elseif cb.type == "fixed" then
+ if futil.is_box(cb.fixed) then
+ return { cb.fixed }
+ elseif futil.is_boxes(cb.fixed) then
+ return cb.fixed
+ else
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ end
+ elseif cb.type == "leveled" then
+ -- TODO: have to check param2
+ if futil.is_box(cb.fixed) then
+ return { cb.fixed }
+ elseif futil.is_boxes(cb.fixed) then
+ return cb.fixed
+ else
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ end
+ elseif cb.type == "wallmounted" then
+ -- TODO: have to check param2? or?
+ local boxes = {}
+
+ if futil.is_box(cb.wall_top) then
+ table.insert(boxes, cb.wall_top)
+ end
+ if futil.is_box(cb.wall_bottom) then
+ table.insert(boxes, cb.wall_bottom)
+ end
+ if futil.is_box(cb.wall_side) then
+ table.insert(boxes, cb.wall_side)
+ end
+
+ if #boxes > 0 then
+ return boxes
+ else
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ end
+ elseif cb.type == "connected" then
+ -- TODO: very very complicated to check, just fudge and add everything
+ local boxes = {}
+
+ insert_connected(boxes, cb.fixed)
+ insert_connected(boxes, cb.connect_top)
+ insert_connected(boxes, cb.connect_bottom)
+ insert_connected(boxes, cb.connect_front)
+ insert_connected(boxes, cb.connect_left)
+ insert_connected(boxes, cb.connect_back)
+ insert_connected(boxes, cb.connect_right)
+ insert_connected(boxes, cb.disconnected_top)
+ insert_connected(boxes, cb.disconnected_bottom)
+ insert_connected(boxes, cb.disconnected_front)
+ insert_connected(boxes, cb.disconnected_left)
+ insert_connected(boxes, cb.disconnected_back)
+ insert_connected(boxes, cb.disconnected_right)
+ insert_connected(boxes, cb.disconnected)
+ insert_connected(boxes, cb.disconnected_sides)
+
+ if #boxes > 0 then
+ return boxes
+ else
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ end
+ end
+
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+end
+
+local function get_collision_boxes(node)
+ local node_def = minetest.registered_nodes[node.name]
+
+ if not node_def then
+ -- unknown nodes are regular solid nodes
+ return { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ end
+
+ if not node_def.walkable then
+ return {}
+ end
+
+ local boxes
+ if node_def.collision_box then
+ boxes = get_boxes(node_def.collision_box)
+ elseif node_def.drawtype == "nodebox" then
+ boxes = get_boxes(node_def.node_box)
+ else
+ boxes = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } }
+ end
+
+ --[[
+ if node_def.paramtype2 == "facedir" then
+ -- TODO: re-orient boxes
+ end
+ ]]
+
+ return boxes
+end
+
+local function is_pos_on_ground(feet_pos, player_box)
+ local node = minetest.get_node(feet_pos)
+ local node_boxes = get_collision_boxes(node)
+
+ for _, node_box in ipairs(node_boxes) do
+ local actual_node_box = futil.box_offset(node_box, feet_pos)
+ if futil.boxes_intersect(actual_node_box, player_box) then
+ return true
+ end
+ end
+
+ return false
+end
+
+function futil.is_on_ground(player)
+ local p_pos = player:get_pos()
+ local cb = player:get_properties().collisionbox
+
+ -- collect the positions of the nodes below the player's feet
+ local feet_poss = {
+ v_new(math.round(p_pos.x + cb[1]), math.ceil(p_pos.y + cb[2] - 0.5), math.round(p_pos.z + cb[3])),
+ v_new(math.round(p_pos.x + cb[1]), math.ceil(p_pos.y + cb[2] - 0.5), math.round(p_pos.z + cb[6])),
+ v_new(math.round(p_pos.x + cb[4]), math.ceil(p_pos.y + cb[2] - 0.5), math.round(p_pos.z + cb[3])),
+ v_new(math.round(p_pos.x + cb[4]), math.ceil(p_pos.y + cb[2] - 0.5), math.round(p_pos.z + cb[6])),
+ }
+
+ for _, feet_pos in ipairs(feet_poss) do
+ if is_pos_on_ground(feet_pos, futil.box_offset(cb, p_pos)) then
+ return true
+ end
+ end
+
+ return false
+end
+
+function futil.get_object_center(object)
+ local pos = object:get_pos()
+ if not pos then
+ return
+ end
+ local cb = object:get_properties().collisionbox
+ return v_new(pos.x + (cb[1] + cb[4]) / 2, pos.y + (cb[2] + cb[5]) / 2, pos.z + (cb[3] + cb[6]) / 2)
+end
+
+function futil.is_player(obj)
+ return minetest.is_player(obj) and not obj.is_fake_player
+end
+
+function futil.is_valid_object(obj)
+ return obj and type(obj.get_pos) == "function" and vector.check(obj:get_pos())
+end
+
+-- this is meant to be able to get the HP of any object, including "immortal" ones whose health is managed themselves
+-- it is *NOT* complete - i've got no idea where every mob API stores its hp.
+-- "health" is mobs_redo (which is actually redundant with `:get_hp()` because they're not actually immortal.
+-- "hp" is mobkit (and petz, which comes with its own fork of mobkit), and also creatura.
+function futil.get_hp(obj)
+ if not futil.is_valid_object(obj) then
+ -- not an object or dead
+ return 0
+ end
+ local ent = obj:get_luaentity()
+ if ent and (type(ent.hp) == "number" or type(ent.health) == "number") then
+ return ent.hp or ent.health
+ end
+ local armor_groups = obj:get_armor_groups()
+ if (armor_groups["immortal"] or 0) == 0 then
+ return obj:get_hp()
+ end
+ return math.huge -- presumably actually immortal
+end
diff --git a/mods/futil/minetest/object_properties.lua b/mods/futil/minetest/object_properties.lua
new file mode 100644
index 00000000..754bb2a5
--- /dev/null
+++ b/mods/futil/minetest/object_properties.lua
@@ -0,0 +1,158 @@
+local f = string.format
+
+local iall = futil.functional.iall
+local map = futil.map
+
+local in_bounds = futil.math.in_bounds
+
+local is_integer = futil.math.is_integer
+local is_number = futil.is_number
+local is_string = futil.is_string
+local is_table = futil.is_table
+
+local function valid_box(value)
+ if value == nil then
+ return true
+ elseif not is_table(value) then
+ return false
+ elseif #value ~= 6 then
+ return false
+ else
+ return iall(map(is_number, value))
+ end
+end
+
+local function valid_visual_size(value)
+ if not is_table(value) then
+ return false
+ end
+
+ local z_type = type(value.z)
+ return is_number(value.x) and is_integer(value.y) and (z_type == "number" or z_type == nil)
+end
+
+local function valid_textures(value)
+ if not is_table(value) then
+ return false
+ end
+
+ return iall(map(is_string, value))
+end
+
+local function valid_color_spec(value)
+ local t = type(value)
+ if t == "string" then
+ -- TODO: we could check for valid values, but that's ... tedious
+ return true
+ elseif t == "table" then
+ local is_number_ = is_number
+ local is_integer_ = is_integer
+ local in_bounds_ = in_bounds
+ local x = value.x
+ local y = value.y
+ local z = value.z
+ local a = value.a
+
+ return (
+ is_number_(x)
+ and in_bounds_(0, x, 255)
+ and is_integer_(x)
+ and is_number_(y)
+ and in_bounds_(0, y, 255)
+ and is_integer_(y)
+ and is_number_(z)
+ and in_bounds_(0, z, 255)
+ and is_integer_(z)
+ and (a == nil or (is_number_(a) and in_bounds_(0, a, 255) and is_integer_(a)))
+ )
+ end
+
+ return false
+end
+
+local function valid_colors(value)
+ if not is_table(value) then
+ return false
+ end
+
+ return iall(map(valid_color_spec, value))
+end
+
+local function valid_spritediv(value)
+ if not is_table(value) then
+ return false
+ end
+
+ local x = value.x
+ local y = value.y
+
+ return is_number(x) and is_integer(x) and is_number(y) and is_number(y)
+end
+
+local function valid_automatic_face_movement_dir(value)
+ return value == false or is_number(value)
+end
+
+local function valid_hp_max(value)
+ return is_number(value) and is_integer(value) and in_bounds(1, value, 65535)
+end
+
+local object_property = {
+ visual = "string",
+ visual_size = valid_visual_size,
+ mesh = "string",
+ textures = valid_textures,
+ colors = valid_colors,
+ use_texture_alpha = "boolean",
+ spritediv = valid_spritediv,
+ initial_sprite_basepos = valid_spritediv,
+ is_visible = "boolean",
+ automatic_rotate = "number",
+ automatic_face_movement_dir = valid_automatic_face_movement_dir,
+ automatic_face_movement_max_rotation_per_sec = "number",
+ backface_culling = "number",
+ glow = "number",
+ damage_texture_modifier = "string",
+ shaded = "boolean",
+
+ hp_max = valid_hp_max,
+ physical = "boolean",
+ pointable = "boolean",
+ collide_with_objects = "boolean",
+ collisionbox = valid_box,
+ selectionbox = valid_box,
+
+ makes_footstep_sound = "boolean",
+
+ stepheight = "number",
+
+ nametag = "string",
+ nametag_color = valid_color_spec,
+ nametag_bgcolor = valid_color_spec,
+
+ infotext = "string",
+
+ static_save = "boolean",
+
+ show_on_minimap = "boolean",
+}
+
+function futil.is_property_key(key)
+ return object_property[key] ~= nil
+end
+
+function futil.is_valid_property_value(key, value)
+ local kind = object_property[key]
+
+ if not kind then
+ return false
+ end
+
+ if type(kind) == "string" then
+ return type(value) == kind
+ elseif type(kind) == "function" then
+ return kind(value)
+ else
+ error(f("coding error in futil for key %q", key))
+ end
+end
diff --git a/mods/futil/minetest/raycast.lua b/mods/futil/minetest/raycast.lua
new file mode 100644
index 00000000..e50a42a8
--- /dev/null
+++ b/mods/futil/minetest/raycast.lua
@@ -0,0 +1,30 @@
+-- before 5.9, raycasts can miss objects they should hit if the cast is too short
+-- see https://github.com/minetest/minetest/issues/14337
+function futil.safecast(start, stop, objects, liquids, margin)
+ margin = margin or 5
+ local ray = stop - start
+ local ray_length = ray:length()
+ if ray_length == 0 then
+ return function() end
+ elseif ray_length >= margin then
+ return Raycast(start, stop, objects, liquids)
+ end
+
+ local actual_stop = start + ray:normalize() * margin
+ local raycast = Raycast(start, actual_stop, objects, liquids)
+ local stopped = false
+ return function()
+ if stopped then
+ return
+ end
+ local pt = raycast()
+ if pt then
+ local ip = pt.intersection_point
+ if (ip - start):length() > ray_length then
+ stopped = true
+ return
+ end
+ return pt
+ end
+ end
+end
diff --git a/mods/futil/minetest/registration.lua b/mods/futil/minetest/registration.lua
new file mode 100644
index 00000000..c17d5ee5
--- /dev/null
+++ b/mods/futil/minetest/registration.lua
@@ -0,0 +1,15 @@
+function futil.make_registration()
+ local t = {}
+ local registerfunc = function(func)
+ t[#t + 1] = func
+ end
+ return t, registerfunc
+end
+
+function futil.make_registration_reverse()
+ local t = {}
+ local registerfunc = function(func)
+ table.insert(t, 1, func)
+ end
+ return t, registerfunc
+end
diff --git a/mods/futil/minetest/serialization.lua b/mods/futil/minetest/serialization.lua
new file mode 100644
index 00000000..37951ecd
--- /dev/null
+++ b/mods/futil/minetest/serialization.lua
@@ -0,0 +1,87 @@
+local f = string.format
+
+local deserialize = minetest.deserialize
+
+local pairs_by_key = futil.table.pairs_by_key
+
+function futil.serialize(x)
+ if type(x) == "number" or type(x) == "boolean" or type(x) == "nil" then
+ return tostring(x)
+ elseif type(x) == "string" then
+ return f("%q", x)
+ elseif type(x) == "table" then
+ local parts = {}
+ for k, v in pairs_by_key(x) do
+ table.insert(parts, f("[%s] = %s", futil.serialize(k), futil.serialize(v)))
+ end
+ return f("{%s}", table.concat(parts, ", "))
+ else
+ error(f("can't serialize type %s", type(x)))
+ end
+end
+
+function futil.deserialize(data)
+ return deserialize(f("return %s", data))
+end
+
+function futil.serialize_invlist(inv, listname)
+ local itemstrings = {}
+ local list = inv:get_list(listname)
+
+ if not list then
+ error(f("couldn't find list %s of %s", listname, minetest.write_json(inv:get_location())))
+ end
+
+ for _, stack in ipairs(list) do
+ table.insert(itemstrings, stack:to_string())
+ end
+
+ return futil.serialize(itemstrings)
+end
+
+function futil.deserialize_invlist(serialized_list, inv, listname)
+ if not inv:is_empty(listname) then
+ error(("trying to deserialize into a non-empty list %s (%s)"):format(listname, serialized_list))
+ end
+
+ local itemstrings = futil.deserialize(serialized_list) or minetest.parse_json(serialized_list)
+
+ inv:set_size(listname, #itemstrings)
+
+ for i, itemstring in ipairs(itemstrings) do
+ inv:set_stack(listname, i, ItemStack(itemstring))
+ end
+end
+
+function futil.serialize_inv(inv)
+ local serialized_lists = {}
+
+ for listname in pairs(inv:get_lists()) do
+ serialized_lists[listname] = futil.serialize_invlist(inv, listname)
+ end
+
+ return futil.serialize(serialized_lists)
+end
+
+function futil.deserialize_inv(serialized_lists, inv)
+ for listname, serialized_list in pairs(futil.deserialize(serialized_lists)) do
+ futil.deserialize_invlist(serialized_list, inv, listname)
+ end
+end
+
+function futil.serialize_node_meta(pos)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ return futil.serialize({
+ fields = meta:to_table().fields,
+ inventory = futil.serialize_inv(inv),
+ })
+end
+
+function futil.deserialize_node_meta(serialized_node_meta, pos)
+ local meta = minetest.get_meta(pos)
+ local x = futil.deserialize(serialized_node_meta)
+ meta:from_table({ fields = x.fields })
+ local inv = meta:get_inventory()
+ futil.deserialize_inv(x.inventory, inv)
+end
diff --git a/mods/futil/minetest/set_look_dir.lua b/mods/futil/minetest/set_look_dir.lua
new file mode 100644
index 00000000..aa356e14
--- /dev/null
+++ b/mods/futil/minetest/set_look_dir.lua
@@ -0,0 +1,7 @@
+local pi = math.pi
+function futil.set_look_dir(player, look_dir)
+ local pitch = math.asin(-look_dir.y)
+ local yaw = math.atan2(look_dir.z, look_dir.x)
+ player:set_look_vertical(pitch)
+ player:set_look_horizontal((yaw + 1.5 * pi) % (2.0 * pi))
+end
diff --git a/mods/futil/minetest/strip_translation.lua b/mods/futil/minetest/strip_translation.lua
new file mode 100644
index 00000000..4271ff24
--- /dev/null
+++ b/mods/futil/minetest/strip_translation.lua
@@ -0,0 +1,205 @@
+local function tokenize(s)
+ local tokens = {}
+
+ local i = 1
+ local j = 1
+
+ while true do
+ if s:sub(j, j) == "" then
+ if i < j then
+ table.insert(tokens, s:sub(i, j - 1))
+ end
+ return tokens
+ elseif s:sub(j, j):byte() == 27 then
+ if i < j then
+ table.insert(tokens, s:sub(i, j - 1))
+ end
+
+ i = j
+ local n = s:sub(i + 1, i + 1)
+
+ if n == "(" then
+ local m = s:sub(i + 2, i + 2)
+ local k = s:find(")", i + 3, true)
+ if not k then
+ futil.log("error", "strip_translation: couldn't tokenize %q", s)
+ return {}
+ end
+ if m == "T" then
+ table.insert(tokens, {
+ type = "translation",
+ domain = s:sub(i + 4, k - 1),
+ })
+ elseif m == "c" then
+ table.insert(tokens, {
+ type = "color",
+ color = s:sub(i + 4, k - 1),
+ })
+ elseif m == "b" then
+ table.insert(tokens, {
+ type = "bgcolor",
+ color = s:sub(i + 4, k - 1),
+ })
+ else
+ futil.log("error", "strip_translation: couldn't tokenize %q", s)
+ return {}
+ end
+ i = k + 1
+ j = k + 1
+ elseif n == "F" then
+ table.insert(tokens, {
+ type = "start",
+ })
+ i = j + 2
+ j = j + 2
+ elseif n == "E" then
+ table.insert(tokens, {
+ type = "stop",
+ })
+ i = j + 2
+ j = j + 2
+ else
+ futil.log("error", "strip_translation: couldn't tokenize %q", s)
+ return {}
+ end
+ else
+ j = j + 1
+ end
+ end
+end
+
+local function parse(tokens, i, parsed)
+ parsed = parsed or {}
+ i = i or 1
+ while i <= #tokens do
+ local token = tokens[i]
+ if type(token) == "string" then
+ table.insert(parsed, token)
+ i = i + 1
+ elseif token.type == "color" or token.type == "bgcolor" then
+ table.insert(parsed, token)
+ i = i + 1
+ elseif token.type == "translation" then
+ local contents = {
+ type = "translation",
+ domain = token.domain,
+ }
+ i = i + 1
+ contents, i = parse(tokens, i, contents)
+ if i == -1 then
+ return "", -1
+ end
+ table.insert(parsed, contents)
+ elseif token.type == "start" then
+ local contents = {
+ type = "escape",
+ }
+ i = i + 1
+ contents, i = parse(tokens, i, contents)
+ if i == -1 then
+ return "", -1
+ end
+ table.insert(parsed, contents)
+ elseif token.type == "stop" then
+ i = i + 1
+ return parsed, i
+ else
+ futil.log("error", "strip_translation: couldn't parse %s", dump(token):gsub("%s+", ""))
+ return "", -1
+ end
+ end
+ return parsed, i
+end
+
+local function unparse_and_strip_translation(parsed, parts)
+ parts = parts or {}
+ for _, part in ipairs(parsed) do
+ if type(part) == "string" then
+ table.insert(parts, part)
+ else
+ if part.type == "bgcolor" then
+ table.insert(parts, ("\27(b@%s)"):format(part.color))
+ elseif part.type == "color" then
+ table.insert(parts, ("\27(c@%s)"):format(part.color))
+ elseif part.domain then
+ unparse_and_strip_translation(part, parts)
+ else
+ unparse_and_strip_translation(part, parts)
+ end
+ end
+ end
+
+ return parts
+end
+
+local function erase_after_newline(parsed, erasing)
+ local single_line_parsed = {}
+
+ for _, piece in ipairs(parsed) do
+ if type(piece) == "string" then
+ if not erasing then
+ if piece:find("\n") then
+ erasing = true
+ local single_line = piece:match("^([^\n]*)\n")
+ table.insert(single_line_parsed, single_line)
+ else
+ table.insert(single_line_parsed, piece)
+ end
+ end
+ elseif piece.type == "bgcolor" or piece.type == "color" then
+ table.insert(single_line_parsed, piece)
+ elseif piece.type == "escape" then
+ table.insert(single_line_parsed, erase_after_newline(piece, erasing))
+ elseif piece.type == "translation" then
+ local stuff = erase_after_newline(piece, erasing)
+ stuff.domain = piece.domain
+ table.insert(single_line_parsed, stuff)
+ else
+ futil.log("error", "strip_translation: couldn't erase_after_newline %s", dump(parsed):gsub("%s+", ""))
+ return {}
+ end
+ end
+
+ return single_line_parsed
+end
+
+local function unparse(parsed, parts)
+ parts = parts or {}
+ for _, part in ipairs(parsed) do
+ if type(part) == "string" then
+ table.insert(parts, part)
+ else
+ if part.type == "bgcolor" then
+ table.insert(parts, ("\27(b@%s)"):format(part.color))
+ elseif part.type == "color" then
+ table.insert(parts, ("\27(c@%s)"):format(part.color))
+ elseif part.domain then
+ table.insert(parts, ("\27(T@%s)"):format(part.domain))
+ unparse(part, parts)
+ table.insert(parts, "\27E")
+ else
+ table.insert(parts, "\27F")
+ unparse(part, parts)
+ table.insert(parts, "\27E")
+ end
+ end
+ end
+
+ return parts
+end
+
+function futil.strip_translation(msg)
+ local tokens = tokenize(msg)
+ local parsed = parse(tokens)
+ return table.concat(unparse_and_strip_translation(parsed), "")
+end
+
+function futil.get_safe_short_description(item)
+ item = type(item) == "userdata" and item or ItemStack(item)
+ local description = item:get_description()
+ local tokens = tokenize(description)
+ local parsed = parse(tokens)
+ local single_line_parsed = erase_after_newline(parsed)
+ local single_line = table.concat(unparse(single_line_parsed), "")
+ return single_line
+end
diff --git a/mods/futil/minetest/texture.lua b/mods/futil/minetest/texture.lua
new file mode 100644
index 00000000..ba53fe69
--- /dev/null
+++ b/mods/futil/minetest/texture.lua
@@ -0,0 +1,9 @@
+-- https://github.com/minetest/minetest/blob/9fc018ded10225589d2559d24a5db739e891fb31/doc/lua_api.txt#L453-L462
+function futil.escape_texture(texturestring)
+ -- store in a variable so we don't return both rvs of gsub
+ local v = texturestring:gsub("[%^:]", {
+ ["^"] = "\\^",
+ [":"] = "\\:",
+ })
+ return v
+end
diff --git a/mods/futil/minetest/time.lua b/mods/futil/minetest/time.lua
new file mode 100644
index 00000000..767bcc1e
--- /dev/null
+++ b/mods/futil/minetest/time.lua
@@ -0,0 +1,7 @@
+function futil.wait(us)
+ local wait_until = minetest.get_us_time() + us
+ local get_us_time = minetest.get_us_time
+ while get_us_time() < wait_until do
+ -- the NOTHING function does nothing.
+ end
+end
diff --git a/mods/futil/minetest/vector.lua b/mods/futil/minetest/vector.lua
new file mode 100644
index 00000000..5090f19b
--- /dev/null
+++ b/mods/futil/minetest/vector.lua
@@ -0,0 +1,402 @@
+local m_abs = math.abs
+local m_acos = math.acos
+local m_asin = math.asin
+local m_atan2 = math.atan2
+local m_cos = math.cos
+local m_floor = math.floor
+local m_min = math.min
+local m_max = math.max
+local m_pi = math.pi
+local m_pow = math.pow
+local m_random = math.random
+local m_sin = math.sin
+
+local v_add = vector.add
+local v_new = vector.new
+local v_sort = vector.sort
+local v_sub = vector.subtract
+
+local in_bounds = futil.math.in_bounds
+local bound = futil.math.bound
+
+local mapblock_size = 16 -- can be redefined, but effectively hard-coded
+local chunksize = m_floor(tonumber(minetest.settings:get("chunksize")) or 5) -- # of mapblocks in a chunk (1 dim)
+local chunksize_nodes = mapblock_size * chunksize -- # of nodes in a chunk (1 dim)
+local max_mapgen_limit = 31007 -- hard coded
+local mapgen_limit =
+ bound(0, m_floor(tonumber(minetest.settings:get("mapgen_limit")) or max_mapgen_limit), max_mapgen_limit)
+local mapgen_limit_b = m_floor(mapgen_limit / mapblock_size) -- # of mapblocks
+
+-- *actual* minimum and maximum coordinates - one mapblock short of the theoretical min and max
+local map_min_i = (-mapgen_limit_b * mapblock_size) + chunksize_nodes
+local map_max_i = ((mapgen_limit_b + 1) * mapblock_size - 1) - chunksize_nodes
+
+local map_min_p = v_new(map_min_i, map_min_i, map_min_i)
+local map_max_p = v_new(map_max_i, map_max_i, map_max_i)
+
+futil.vector = {}
+
+function futil.vector.get_bounds(pos, radius)
+ return v_sub(pos, radius), v_add(pos, radius)
+end
+
+futil.get_bounds = futil.vector.get_bounds
+
+function futil.vector.get_world_bounds()
+ return map_min_p, map_max_p
+end
+
+futil.get_world_bounds = futil.vector.get_world_bounds
+
+function futil.vector.get_blockpos(pos)
+ return v_new(m_floor(pos.x / mapblock_size), m_floor(pos.y / mapblock_size), m_floor(pos.z / mapblock_size))
+end
+
+futil.get_blockpos = futil.vector.get_blockpos
+
+function futil.vector.get_block_min(blockpos)
+ return v_new(blockpos.x * mapblock_size, blockpos.y * mapblock_size, blockpos.z * mapblock_size)
+end
+
+function futil.vector.get_block_max(blockpos)
+ return v_new(
+ blockpos.x * mapblock_size + (mapblock_size - 1),
+ blockpos.y * mapblock_size + (mapblock_size - 1),
+ blockpos.z * mapblock_size + (mapblock_size - 1)
+ )
+end
+
+function futil.vector.get_block_bounds(blockpos)
+ return futil.vector.get_block_min(blockpos), futil.vector.get_block_max(blockpos)
+end
+
+futil.get_block_bounds = futil.vector.get_block_bounds
+
+function futil.vector.get_block_center(blockpos)
+ return v_add(futil.vector.get_block_min(blockpos), 8) -- 8 = 16 / 2
+end
+
+function futil.vector.get_chunkpos(pos)
+ return v_new(
+ m_floor((pos.x - map_min_i) / chunksize_nodes),
+ m_floor((pos.y - map_min_i) / chunksize_nodes),
+ m_floor((pos.z - map_min_i) / chunksize_nodes)
+ )
+end
+
+futil.get_chunkpos = futil.vector.get_chunkpos
+
+function futil.vector.get_chunk_bounds(chunkpos)
+ return v_new(
+ chunkpos.x * chunksize_nodes + map_min_i,
+ chunkpos.y * chunksize_nodes + map_min_i,
+ chunkpos.z * chunksize_nodes + map_min_i
+ ),
+ v_new(
+ chunkpos.x * chunksize_nodes + map_min_i + (chunksize_nodes - 1),
+ chunkpos.y * chunksize_nodes + map_min_i + (chunksize_nodes - 1),
+ chunkpos.z * chunksize_nodes + map_min_i + (chunksize_nodes - 1)
+ )
+end
+
+futil.get_chunk_bounds = futil.vector.get_chunk_bounds
+
+function futil.vector.formspec_pos(pos)
+ return ("%i,%i,%i"):format(pos.x, pos.y, pos.z)
+end
+
+futil.formspec_pos = futil.vector.formspec_pos
+
+function futil.vector.iterate_area(minp, maxp)
+ minp, maxp = v_sort(minp, maxp)
+ local min_x = minp.x
+ local min_z = minp.z
+
+ local x = min_x - 1
+ local y = minp.y
+ local z = min_z
+
+ local max_x = maxp.x
+ local max_y = maxp.y
+ local max_z = maxp.z
+
+ return function()
+ if y > max_y then
+ return
+ end
+
+ x = x + 1
+ if x > max_x then
+ x = min_x
+ z = z + 1
+ end
+
+ if z > max_z then
+ z = min_z
+ y = y + 1
+ end
+
+ if y <= max_y then
+ return v_new(x, y, z)
+ end
+ end
+end
+
+futil.iterate_area = futil.vector.iterate_area
+
+function futil.vector.iterate_volume(pos, radius)
+ return futil.iterate_area(futil.get_bounds(pos, radius))
+end
+
+futil.iterate_volume = futil.vector.iterate_volume
+
+function futil.is_pos_in_bounds(minp, pos, maxp)
+ minp, maxp = v_sort(minp, maxp)
+ return (in_bounds(minp.x, pos.x, maxp.x) and in_bounds(minp.y, pos.y, maxp.y) and in_bounds(minp.z, pos.z, maxp.z))
+end
+
+function futil.vector.is_inside_world_bounds(pos)
+ return futil.is_pos_in_bounds(map_min_p, pos, map_max_p)
+end
+
+function futil.vector.is_blockpos_inside_world_bounds(blockpos)
+ return futil.vector.is_inside_world_bounds(futil.vector.get_block_min(blockpos))
+end
+
+futil.is_inside_world_bounds = futil.vector.is_inside_world_bounds
+
+function futil.vector.bound_position_to_world(pos)
+ return v_new(
+ bound(map_min_i, pos.x, map_max_i),
+ bound(map_min_i, pos.y, map_max_i),
+ bound(map_min_i, pos.z, map_max_i)
+ )
+end
+
+futil.bound_position_to_world = futil.vector.bound_position_to_world
+
+function futil.vector.volume(pos1, pos2)
+ local minp, maxp = v_sort(pos1, pos2)
+ return (maxp.x - minp.x + 1) * (maxp.y - minp.y + 1) * (maxp.z - minp.z + 1)
+end
+
+function futil.split_region_by_mapblock(pos1, pos2, num_blocks)
+ local chunk_size = 16 * (num_blocks or 1)
+ local chunk_span = chunk_size - 1
+
+ pos1, pos2 = vector.sort(pos1, pos2)
+
+ local min_x = pos1.x
+ local min_y = pos1.y
+ local min_z = pos1.z
+ local max_x = pos2.x
+ local max_y = pos2.y
+ local max_z = pos2.z
+
+ local x1 = min_x - (min_x % chunk_size)
+ local x2 = max_x - (max_x % chunk_size) + chunk_span
+ local y1 = min_y - (min_y % chunk_size)
+ local y2 = max_y - (max_y % chunk_size) + chunk_span
+ local z1 = min_z - (min_z % chunk_size)
+ local z2 = max_z - (max_z % chunk_size) + chunk_span
+
+ local chunks = {}
+ for y = y1, y2, chunk_size do
+ local y_min = m_max(min_y, y)
+ local y_max = m_min(max_y, y + chunk_span)
+
+ for x = x1, x2, chunk_size do
+ local x_min = m_max(min_x, x)
+ local x_max = m_min(max_x, x + chunk_span)
+
+ for z = z1, z2, chunk_size do
+ local z_min = m_max(min_z, z)
+ local z_max = m_min(max_z, z + chunk_span)
+
+ chunks[#chunks + 1] = { v_new(x_min, y_min, z_min), v_new(x_max, y_max, z_max) }
+ end
+ end
+ end
+
+ return chunks
+end
+
+function futil.random_unit_vector()
+ local u = m_random()
+ local v = m_random()
+ local lambda = m_acos(2 * u - 1) - (m_pi / 2)
+ local phi = 2 * m_pi * v
+ return v_new(m_cos(lambda) * m_cos(phi), m_cos(lambda) * m_sin(phi), m_sin(lambda))
+end
+
+---- https://math.stackexchange.com/a/205589
+--function futil.random_unit_vector_in_solid_angle(theta, direction)
+-- local z = m_random() * (1 - m_cos(theta)) - 1
+-- local phi = m_random() * 2 * m_pi
+-- local z2 = (1 - z*z) ^ 0.5
+-- local ruv = v_new(z2 * m_cos(phi), z2 * m_sin(phi), z)
+-- direction = direction:normalize()
+-- ...
+--end
+
+function futil.is_indoors(pos, distance, trials, hits_needed)
+ distance = distance or 20
+ trials = trials or 11
+ hits_needed = hits_needed or 9
+ local num_hits = 0
+ for _ = 1, trials do
+ local ruv = futil.random_unit_vector()
+ local target = pos + (distance * ruv)
+ local hit = Raycast(pos, target, false, false)()
+ if hit then
+ num_hits = num_hits + 1
+ if num_hits == hits_needed then
+ return true
+ end
+ end
+ end
+ return false
+end
+
+function futil.can_see_sky(pos, distance, trials, max_hits)
+ distance = distance or 200
+ trials = trials or 11
+ max_hits = max_hits or 9
+ local num_hits = 0
+ for _ = 1, trials do
+ local ruv = futil.random_unit_vector()
+ ruv.y = m_abs(ruv.y) -- look up, not at the ground
+ local target = pos + (distance * ruv)
+ local hit = Raycast(pos, target, false, false)()
+ if hit then
+ num_hits = num_hits + 1
+ if num_hits > max_hits then
+ return false
+ end
+ end
+ end
+ return true
+end
+
+function futil.vector.is_valid_position(pos)
+ if type(pos) ~= "table" then
+ return false
+ elseif not (type(pos.x) == "number" and type(pos.y) == "number" and type(pos.z) == "number") then
+ return false
+ else
+ return futil.is_inside_world_bounds(vector.round(pos))
+ end
+end
+
+-- minetest.hash_node_position only works with integer coordinates
+function futil.vector.hash(pos)
+ return string.format("%a:%a:%a", pos.x, pos.y, pos.z)
+end
+
+function futil.vector.unhash(string)
+ local x, y, z = string:match("^([^:]+):([^:]+):([^:]+)$")
+ x, y, z = tonumber(x), tonumber(y), tonumber(z)
+ if not (x and y and z) then
+ return
+ end
+ return v_new(x, y, z)
+end
+
+function futil.vector.ldistance(pos1, pos2, p)
+ if p == math.huge then
+ return m_max(m_abs(pos1.x - pos2.x), m_abs(pos1.y - pos2.y), m_abs(pos1.z - pos2.z))
+ else
+ return m_pow(
+ m_pow(m_abs(pos1.x - pos2.x), p) + m_pow(m_abs(pos1.y - pos2.y), p) + m_pow(m_abs(pos1.z - pos2.z), p),
+ 1 / p
+ )
+ end
+end
+
+function futil.vector.round(pos, mult)
+ local round = futil.math.round
+ return v_new(round(pos.x, mult), round(pos.y, mult), round(pos.z, mult))
+end
+
+-- https://msl.cs.uiuc.edu/planning/node102.html
+function futil.vector.rotation_to_matrix(rotation)
+ local cosp = m_cos(rotation.x)
+ local sinp = m_sin(rotation.x)
+ local pitch = {
+ { cosp, 0, sinp },
+ { 0, 1, 0 },
+ { -sinp, 0, cosp },
+ }
+ local cosy = m_cos(rotation.y)
+ local siny = m_sin(rotation.y)
+ local yaw = {
+ { cosy, -siny, 0 },
+ { siny, cosy, 0 },
+ { 0, 0, 1 },
+ }
+ local cosr = m_cos(rotation.z)
+ local sinr = m_sin(rotation.z)
+ local roll = {
+ { 1, 0, 0 },
+ { 0, cosr, -sinr },
+ { 0, sinr, cosr },
+ }
+ return futil.matrix.multiply(futil.matrix.multiply(yaw, pitch), roll)
+end
+
+-- https://msl.cs.uiuc.edu/planning/node103.html
+function futil.vector.matrix_to_rotation(matrix)
+ local pitch = m_atan2(matrix[2][1], matrix[1][1])
+ local yaw = m_asin(-matrix[3][1])
+ local roll = m_atan2(matrix[3][2], matrix[3][3])
+ return v_new(pitch, yaw, roll)
+end
+
+function futil.vector.inverse_rotation(rot)
+ -- since the determinant of a rotation matrix is 1, the inverse is just the transpose and i don't have to write
+ -- a matrix inverter
+ return futil.vector.matrix_to_rotation(futil.matrix.transpose(futil.vector.rotation_to_matrix(rot)))
+end
+
+-- assumed in radians
+function futil.vector.compose_rotations(rot1, rot2)
+ local m1 = futil.vector.rotation_to_matrix(rot1)
+ local m2 = futil.vector.rotation_to_matrix(rot2)
+ return futil.vector.matrix_to_rotation(futil.matrix.multiply(m1, m2))
+end
+
+-- https://palitri.com/vault/stuff/maths/Rays%20closest%20point.pdf
+-- this was originally part of the ballistics mod but i don't need it there anymore
+function futil.vector.closest_point_to_two_lines(last_pos, last_vel, cur_pos, cur_vel, threshold)
+ threshold = threshold or 0.0001 -- if certain values are too close to 0, the results will not be good
+ local a = cur_vel
+ local b = last_vel
+ local a2 = a:dot(a)
+ if a2 < threshold then
+ return
+ end
+ local b2 = b:dot(b)
+ if b2 < threshold then
+ return
+ end
+ local ab = a:dot(b)
+ local denom = (a2 * b2) - (ab * ab)
+ if denom < threshold then
+ return
+ end
+ local A = cur_pos
+ local B = last_pos
+ local c = last_pos - cur_pos
+ local bc = b:dot(c)
+ local ac = a:dot(c)
+ local D = A + a * ((ac * b2 - ab * bc) / denom)
+ local E = B + b * ((ab * ac - bc * a2) / denom)
+ return (D + E) / 2
+end
+
+function futil.vector.v2f_to_float_32(v)
+ return {
+ x = futil.math.to_float32(v.x),
+ y = futil.math.to_float32(v.y),
+ }
+end
diff --git a/mods/futil/mod.conf b/mods/futil/mod.conf
new file mode 100644
index 00000000..ff9311bc
--- /dev/null
+++ b/mods/futil/mod.conf
@@ -0,0 +1,12 @@
+name = futil
+title = futil
+description = flux's utility mod
+website = https://content.minetest.net/packages/rheo/futil/
+author = rheo
+license = LGPL-3.0-or-later
+media_license = CC-BY-SA-4.0
+version = 2024-12-14
+min_minetest_version = 5.8.0
+supported_games = *
+depends = fmod
+release = 29065
diff --git a/mods/futil/util/bisect.lua b/mods/futil/util/bisect.lua
new file mode 100644
index 00000000..1d9ee0ae
--- /dev/null
+++ b/mods/futil/util/bisect.lua
@@ -0,0 +1,51 @@
+futil.bisect = {}
+
+function futil.bisect.right(t, x, low, high, key)
+ low = low or 1
+ high = high or #t + 1
+ if key then
+ while low < high do
+ local mid = math.floor((low + high) / 2)
+ if x < key(t[mid]) then
+ high = mid
+ else
+ low = mid + 1
+ end
+ end
+ else
+ while low < high do
+ local mid = math.floor((low + high) / 2)
+ if x < t[mid] then
+ high = mid
+ else
+ low = mid + 1
+ end
+ end
+ end
+ return low
+end
+
+function futil.bisect.left(t, x, low, high, key)
+ low = low or 1
+ high = high or #t + 1
+ if key then
+ while low < high do
+ local mid = math.floor((low + high) / 2)
+ if key(t[mid]) < x then
+ low = mid + 1
+ else
+ high = mid
+ end
+ end
+ else
+ while low < high do
+ local mid = math.floor((low + high) / 2)
+ if t[mid] < x then
+ low = mid + 1
+ else
+ high = mid
+ end
+ end
+ end
+ return low
+end
diff --git a/mods/futil/util/class.lua b/mods/futil/util/class.lua
new file mode 100644
index 00000000..fa8c004a
--- /dev/null
+++ b/mods/futil/util/class.lua
@@ -0,0 +1,89 @@
+function futil.class1(super)
+ local class = {}
+ class.__index = class -- this becomes the index "metamethod" of objects
+
+ setmetatable(class, {
+ __index = super and super.__index or super,
+ __call = function(this_class, ...)
+ local obj = setmetatable({}, this_class)
+ local init = obj._init
+ if init then
+ init(obj, ...)
+ end
+ return obj
+ end,
+ })
+
+ function class:is_a(class2)
+ if class == class2 then
+ return true
+ end
+
+ if super and super:is_a(class2) then
+ return true
+ end
+
+ return false
+ end
+
+ return class
+end
+
+function futil.class(...)
+ local class = {}
+ class.__index = class
+
+ local meta = {
+ __call = function(this_class, ...)
+ local obj = setmetatable({}, this_class)
+ local init = obj._init
+ if init then
+ init(obj, ...)
+ end
+ return obj
+ end,
+ }
+
+ local parents = { ... }
+ class._parents = parents
+
+ if #parents > 0 then
+ function meta:__index(key)
+ for i = #parents, 1, -1 do
+ local parent = parents[i]
+ local index = parent.__index
+ local v
+ if index then
+ if type(index) == "function" then
+ v = index(self, key)
+ else
+ v = index[key]
+ end
+ else
+ v = parent[key]
+ end
+ if v then
+ return v
+ end
+ end
+ end
+ end
+
+ setmetatable(class, meta)
+
+ function class:is_a(class2)
+ if class == class2 then
+ return true
+ end
+
+ for _, parent in ipairs(parents) do
+ if parent:is_a(class2) then
+ return true
+ end
+ end
+
+ return false
+ end
+
+ return class
+end
diff --git a/mods/futil/util/coalesce.lua b/mods/futil/util/coalesce.lua
new file mode 100644
index 00000000..1491e3af
--- /dev/null
+++ b/mods/futil/util/coalesce.lua
@@ -0,0 +1,9 @@
+function futil.coalesce(...)
+ local arg = futil.table.pack(...)
+ for i = 1, arg.n do
+ local v = arg[i]
+ if v ~= nil then
+ return v
+ end
+ end
+end
diff --git a/mods/futil/util/equals.lua b/mods/futil/util/equals.lua
new file mode 100644
index 00000000..c5587b3e
--- /dev/null
+++ b/mods/futil/util/equals.lua
@@ -0,0 +1,28 @@
+local table_size = futil.table.size
+
+local function equals(a, b)
+ local t = type(a)
+
+ if t ~= type(b) then
+ return false
+ end
+
+ if t ~= "table" then
+ return a == b
+ elseif a == b then
+ return true
+ end
+
+ local size_a = 0
+
+ for key, value in pairs(a) do
+ if not equals(value, b[key]) then
+ return false
+ end
+ size_a = size_a + 1
+ end
+
+ return size_a == table_size(b)
+end
+
+futil.equals = equals
diff --git a/mods/futil/util/exception.lua b/mods/futil/util/exception.lua
new file mode 100644
index 00000000..45800a42
--- /dev/null
+++ b/mods/futil/util/exception.lua
@@ -0,0 +1,29 @@
+function futil.safe_wrap(func, rv_on_fail, error_callback)
+ -- wrap a function w/ logic to avoid crashing
+ return function(...)
+ local rvs = { xpcall(func, debug.traceback, ...) }
+
+ if rvs[1] then
+ return unpack(rvs, 2)
+ else
+ if error_callback then
+ error_callback(debug.getinfo(func), { ... }, rvs[2])
+ else
+ futil.log(
+ "error",
+ "(check_call): %s args: %s out: %s",
+ dump(debug.getinfo(func)),
+ dump({ ... }),
+ rvs[2]
+ )
+ end
+ return rv_on_fail
+ end
+ end
+end
+
+function futil.safe_call(func, rv_on_fail, error_callback, ...)
+ return futil.safe_wrap(func, rv_on_fail, error_callback)(...)
+end
+
+futil.check_call = futil.safe_wrap -- backwards compatibility
diff --git a/mods/futil/util/file.lua b/mods/futil/util/file.lua
new file mode 100644
index 00000000..999f97b7
--- /dev/null
+++ b/mods/futil/util/file.lua
@@ -0,0 +1,37 @@
+function futil.file_exists(path)
+ local f = io.open(path, "r")
+ if f then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+function futil.load_file(filename)
+ local file = io.open(filename, "r")
+
+ if not file then
+ return
+ end
+
+ local contents = file:read("*a")
+
+ file:close()
+
+ return contents
+end
+
+-- minetest.safe_file_write is apparently unreliable on windows
+function futil.write_file(filename, contents)
+ local file = io.open(filename, "w")
+
+ if not file then
+ return false
+ end
+
+ file:write(contents)
+ file:close()
+
+ return true
+end
diff --git a/mods/futil/util/functional.lua b/mods/futil/util/functional.lua
new file mode 100644
index 00000000..9fc56cd0
--- /dev/null
+++ b/mods/futil/util/functional.lua
@@ -0,0 +1,159 @@
+local functional = {}
+
+local t_iterate = futil.table.iterate
+local t_insert = table.insert
+
+function functional.noop()
+ -- the NOTHING function does nothing.
+end
+
+function functional.identity(x)
+ return x
+end
+
+function functional.izip(...)
+ local is = { ... }
+ if #is == 0 then
+ return functional.noop
+ end
+
+ return function()
+ local t = {}
+ for i in t_iterate(is) do
+ local v = i()
+ if v ~= nil then
+ t_insert(t, v)
+ else
+ return
+ end
+ end
+
+ return t
+ end
+end
+
+function functional.zip(...)
+ local is = {}
+ for t in t_iterate({ ... }) do
+ t_insert(is, t_iterate(t))
+ end
+ return functional.izip(unpack(is))
+end
+
+function functional.imap(func, ...)
+ local zipper = functional.izip(...)
+ return function()
+ local args = zipper()
+ if args then
+ return func(unpack(args))
+ end
+ end
+end
+
+function functional.map(func, ...)
+ local zipper = functional.zip(...)
+ return function()
+ local args = zipper()
+ if args then
+ return func(unpack(args))
+ end
+ end
+end
+
+function functional.apply(func, t)
+ local t2 = {}
+ for k, v in pairs(t) do
+ t2[k] = func(v)
+ end
+ return t2
+end
+
+function functional.reduce(func, t, initial)
+ local i = t_iterate(t)
+ if not initial then
+ initial = i()
+ end
+ local next = i()
+ while next do
+ initial = func(initial, next)
+ next = i()
+ end
+ return initial
+end
+
+function functional.partial(func, ...)
+ local args = { ... }
+ return function(...)
+ return func(unpack(args), ...)
+ end
+end
+
+function functional.compose(a, b)
+ return function(...)
+ return a(b(...))
+ end
+end
+
+function functional.ifilter(pred, i)
+ local v
+ return function()
+ v = i()
+ while v ~= nil and not pred(v) do
+ v = i()
+ end
+ return v
+ end
+end
+
+function functional.filter(pred, t)
+ return functional.ifilter(pred, t_iterate(t))
+end
+
+function functional.iall(i)
+ while true do
+ local v = i()
+ if v == false then
+ return false
+ elseif v == nil then
+ return true
+ end
+ end
+end
+
+function functional.all(t)
+ for i = 1, #t do
+ if not t[i] then
+ return false
+ end
+ end
+
+ return true
+end
+
+function functional.iany(i)
+ while true do
+ local v = i()
+ if v == nil then
+ return false
+ elseif v then
+ return true
+ end
+ end
+end
+
+function functional.any(t)
+ for i = 1, #t do
+ if t[i] then
+ return true
+ end
+ end
+ return false
+end
+
+function functional.wrap(f)
+ return function(...)
+ return f(...)
+ end
+end
+
+futil.functional = functional
diff --git a/mods/futil/util/http.lua b/mods/futil/util/http.lua
new file mode 100644
index 00000000..3973d76b
--- /dev/null
+++ b/mods/futil/util/http.lua
@@ -0,0 +1,10 @@
+local function char_to_hex(c)
+ return string.format("%%%02X", string.byte(c))
+end
+
+function futil.urlencode(text)
+ text = text:gsub("\n", "\r\n")
+ text = text:gsub("([^0-9a-zA-Z !'()*._~-])", char_to_hex)
+ text = text:gsub(" ", "+")
+ return text
+end
diff --git a/mods/futil/util/init.lua b/mods/futil/util/init.lua
new file mode 100644
index 00000000..e886719a
--- /dev/null
+++ b/mods/futil/util/init.lua
@@ -0,0 +1,24 @@
+futil.dofile("util", "bisect")
+futil.dofile("util", "class")
+futil.dofile("util", "coalesce")
+futil.dofile("util", "exception")
+futil.dofile("util", "file")
+futil.dofile("util", "http")
+futil.dofile("util", "list")
+futil.dofile("util", "math")
+futil.dofile("util", "matrix")
+futil.dofile("util", "memoization")
+futil.dofile("util", "memory")
+futil.dofile("util", "path")
+futil.dofile("util", "predicates")
+futil.dofile("util", "string")
+futil.dofile("util", "table")
+
+futil.dofile("util", "equals") -- depends on table
+futil.dofile("util", "functional") -- depends on table
+futil.dofile("util", "iterators") -- depends on functional
+futil.dofile("util", "random") -- depends on math
+futil.dofile("util", "regex") -- depends on exception
+futil.dofile("util", "selection") -- depends on table, math
+futil.dofile("util", "time") -- depends on math
+futil.dofile("util", "limiters") -- depends on functional
diff --git a/mods/futil/util/iterators.lua b/mods/futil/util/iterators.lua
new file mode 100644
index 00000000..0eeaba86
--- /dev/null
+++ b/mods/futil/util/iterators.lua
@@ -0,0 +1,106 @@
+local iterators = {}
+
+function iterators.range(...)
+ local a, b, c = ...
+ if type(a) ~= "number" then
+ error("invalid range")
+ end
+ if not b then
+ a, b = 1, a
+ end
+ if type(b) ~= "number" then
+ error("invalid range")
+ end
+ c = c or 1
+ if type(c) ~= "number" or c == 0 then
+ error("invalid range")
+ end
+
+ if c > 0 then
+ return function()
+ if a > b then
+ return
+ end
+ local to_return = a
+ a = a + c
+ return to_return
+ end
+ else
+ return function()
+ if a < b then
+ return
+ end
+ local to_return = a
+ a = a + c
+ return to_return
+ end
+ end
+end
+
+function iterators.repeat_(value, times)
+ if times then
+ local i = 0
+ return function()
+ i = i + 1
+ if i <= times then
+ return value
+ end
+ end
+ else
+ return function()
+ return value
+ end
+ end
+end
+
+function iterators.chain(...)
+ local arg = { ... }
+ local i = 1
+
+ return function()
+ while i <= #arg do
+ local v = arg[i]()
+ if v then
+ return v
+ end
+ end
+ end
+end
+
+function iterators.count(start, step)
+ step = step or 1
+ return function()
+ local rv = start
+ start = start + step
+ return rv
+ end
+end
+
+function iterators.values(t)
+ local k
+ return function()
+ local value
+ k, value = next(t, k)
+ return value
+ end
+end
+
+function iterators.accumulate(t, composer, initial)
+ local value = initial
+ local i = futil.table.iterate(t)
+ return function()
+ local next_value = i()
+ if next_value then
+ if value == nil then
+ value = next_value
+ elseif composer then
+ value = composer(value, next_value)
+ else
+ value = value + next_value
+ end
+ return value
+ end
+ end
+end
+
+futil.iterators = iterators
diff --git a/mods/futil/util/limiters.lua b/mods/futil/util/limiters.lua
new file mode 100644
index 00000000..41db21a4
--- /dev/null
+++ b/mods/futil/util/limiters.lua
@@ -0,0 +1,42 @@
+--[[
+ functions to limit the growth of a variable.
+ the intention here is to provide a family of functions for which
+ * f(x) is defined for all x >= 0
+ * f(0) = 0
+ * f(1) = 1
+ * f is continuous
+ * f is nondecreasing
+ * f\'(x) is nonincreasing when x > 1 (when the parameters are appropriate)
+]]
+
+local log = math.log
+local pow = math.pow
+local tanh = math.tanh
+
+futil.limiters = {
+ -- no limiting
+ none = futil.functional.identity,
+ -- f(x) = x ^ param_1. param_1 should be < 1 for f\'(x) to be nonincreasing
+ -- f(x) will grow arbitrarily, but at a decreasing rate.
+ gamma = function(x, param_1)
+ return pow(x, param_1)
+ end,
+ -- the hyperbolic tangent scaled so that f(0) = 0 and f(1) = 1.
+ -- f(x) will grow approximately linearly for small x, but it will never grow beyond a maximum value, which is
+ -- approximately equal to param_1 + 1
+ tanh = function(x, param_1)
+ return (tanh((x - 1) / param_1) - tanh(-1 / param_1)) / -tanh(-1 / param_1)
+ end,
+ -- f(x) = log^param_2(param_1 * x + 1), scaled so that f(0) = 0 and f(1) = 1.
+ -- f(x) will grow arbitrarily, but at a much slower rate than a gamma limiter
+ log__n = function(x, param_1, param_2)
+ return (log(x + 1) * pow(log(param_1 * x + 1), param_2) / (log(2) * pow(log(param_1 + 1), param_2)))
+ end,
+}
+
+function futil.create_limiter(name, param_1, param_2)
+ local f = futil.limiters[name]
+ return function(x)
+ return f(x, param_1, param_2)
+ end
+end
diff --git a/mods/futil/util/list.lua b/mods/futil/util/list.lua
new file mode 100644
index 00000000..ed8103fa
--- /dev/null
+++ b/mods/futil/util/list.lua
@@ -0,0 +1,19 @@
+function futil.list(iterator)
+ local t = {}
+ local v = iterator()
+ while v do
+ t[#t + 1] = v
+ v = iterator()
+ end
+ return t
+end
+
+function futil.list_multiple(iterator)
+ local t = {}
+ local v = { iterator() }
+ while #v > 0 do
+ t[#t + 1] = v
+ v = { iterator() }
+ end
+ return t
+end
diff --git a/mods/futil/util/math.lua b/mods/futil/util/math.lua
new file mode 100644
index 00000000..f2df95e7
--- /dev/null
+++ b/mods/futil/util/math.lua
@@ -0,0 +1,162 @@
+futil.math = {}
+
+local floor = math.floor
+local huge = math.huge
+local max = math.max
+local min = math.min
+
+function futil.math.idiv(a, b)
+ local rem = a % b
+ return (a - rem) / b, rem
+end
+
+function futil.math.bound(m, v, M)
+ return max(m, min(v, M))
+end
+
+function futil.math.in_bounds(m, v, M)
+ return m <= v and v <= M
+end
+
+local in_bounds = futil.math.in_bounds
+
+function futil.math.is_integer(v)
+ return floor(v) == v
+end
+
+local is_integer = futil.math.is_integer
+
+function futil.math.is_u8(i)
+ return (type(i) == "number" and is_integer(i) and in_bounds(0, i, 0xFF))
+end
+
+function futil.math.is_u16(i)
+ return (type(i) == "number" and is_integer(i) and in_bounds(0, i, 0xFFFF))
+end
+
+function futil.math.sum(t, initial)
+ local sum
+ local start
+ if initial then
+ sum = initial
+ start = 1
+ else
+ sum = t[1]
+ start = 2
+ end
+
+ for i = start, #t do
+ sum = sum + t[i]
+ end
+
+ return sum
+end
+
+function futil.math.isum(i, initial)
+ local sum
+
+ if initial == nil then
+ sum = i()
+ else
+ sum = initial
+ end
+
+ local v = i()
+
+ while v do
+ sum = sum + v
+ v = i()
+ end
+
+ return sum
+end
+
+function futil.math.product(t, initial)
+ local product
+ local start
+ if initial then
+ product = initial
+ start = 1
+ else
+ product = t[1]
+ start = 2
+ end
+
+ for i = start, #t do
+ product = product * t[i]
+ end
+
+ return product
+end
+
+function futil.math.iproduct(i, initial)
+ local product
+
+ if initial == nil then
+ product = i()
+ else
+ product = initial
+ end
+
+ local v = i()
+
+ while v do
+ product = product * v
+ v = i()
+ end
+
+ return product
+end
+
+function futil.math.probabilistic_round(v)
+ return floor(v + math.random())
+end
+
+function futil.math.cmp(a, b)
+ return a < b
+end
+
+futil.math.deg2rad = math.deg
+
+futil.math.rad2deg = math.rad
+
+function futil.math.do_intervals_overlap(min1, max1, min2, max2)
+ return min1 <= max2 and min2 <= max1
+end
+
+-- i took one class from kahan and can't stop doing this
+local function round(n)
+ local d = n % 1
+ local i = n - d
+
+ if i % 2 == 0 then
+ if d <= 0.5 then
+ return i
+ else
+ return i + 1
+ end
+ else
+ if d < 0.5 then
+ return i
+ else
+ return i + 1
+ end
+ end
+end
+
+function futil.math.round(number, mult)
+ if mult then
+ return round(number / mult) * mult
+ else
+ return round(number)
+ end
+end
+
+-- TODO this doesn't handle out-of-bounds exponents
+function futil.math.to_float32(number)
+ if number == huge or number == -huge or number ~= number then
+ return number
+ end
+ local sign, significand, exponent = ("%a"):format(number):match("^(-?)0x([0-9a-f\\.]+)p([0-9+-]+)$")
+ return tonumber(("%s0x%sp%s"):format(sign, significand:sub(1, 8), exponent))
+end
diff --git a/mods/futil/util/matrix.lua b/mods/futil/util/matrix.lua
new file mode 100644
index 00000000..433d9a7c
--- /dev/null
+++ b/mods/futil/util/matrix.lua
@@ -0,0 +1,30 @@
+futil.matrix = {}
+
+function futil.matrix.multiply(m1, m2)
+ assert(#m1[1] == #m2, "width of first argument must be height of second")
+ local product = {}
+ for i = 1, #m1 do
+ local row = {}
+ for j = 1, #m2[1] do
+ local value = 0
+ for k = 1, #m2 do
+ value = value + m1[i][k] * m2[k][j]
+ end
+ row[j] = value
+ end
+ product[i] = row
+ end
+ return product
+end
+
+function futil.matrix.transpose(m)
+ local t = {}
+ for i = 1, #m[1] do
+ local row = {}
+ for j = 1, #m do
+ row[j] = m[j][i]
+ end
+ t[i] = row
+ end
+ return t
+end
diff --git a/mods/futil/util/memoization.lua b/mods/futil/util/memoization.lua
new file mode 100644
index 00000000..73a86163
--- /dev/null
+++ b/mods/futil/util/memoization.lua
@@ -0,0 +1,51 @@
+local private_state = ...
+local mod_storage = private_state.mod_storage
+
+function futil.memoize1(func)
+ local memo = {}
+ return function(arg)
+ if arg == nil then
+ return func(arg)
+ end
+ local rv = memo[arg]
+
+ if not rv then
+ rv = func(arg)
+ memo[arg] = rv
+ end
+
+ return rv
+ end
+end
+
+function futil.memoize_dumpable(func)
+ local memo = {}
+ return function(...)
+ local key = dump({ ... })
+ local rv = memo[key]
+
+ if not rv then
+ rv = func(...)
+ memo[key] = rv
+ end
+
+ return rv
+ end
+end
+
+function futil.memoize1_modstorage(id, func)
+ local key_format = ("%%s:%s:memoize"):format(id)
+ return function(arg)
+ local key_key = key_format:format(tostring(arg))
+ local rv = mod_storage:get(key_key)
+
+ if not rv then
+ rv = func(arg)
+ mod_storage:set_string(key_key, tostring(rv))
+ end
+
+ return rv
+ end
+end
+
+futil.memoize1ms = futil.memoize1_modstorage -- backwards compatibility
diff --git a/mods/futil/util/memory.lua b/mods/futil/util/memory.lua
new file mode 100644
index 00000000..7ad6da8b
--- /dev/null
+++ b/mods/futil/util/memory.lua
@@ -0,0 +1,45 @@
+-- i have no idea how accurate this is, i use documentation from the below link for a few things
+-- https://wowwiki-archive.fandom.com/wiki/Lua_object_memory_sizes
+
+local function estimate_memory_usage(thing, seen)
+ local typ = type(thing)
+ if typ == "nil" then
+ return 0
+ end
+
+ seen = seen or {}
+ if seen[thing] then
+ return 0
+ end
+ seen[thing] = true
+
+ if typ == "boolean" then
+ return 4
+ elseif typ == "number" then
+ return 8 -- this is probably larger?
+ elseif typ == "string" then
+ return 25 + typ:len()
+ elseif typ == "function" then
+ -- TODO: we can calculate the usage of upvalues, but that's complicated
+ return 40
+ elseif typ == "userdata" then
+ return 0 -- this is probably larger
+ elseif typ == "thread" then
+ return 1224 -- this is probably larger
+ elseif typ == "table" then
+ local size = 64
+ for k, v in pairs(thing) do
+ if type(k) == "number" then
+ size = size + 16 + estimate_memory_usage(v, seen)
+ else
+ size = size + 40 + estimate_memory_usage(k, seen) + estimate_memory_usage(v, seen)
+ end
+ end
+ return size
+ else
+ futil.log("warning", "estimate_memory_usage: unknown type %s", typ)
+ return 0 -- ????
+ end
+end
+
+futil.estimate_memory_usage = estimate_memory_usage
diff --git a/mods/futil/util/path.lua b/mods/futil/util/path.lua
new file mode 100644
index 00000000..9c225742
--- /dev/null
+++ b/mods/futil/util/path.lua
@@ -0,0 +1,7 @@
+function futil.path_concat(...)
+ return table.concat({ ... }, DIR_DELIM)
+end
+
+function futil.path_split(path)
+ return string.split(path, DIR_DELIM, true)
+end
diff --git a/mods/futil/util/predicates.lua b/mods/futil/util/predicates.lua
new file mode 100644
index 00000000..61aa26b2
--- /dev/null
+++ b/mods/futil/util/predicates.lua
@@ -0,0 +1,43 @@
+function futil.is_nil(v)
+ return v == nil
+end
+
+function futil.is_boolean(v)
+ return v == true or v == false
+end
+
+function futil.is_number(v)
+ return type(v) == "number"
+end
+
+function futil.is_positive(v)
+ return v > 0
+end
+
+function futil.is_integer(v)
+ return v % 1 == 0
+end
+
+function futil.is_positive_integer(v)
+ return type(v) == "number" and v > 0 and v % 1 == 0
+end
+
+function futil.is_string(v)
+ return type(v) == "string"
+end
+
+function futil.is_userdata(v)
+ return type(v) == "userdata"
+end
+
+function futil.is_function(v)
+ return type(v) == "function"
+end
+
+function futil.is_thread(v)
+ return type(v) == "thread"
+end
+
+function futil.is_table(v)
+ return type(v) == "table"
+end
diff --git a/mods/futil/util/random.lua b/mods/futil/util/random.lua
new file mode 100644
index 00000000..81a38321
--- /dev/null
+++ b/mods/futil/util/random.lua
@@ -0,0 +1,84 @@
+local f = string.format
+
+futil.random = {}
+
+function futil.random.choice(t, random)
+ random = random or math.random
+ return t[random(#t)]
+end
+
+function futil.random.weighted_choice(t, random)
+ random = random or math.random
+ local elements, weights = {}, {}
+ local i = 1
+ for element, weight in pairs(t) do
+ elements[i] = element
+ weights[i] = weight
+ i = i + 1
+ end
+ local breaks = futil.list(futil.iterators.accumulate(weights))
+ local value = random() * breaks[#breaks]
+ return elements[futil.bisect.right(breaks, value)]
+end
+
+local WeightedChooser = futil.class1()
+
+function WeightedChooser:_init(t)
+ local elements, weights = {}, {}
+ local i = 1
+ for element, weight in pairs(t) do
+ elements[i] = element
+ weights[i] = weight
+ i = i + 1
+ end
+ self._elements = elements
+ self._breaks = futil.list(futil.iterators.accumulate(weights))
+end
+
+function WeightedChooser:next(random)
+ random = random or math.random
+ local breaks = self._breaks
+ local value = random() * breaks[#breaks]
+ return self._elements[futil.bisect.right(breaks, value)]
+end
+
+futil.random.WeightedChooser = WeightedChooser
+
+function futil.random.choice(t, random)
+ assert(#t > 0, "cannot get choice from an empty table")
+ random = random or math.random
+ return t[random(#t)]
+end
+
+-- https://stats.stackexchange.com/questions/569647/
+function futil.random.sample(t, k, random)
+ assert(k <= #t, f("cannot sample %i items from a set of size %i", k, #t))
+ random = random or math.random
+ local sample = {}
+ for i = 1, k do
+ sample[i] = t[i]
+ end
+ for j = k + 1, #t do
+ if random() < k / j then
+ sample[random(1, k)] = t[j]
+ end
+ end
+
+ return sample
+end
+
+function futil.random.sample_with_indices(t, k, random)
+ assert(k <= #t, f("cannot sample %i items from a set of size %i", k, #t))
+ random = random or math.random
+ local sample = {}
+ for i = 1, k do
+ sample[i] = { i, t[i] }
+ end
+ for j = k + 1, #t do
+ if random() < k / j then
+ sample[random(1, k)] = { j, t[j] }
+ end
+ end
+
+ return sample
+end
diff --git a/mods/futil/util/regex.lua b/mods/futil/util/regex.lua
new file mode 100644
index 00000000..f691c9f4
--- /dev/null
+++ b/mods/futil/util/regex.lua
@@ -0,0 +1,6 @@
+function futil.is_valid_regex(pattern)
+ return futil.safe_call(function()
+ (""):match(pattern)
+ return true
+ end, false, futil.functional.noop)
+end
diff --git a/mods/futil/util/selection.lua b/mods/futil/util/selection.lua
new file mode 100644
index 00000000..3e2c99f7
--- /dev/null
+++ b/mods/futil/util/selection.lua
@@ -0,0 +1,109 @@
+local floor = math.floor
+local min = math.min
+local random = math.random
+
+local swap = futil.table.swap
+local default_cmp = futil.math.cmp
+
+futil.selection = {}
+
+local function partition5(t, left, right, cmp)
+ cmp = cmp or default_cmp
+ local i = left + 1
+ while i <= right do
+ local j = i
+ while j > left and cmp(t[j], t[j - 1]) do
+ swap(t, j - 1, j)
+ j = j - 1
+ end
+ i = i + 1
+ end
+ return floor((left + right) / 2)
+end
+
+local function partition(t, left, right, pivot_i, i, cmp)
+ cmp = cmp or default_cmp
+ local pivot_v = t[pivot_i]
+ swap(t, pivot_i, right)
+ local store_i = left
+ for j = left, right - 1 do
+ if cmp(t[j], pivot_v) then
+ swap(t, store_i, j)
+ store_i = store_i + 1
+ end
+ end
+ local store_i_eq = store_i
+ for j = store_i, right - 1 do
+ if t[j] == pivot_v then
+ swap(t, store_i_eq, j)
+ store_i_eq = store_i_eq + 1
+ end
+ end
+ swap(t, right, store_i_eq)
+ if i < store_i then
+ return store_i
+ elseif i <= store_i_eq then
+ return i
+ else
+ return store_i_eq
+ end
+end
+
+local function quickselect(t, left, right, i, pivot_alg, cmp)
+ cmp = cmp or default_cmp
+ while true do
+ if left == right then
+ return left
+ end
+ local pivot_i = partition(t, left, right, pivot_alg(t, left, right, cmp), i, cmp)
+ if i == pivot_i then
+ return i
+ elseif i < pivot_i then
+ right = pivot_i - 1
+ else
+ left = pivot_i + 1
+ end
+ end
+end
+
+futil.selection.quickselect = quickselect
+
+futil.selection.pivot = {}
+
+function futil.selection.pivot.random(t, left, right, cmp)
+ return random(left, right)
+end
+
+local function pivot_medians_of_medians(t, left, right, cmp)
+ cmp = cmp or default_cmp
+ if right - left < 5 then
+ return partition5(t, left, right, cmp)
+ end
+ for i = left, right, 5 do
+ local sub_right = min(i + 4, right)
+ local median5 = partition5(t, i, sub_right, cmp)
+ swap(t, median5, left + floor((i - left) / 5))
+ end
+ local mid = floor((right - left) / 10) + left + 1
+ return quickselect(t, left, left + floor((right - left) / 5), mid, pivot_medians_of_medians, cmp)
+end
+
+futil.selection.pivot.median_of_medians = pivot_medians_of_medians
+
+--[[
+make use of quickselect to munge a table:
+ median_index = math.floor(#t / 2)
+ after calling this,
+ t[1] through t[median_index - 1] will be the elements less than t[median_index]
+ t[median_index] will be the median (or element less-than-the-median for even length tables)
+ t[median_index + 1] through t[#t] will be the elements greater than t[median_index]
+pivot is a pivot algorithm, defaults to random selection
+cmp is a comparison function.
+returns median_index.
+]]
+function futil.selection.select(t, pivot_alg, cmp)
+ cmp = cmp or default_cmp
+ pivot_alg = pivot_alg or futil.selection.pivot.random
+ local median_index = math.floor(#t / 2)
+ return quickselect(t, 1, #t, median_index, pivot_alg, cmp)
+end
diff --git a/mods/futil/util/string.lua b/mods/futil/util/string.lua
new file mode 100644
index 00000000..16877294
--- /dev/null
+++ b/mods/futil/util/string.lua
@@ -0,0 +1,62 @@
+futil.string = {}
+
+function futil.string.truncate(s, max_length, suffix)
+ suffix = suffix or "..."
+
+ if s:len() > max_length then
+ return s:sub(1, max_length - suffix:len()) .. suffix
+ else
+ return s
+ end
+end
+
+function futil.string.lc_cmp(a, b)
+ return a:lower() < b:lower()
+end
+
+function futil.string.startswith(s, start, start_i, end_i)
+ return s:sub(start_i or 0, end_i or #s):sub(1, #start) == start
+end
+
+local escape_pattern = "([%(%)%.%%%+%-%*%?%[%^%$])"
+local function escape_regex(str)
+ return str:gsub(escape_pattern, "%%%1")
+end
+
+local glob_patterns = {
+ ["?"] = ".",
+ ["*"] = ".*",
+}
+
+local function transform_pattern(pattern)
+ local parts = {}
+ local start = 1
+ for i = 1, #pattern do
+ local glob_pattern = glob_patterns[pattern:sub(i)]
+ if glob_pattern then
+ if start < i then
+ parts[#parts + 1] = escape_regex(pattern:sub(start, i - 1))
+ end
+ parts[#parts + 1] = glob_pattern
+ start = i + 1
+ end
+ end
+ if start < #pattern then
+ parts[#parts + 1] = escape_regex(pattern:sub(start, #pattern))
+ end
+ return table.concat(parts, "")
+end
+
+function futil.string.globmatch(str, pattern)
+ return str:match(transform_pattern(pattern))
+end
+
+futil.GlobMatcher = futil.class1()
+
+function futil.GlobMatcher:_init(pattern)
+ self._pattern = transform_pattern(pattern)
+end
+
+function futil.GlobMatcher:match(str)
+ return str:match(self._pattern)
+end
diff --git a/mods/futil/util/table.lua b/mods/futil/util/table.lua
new file mode 100644
index 00000000..0a2ae296
--- /dev/null
+++ b/mods/futil/util/table.lua
@@ -0,0 +1,188 @@
+local default_cmp = futil.math.cmp
+
+futil.table = {}
+
+function futil.table.set_all(t1, t2)
+ for k, v in pairs(t2) do
+ t1[k] = v
+ end
+ return t1
+end
+
+function futil.table.compose(t1, t2)
+ local t = table.copy(t1)
+ futil.table.set_all(t, t2)
+ return t
+end
+
+function futil.table.pairs_by_value(t, cmp)
+ cmp = cmp or default_cmp
+ local s = {}
+ for k, v in pairs(t) do
+ table.insert(s, { k, v })
+ end
+
+ table.sort(s, function(a, b)
+ return cmp(a[2], b[2])
+ end)
+
+ local i = 0
+ return function()
+ i = i + 1
+ local v = s[i]
+ if v then
+ return unpack(v)
+ else
+ return nil
+ end
+ end
+end
+
+function futil.table.pairs_by_key(t, cmp)
+ cmp = cmp or default_cmp
+ local s = {}
+ for k, v in pairs(t) do
+ table.insert(s, { k, v })
+ end
+
+ table.sort(s, function(a, b)
+ return cmp(a[1], b[1])
+ end)
+
+ local i = 0
+ return function()
+ i = i + 1
+ local v = s[i]
+ if v then
+ return unpack(v)
+ else
+ return nil
+ end
+ end
+end
+
+function futil.table.size(t)
+ local size = 0
+ for _ in pairs(t) do
+ size = size + 1
+ end
+ return size
+end
+
+function futil.table.is_empty(t)
+ return next(t) == nil
+end
+
+function futil.table.count_elements(t)
+ local counts = {}
+ for _, item in ipairs(t) do
+ counts[item] = (counts[item] or 0) + 1
+ end
+ return counts
+end
+
+function futil.table.sets_intersect(set1, set2)
+ for k in pairs(set1) do
+ if set2[k] then
+ return true
+ end
+ end
+
+ return false
+end
+
+function futil.table.iterate(t)
+ local i = 0
+ return function()
+ i = i + 1
+ return t[i]
+ end
+end
+
+function futil.table.reversed(t)
+ local len = #t
+ local reversed = {}
+
+ for i = len, 1, -1 do
+ reversed[len - i + 1] = t[i]
+ end
+
+ return reversed
+end
+
+function futil.table.contains(t, value)
+ for _, v in ipairs(t) do
+ if v == value then
+ return true
+ end
+ end
+
+ return false
+end
+
+function futil.table.keys(t)
+ local keys = {}
+ for key in pairs(t) do
+ keys[#keys + 1] = key
+ end
+ return keys
+end
+
+function futil.table.ikeys(t)
+ local key
+ return function()
+ key = next(t, key)
+ return key
+ end
+end
+
+function futil.table.values(t)
+ local values = {}
+ for _, value in pairs(t) do
+ values[#values + 1] = value
+ end
+ return values
+end
+
+function futil.table.sort_keys(t, cmp)
+ local keys = futil.table.keys(t)
+ table.sort(keys, cmp)
+ return keys
+end
+
+-- https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
+function futil.table.shuffle(t, rnd)
+ rnd = rnd or math.random
+ for i = #t, 2, -1 do
+ local j = rnd(i)
+ t[i], t[j] = t[j], t[i]
+ end
+ return t
+end
+
+local function swap(t, i, j)
+ t[i], t[j] = t[j], t[i]
+end
+
+futil.table.swap = swap
+
+function futil.table.get(t, key, default)
+ local value = t[key]
+ if value == nil then
+ return default
+ end
+ return value
+end
+
+function futil.table.setdefault(t, key, default)
+ local value = t[key]
+ if value == nil then
+ t[key] = default
+ return default
+ end
+ return value
+end
+
+function futil.table.pack(...)
+ return { n = select("#", ...), ... }
+end
diff --git a/mods/futil/util/time.lua b/mods/futil/util/time.lua
new file mode 100644
index 00000000..ec7de0cb
--- /dev/null
+++ b/mods/futil/util/time.lua
@@ -0,0 +1,29 @@
+local idiv = futil.math.idiv
+
+-- convert a number of seconds into a more human-readable value
+-- ignores the actual passage of time and assumes all years are 365 days
+function futil.seconds_to_interval(time)
+ local s, m, h, d
+
+ time, s = idiv(time, 60)
+ time, m = idiv(time, 60)
+ time, h = idiv(time, 24)
+ time, d = idiv(time, 365)
+
+ if time ~= 0 then
+ return ("%d years %d days %02d:%02d:%02d"):format(time, d, h, m, s)
+ elseif d ~= 0 then
+ return ("%d days %02d:%02d:%02d"):format(d, h, m, s)
+ elseif h ~= 0 then
+ return ("%02d:%02d:%02d"):format(h, m, s)
+ elseif m ~= 0 then
+ return ("%02d:%02d"):format(m, s)
+ else
+ return ("%ds"):format(s)
+ end
+end
+
+-- ISO 8601 date format
+function futil.format_utc(timestamp)
+ return os.date("!%Y-%m-%dT%TZ", timestamp)
+end
diff --git a/mods/ma_pops_furniture/MTBOX.MD b/mods/ma_pops_furniture/MTBOX.MD
new file mode 100644
index 00000000..49161d08
--- /dev/null
+++ b/mods/ma_pops_furniture/MTBOX.MD
@@ -0,0 +1,61 @@
+# ma_pops_furniture
+
+A revamp of Ma and Pop's Furniture Mod
+
+================================================================
+
+Credits:
+Radio Mod Code (Mihobre)
+BlockMen (Lots and lots) |For helping me alot with the code
+ |Letting me experiment with his mod
+ |Supporting me all the time
+ |Encouraging me
+DanJohansen(Newgrounds) |All music used
+
+Nathan.S Helped with a ton of the coding and models making this mod possible
+
+Freezer Mod:
+https://forum.minetest.net/viewtopic.php?f=9&t=14925
+
+Textures (c) 2016 Gabriel Pérez-Cerezo, WTFPL
+
+This mod is based on the default furnace, license for the code below:
+
+Copyright (C) 2011-2012 celeron55, Perttu Ahola
+Copyright (C) 2016 Gabriel Pérez-Cerezo
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+================================================================
+
+Special thanks to:
+Wizzerine
+BillyS
+Noodlemire
+
+For helping out with the microwave.
+
+=================================================================
+
+Each item used as tool and what it does:
+
+Hammer: Rotates and/or changes the type of sofa and bookshelf.
+
+Hammer1: Rotates and/or changes the type of counter and table.
+
+Shears: "Trims" the hedges making it possible for different rotation and direction of hedge.
+
+Saw: Used to turn a bookshelf into a wall bookshelf.
+
+==================================================================
+License of source code and textures: WTFPL
+------------------------------------------------------------------
+This program is free software. It comes without any warranty, to
+the extent permitted by applicable law. You can redistribute it
+and/or modify it under the terms of the do whatever you want with it.
+To Public License, Version 2, as published by Sam Hocevar. See
+http://sam.zoy.org/wtfpl/COPYING for more details.
+==================================================================
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/README.md b/mods/ma_pops_furniture/README.md
new file mode 100644
index 00000000..98c68638
--- /dev/null
+++ b/mods/ma_pops_furniture/README.md
@@ -0,0 +1,60 @@
+# ma_pops_furniture
+
+A revamp of Ma and Pop's Furniture Mod
+
+================================================================
+
+Credits:
+Radio Mod Code (Mihobre)
+BlockMen (Lots and lots) |For helping me alot with the code
+ |Letting me experiment with his mod
+ |Supporting me all the time
+ |Encouraging me
+DanJohansen(Newgrounds) |All music used
+
+Nathan.S Helped with a ton of the coding and models making this mod possible
+
+Toaster Code:
+Special thanks to GreenDimond for the code.
+
+Freezer Mod:
+https://forum.minetest.net/viewtopic.php?f=9&t=14925
+
+Textures (c) 2016 Gabriel Pérez-Cerezo, WTFPL
+
+This mod is based on the default furnace, license for the code below:
+
+Copyright (C) 2011-2012 celeron55, Perttu Ahola
+Copyright (C) 2016 Gabriel Pérez-Cerezo
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+================================================================
+
+Special thanks to:
+Wizzerine
+BillyS
+Noodlemire
+
+For helping out with the microwave.
+
+=================================================================
+
+Each item used as tool and what it does:
+
+Hammer: Rotates and/or changes the type of sofa.
+
+Shears: "Trims" the hedges making it possible for different rotation and direction of hedge.
+
+==================================================================
+License of source code and textures: WTFPL
+------------------------------------------------------------------
+This program is free software. It comes without any warranty, to
+the extent permitted by applicable law. You can redistribute it
+and/or modify it under the terms of the do whatever you want with it.
+To Public License, Version 2, as published by Sam Hocevar. See
+http://sam.zoy.org/wtfpl/COPYING for more details.
+==================================================================
diff --git a/mods/ma_pops_furniture/abm.lua b/mods/ma_pops_furniture/abm.lua
new file mode 100644
index 00000000..f4c05411
--- /dev/null
+++ b/mods/ma_pops_furniture/abm.lua
@@ -0,0 +1,54 @@
+minetest.register_abm({ -- Controls the contained fires.
+ nodenames = {'ma_pops_furniture:fireplace', 'ma_pops_furniture:fireplace_on'},
+ interval = 1.0,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local meta = minetest.env:get_meta(pos)
+ for i, name in ipairs({
+ 'fuel_totaltime',
+ 'fuel_time',
+ }) do
+ if meta:get_string(name) == '' then
+ meta:set_float(name, 0.0)
+ end
+ end
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ local was_active = false
+ if meta:get_float('fuel_time') < meta:get_float('fuel_totaltime') then
+ was_active = true
+ meta:set_float('fuel_time', meta:get_float('fuel_time') + 0.25)
+ end
+ if meta:get_float('fuel_time') < meta:get_float('fuel_totaltime') then
+ minetest.sound_play({name='fire_small'},{gain=0.07, pos = pos},
+ {loop=true})
+ local percent = math.floor(meta:get_float('fuel_time') /
+ meta:get_float('fuel_totaltime') * 100)
+ meta:set_string('infotext','Fireplace active: '..percent..'%')
+ minetest.swap_node(pos, {name = 'ma_pops_furniture:fireplace_on', param2 = node.param2})
+ meta:set_string('formspec', ma_pops_furniture.fireplace_formspec)
+ return
+ end
+ local fuel = nil
+ local fuellist = inv:get_list('fuel')
+ if fuellist then
+ fuel = minetest.get_craft_result({method = 'fuel', width = 1, items = fuellist})
+ end
+ if fuel.time <= 0 then
+ local node = minetest.get_node(pos)
+ if node.name == 'ma_pops_furniture:fireplace_on' then
+ meta:set_string('infotext','Put more wood in the fireplace!')
+ minetest.swap_node(pos, {name = 'ma_pops_furniture:fireplace', param2 = node.param2})
+ meta:set_string('formspec', ma_pops_furniture.fireplace_formspec)
+ local timer = minetest.get_node_timer(pos)
+ timer:start(190)
+ end
+ return
+ end
+ meta:set_string('fuel_totaltime', fuel.time)
+ meta:set_string('fuel_time', 0)
+ local stack = inv:get_stack('fuel', 1)
+ stack:take_item()
+ inv:set_stack('fuel', 1, stack)
+end,
+})
diff --git a/mods/ma_pops_furniture/bathroom.lua b/mods/ma_pops_furniture/bathroom.lua
new file mode 100644
index 00000000..18f78091
--- /dev/null
+++ b/mods/ma_pops_furniture/bathroom.lua
@@ -0,0 +1,282 @@
+minetest.register_node("ma_pops_furniture:bath_faucet", {
+ description = "Bathroom Faucet",
+ tiles = {
+ "mp_knob_top.png",
+ "mp_knob_bottom.png",
+ "mp_knob_right.png",
+ "mp_knob_left.png",
+ "mp_knob_back.png",
+ "mp_knob_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.0625, -0.5, 0.3125, 0.0625, -0.1875, 0.4375},
+ {-0.0625, -0.1875, 0.125, 0.0625, -0.125, 0.4375},
+ {0.125, -0.25, 0.25, 0.25, -0.0625, 0.4375},
+ {-0.25, -0.25, 0.25, -0.125, -0.0625, 0.4375},
+ {-0.0625, -0.25, 0.125, 0.0625, -0.125, 0.1875},
+ {-0.125, -0.1875, 0.3125, 0.125, -0.125, 0.375},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:toilet_paper_roll_dispenser", {
+ description = "Toilet Paper Roll Dispenser",
+ tiles = {
+ "mp_tp_top.png",
+ "mp_tp_bottom.png",
+ "mp_tp_right.png",
+ "mp_tp_left.png",
+ "mp_tp_back.png",
+ "mp_tp_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.3125, -0.1875, 0.4375, 0.3125, 0.125, 0.5},
+ {-0.1875, -0.125, 0.25, 0.1875, 0.0625, 0.4375},
+ {-0.25, -0.0625, 0.3125, 0.25, 0, 0.5},
+ },
+ }
+})
+
+minetest.register_node('ma_pops_furniture:toilet_open', {
+ description = 'Toilet',
+ drawtype = 'mesh',
+ mesh = 'FM_toilet_open.obj',
+ tiles = {{name='default_coral_skeleton.png'},{name='default_wood.png'}},
+ groups = {choppy=2, oddly_breakably_by_hand=2, furniture=1, not_in_creative_inventory=1},
+ --inventory_image = 'fm_chair_stone.png',
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ drop = 'ma_pops_furniture:toilet_close',
+ sounds = moditems.WOOD_SOUNDS,
+ selection_box = {
+ type = 'fixed',
+ fixed = {
+ {-.35, -.5, -.35, .35, 0, .5}, -- Right, Bottom, Back, Left, Top, Front
+ {-.35, 0, .2, .35, .5, .5},
+ }
+ },
+ collision_box = {
+ fixed = {
+ {-.35, -.5, -.35, .35, 0, .5}, -- Right, Bottom, Back, Left, Top, Front
+ {-.35, 0, .2, .35, .5, .5},
+ }
+ },
+ on_rightclick = function(pos, node, clicker)
+ ma_pops_furniture.sit(pos, node, clicker)
+ end,
+ on_punch = function (pos, node, puncher)
+ node.name = "ma_pops_furniture:toilet_close"
+ minetest.set_node(pos, node)
+ end,
+})
+
+minetest.register_node('ma_pops_furniture:toilet_close', {
+ description = 'Toilet',
+ drawtype = 'mesh',
+ mesh = 'FM_toilet_close.obj',
+ tiles = {{name='default_coral_skeleton.png'},{name='default_wood.png'}},
+ groups = {choppy=2, oddly_breakably_by_hand=2, furniture=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ selection_box = {
+ type = 'fixed',
+ fixed = {
+ {-.35, -.5, -.35, .35, 0, .5}, -- Right, Bottom, Back, Left, Top, Front
+ {-.35, 0, .2, .35, .5, .5},
+ }
+ },
+ collision_box = {
+ fixed = {
+ {-.35, -.5, -.35, .35, 0, .5}, -- Right, Bottom, Back, Left, Top, Front
+ {-.35, 0, .2, .35, .5, .5},
+ }
+ },
+ on_rightclick = function(pos, node, clicker)
+ ma_pops_furniture.sit(pos, node, clicker)
+ end,
+ on_punch = function (pos, node, puncher)
+ node.name = "ma_pops_furniture:toilet_open"
+ minetest.set_node(pos, node)
+ end,
+})
+
+minetest.register_node("ma_pops_furniture:br_sink", {
+ description = "Sink (Bathroom)",
+ tiles = {
+ "mp_hw_top.png",
+ "mp_hw_bottom.png",
+ "mp_hw_right.png",
+ "mp_hw_left.png",
+ "mp_hw_back.png",
+ "mp_hw_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, 0.25, -0.3125, 0.4375, 0.5, 0.5},
+ {-0.125, -0.5, 0.125, 0.125, 0.25, 0.4375},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:mirror_closed", {
+ description = "Mirror",
+ tiles = {
+ "mp_mirror_top.png",
+ "mp_mirror_bottom.png",
+ "mp_mirror_right.png",
+ "mp_mirror_left.png",
+ "default_wood.png",
+ "mp_mirror_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ on_punch = function(pos, node, puncher)
+ minetest.env:add_node(pos, {name = "ma_pops_furniture:mirror", param2 = node.param2})
+ ma_pops_furniture.window_operate( pos, "ma_pops_furniture:mirror_closed", "ma_pops_furniture:mirror" );
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.375, 0.3125, 0.4375, 0.5, 0.5},
+ {0, -0.375, 0.25, 0.4375, 0.5, 0.3125},
+ {-0.4375, -0.375, 0.25, 2.98023e-008, 0.5, 0.3125},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:mirror", {
+ description = "Mirror (Open)",
+ tiles = {
+ "mp_mirror_open_top.png",
+ "mp_mirror_open_bottom.png",
+ "mp_mirror_open_right.png",
+ "mp_mirror_open_left.png",
+ "mp_mirror_front.png",
+ "mp_mirror_open_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ drop = "ma_pops_furniture:mirror_closed",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1},
+ on_punch = function(pos, node, puncher)
+ minetest.env:add_node(pos, {name = "ma_pops_furniture:mirror_closed", param2 = node.param2})
+ ma_pops_furniture.window_operate( pos, "ma_pops_furniture:mirror", "ma_pops_furniture:mirror_closed" );
+ end,
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 4*4)
+ meta:set_string('formspec',
+ 'size [9,10]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;3,1.5;3,3;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.375, 0.3125, 0.4375, 0.5, 0.5},
+ {0.4375, -0.375, -0.125, 0.5, 0.5, 0.3125},
+ {-0.5, -0.375, -0.125, -0.4375, 0.5, 0.3125},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:shower_base", {
+ description = "Shower Base",
+ tiles = {
+ "mp_showbas_top.png",
+ "mp_showbas_top.png",
+ "mp_showbas_sides.png",
+ "mp_showbas_sides.png",
+ "mp_showbas_sides.png",
+ "mp_showbas_sides.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.5, -0.4375, 0.4375, -0.4375, 0.4375},
+ {0.4375, -0.5, -0.5, 0.5, -0.3125, 0.5},
+ {-0.5, -0.5, 0.4375, 0.5, -0.3125, 0.5},
+ {-0.5, -0.5, -0.5, -0.4375, -0.3125, 0.5},
+ {-0.5, -0.5, -0.5, 0.5, -0.3125, -0.4375},
+ {-0.125, -0.5, 0.125, 0.125, -0.375, 0.375},
+ }
+ },
+})
+
+minetest.register_node("ma_pops_furniture:shower_top", {
+ description = "Shower Head",
+ tiles = {
+ "mp_shk_top.png",
+ "mp_shk_bottom.png",
+ "mp_shk_right.png",
+ "mp_shk_left.png",
+ "mp_shk_back.png",
+ "mp_shk_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, 0.4375, 0.25, 0.5, 0.5},
+ {-0.125, 0.3125, -0.1875, 0.125, 0.4375, 0.25},
+ {-0.1875, -0.25, 0.375, -0.125, -0.1875, 0.4375},
+ {0.125, -0.25, 0.375, 0.1875, -0.1875, 0.4375},
+ {-0.1875, -0.25, 0.3125, -0.125, -0.0625, 0.375},
+ {0.125, -0.25, 0.3125, 0.1875, -0.0625, 0.375},
+ {-0.0625, 0.375, 0.25, 0.0625, 0.4375, 0.4375},
+ },
+ }
+})
+
+minetest.register_node('ma_pops_furniture:br_tile', {
+ description = 'Bathroom Tile',
+ drawtype = 'nodebox',
+ tiles = {
+ "mp_bathroom_tile.png"
+ },
+ groups = {cracky=2, oddly_breakable_by_hand=5, furniture=1},
+ paramtype = 'light',
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
+ },
+ }
+})
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/bedroom.lua b/mods/ma_pops_furniture/bedroom.lua
new file mode 100644
index 00000000..036c1762
--- /dev/null
+++ b/mods/ma_pops_furniture/bedroom.lua
@@ -0,0 +1,51 @@
+local night_table = { --name, material, invimg
+{'Wood Nightstand', 'wood'},
+{'Acacia Wood Nightstand', 'acacia_wood'},
+{'Aspen Wood Nightstand', 'aspen_wood'},
+{'Pine Wood Nightstand', 'pine_wood'},
+{'Jungle Wood Nightstand', 'junglewood'}
+}
+
+for i in ipairs (night_table) do
+ local name = night_table[i][1]
+ local material = night_table[i][2]
+ local invimg = night_table[i][3]
+
+minetest.register_node('ma_pops_furniture:nightstand_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ groups = {choppy=2, oddly_breakably_by_hand=2, furniture=1, flammable=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 3*3)
+ meta:set_string('formspec',
+ 'size [9,10]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;3,1.5;3,3;]'..
+ 'list[current_player;main;0.5,6.2;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.4375, 0.5, -0.4375, 0.5}, -- NodeBox1
+ {-0.5, 0.4375, -0.4375, 0.5, 0.5, 0.5}, -- NodeBox2
+ {-0.5, -0.4375, -0.375, 0.5, 0.4375, 0.5}, -- NodeBox3
+ {-0.4375, 0.0625, -0.4375, 0.4375, 0.375, -0.375}, -- NodeBox4
+ {-0.4375, -0.375, -0.4375, 0.4375, -0.0625, -0.375}, -- NodeBox5
+ {-0.125, -0.3125, -0.5, 0.125, -0.125, -0.4375}, -- NodeBox6
+ {-0.125, 0.125, -0.5, 0.125, 0.3125, -0.4375}, -- NodeBox7
+ }
+ }
+})
+end
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/crafts.lua b/mods/ma_pops_furniture/crafts.lua
new file mode 100644
index 00000000..70c7a71e
--- /dev/null
+++ b/mods/ma_pops_furniture/crafts.lua
@@ -0,0 +1,1035 @@
+minetest.register_craft({
+ output = 'ma_pops_furniture:smoke_detector',
+ recipe = {
+ {'default:stone','dye:white','default:stone',},
+ {'default:stone','default:copper_ingot','default:stone',},
+ {'default:stone','dye:red','default:stone',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:br_tile',
+ recipe = {
+ {'dye:black','dye:white','dye:black',},
+ {'','default:stone_block','',},
+ {'dye:black','','dye:black',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:ceiling_lamp',
+ recipe = {
+ {'', 'default:stone', ''},
+ {'default:stone', 'default:meselamp', 'default:stone'},
+ {'default:stone', 'default:meselamp', 'default:stone'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:outdoor_lamp',
+ recipe = {
+ {'','','',},
+ {'default:stone','default:stone','default:stone',},
+ {'default:stone','default:meselamp','default:stone',},
+ }
+})
+--changed bathroom_faucet to bath_faucet and added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:bath_faucet',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','','bucket:bucket_water',},
+ {'default:steel_ingot','','',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:toilet_paper_roll_dispenser',
+ recipe = {
+ {'default:stone','default:stone','default:stone',},
+ {'default:paper','bucket:water','default:paper',},
+ {'','default:paper','',},
+ }
+})
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:toilet_close',
+ recipe = {
+ {'','','default:steel_ingot',},
+ {'default:steel_ingot','stairs:slab_wood','default:steel_ingot',},
+ {'default:steel_ingot','bucket:bucket_water','default:steel_ingot',},
+ }
+})
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:br_sink',
+ recipe = {
+ {'default:steel_ingot','','default:steel_ingot',},
+ {'','default:steel_ingot','',},
+ {'','default:steel_ingot','',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:mirror_closed',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:glass','default:glass','default:glass',},
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ }
+})
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:shower_base',
+ recipe = {
+ {'','','',},
+ {'','','',},
+ {'default:steel_ingot','bucket:bucket_empty','default:steel_ingot',},
+ }
+})
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:shower_top',
+ recipe = {
+ {'','default:steel_ingot','',},
+ {'default:steel_ingot','bucket:bucket_water','default:steel_ingot',},
+ {'default:steel_ingot','','default:steel_ingot',},
+ }
+})
+
+local night_table = { --name, material, invimg
+{'wood'},
+{'aspen_wood'},
+{'junglewood'},
+{'acacia_wood'},
+{'pine_wood'},
+{'cobble'}
+}
+
+for i in ipairs (night_table) do
+ local material = night_table[i][1]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:nightstand_'..material,
+ recipe = {
+ {'default:'..material, 'default:'..material, 'default:'..material},
+ {'default:'..material, 'default:chest', 'default:'..material},
+ {'default:'..material, 'default:'..material, 'default:'..material}
+ }
+})
+end
+
+local chair = { --name, material, invimg
+{'wood'},
+{'aspen_wood'},
+{'junglewood'},
+{'acacia_wood'},
+{'pine_wood'},
+{'cobble'}
+}
+
+for i in ipairs (chair) do
+ local material = chair[i][1]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair_'..material,
+ recipe = {
+ {'default:'..material, '', ''},
+ {'default:'..material, 'default:'..material, 'default:'..material},
+ {'default:'..material, '', 'default:'..material}
+ }
+})
+end
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:barrel',
+ recipe = {
+ {'default:wood','default:wood','default:wood',},
+ {'default:wood','default:steel_ingot','default:wood',},
+ {'default:wood','default:wood','default:wood',},
+ }
+})
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:dw',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','bucket:bucket_water','default:steel_ingot',},
+ {'default:steel_ingot','default:mese_crystal','default:steel_ingot',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:oven',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','default:furnace','default:steel_ingot',},
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ }
+})
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:oven_overhead',
+ recipe = {
+ {'default:steel_ingot','default:mese_crystal_fragment','default:steel_ingot',},
+ {'','','',},
+ {'','','',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:microwave',
+ recipe = {
+ {'','','',},
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','default:furnace','default:steel_ingot',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:coffee_maker',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','default:copper_ingot','default:steel_ingot',},
+ {'','default:glass','',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:coffee_cup',
+ recipe = {
+ {'default:glass','dye:blue','default:glass',},
+ {'default:glass','dye:blue','default:glass',},
+ {'default:glass','default:glass','default:glass',},
+ }
+})
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:toaster',
+ recipe = {
+ {'','','',},
+ {'default:steel_ingot','default:furnace','default:steel_ingot',},
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:trash_can',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','bucket:bucket_lava','default:steel_ingot',},
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ }
+})
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:kitchen_faucet',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','','default:steel_ingot',},
+ {'default:steel_ingot','','',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:cutting_board',
+ recipe = {
+ {'','','',},
+ {'','','',},
+ {'default:wood','default:wood','',},
+ }
+})
+local counter_table = { --name, color, colorize(hex or color name:intensity(1-255))
+{'Black', 'black', 'black:225'},
+{'Blue', 'blue', 'blue:150'},
+{'Brown', 'brown', 'brown:100'},
+{'Cyan', 'cyan', 'cyan:150'},
+{'Dark Green', 'dark_green', 'green:200'},
+--{'Dark Grey', 'dark_grey', 'black:200'},
+{'Green', 'green', '#32cd32:150'},
+--{'Grey', 'grey', 'black:150'},
+{'Magenta', 'magenta', 'magenta:200'},
+{'Orange', 'orange', 'orange:150'},
+{'Pink', 'pink', 'pink:150'},
+{'Red', 'red', 'red:150'},
+{'Violet', 'violet', 'violet:150'},
+{'White', 'white', 'white:150'},
+{'Yellow', 'yellow', 'yellow:150'},
+}
+
+for i in ipairs (counter_table) do
+ local name = counter_table[i][1]
+ local color = counter_table[i][2]
+ local hex = counter_table[i][3]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:counter2_'..color,
+ recipe = {
+ {'group:wood','group:wood','group:wood',},
+ {'group:wood','dye:'..color,'group:wood',},
+ {'group:wood','group:wood','group:wood',},
+ }
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:counter1_'..color,
+ recipe =
+ {'ma_pops_furniture:counter2_'..color}
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:counter3_'..color,
+ recipe =
+ {'ma_pops_furniture:counter2_'..color, "default:chest"}
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:counter_'..color,
+ recipe =
+ {'ma_pops_furniture:counter3_'..color}
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:upcabinet_'..color,
+ recipe = {
+ {'group:wood','dye:'..color,'group:wood',},
+ {'group:wood','default:chest','group:wood',},
+ {'group:wood','group:wood','group:wood',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:upcabinet_corner',
+ recipe = {
+ {'group:wood','group:wood','group:wood',},
+ {'group:wood','group:wood','default:chest',},
+ {'group:wood','dye:'..color,'',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:sink_'..color,
+ recipe = {
+ {'ma_pops_furniture:br_sink','ma_pops_furniture:counter_'..color,},
+ }
+})
+end
+
+local counter_table = { --name, material
+{'Wooden', 'wood'},
+{'Acacia', 'acacia_wood'},
+{'Aspen', 'aspen_wood'},
+{'Jungle', 'junglewood' },
+{'Pine', 'pine_wood'},
+}
+
+for i in ipairs (counter_table) do
+ local name = counter_table[i][1]
+ local material = counter_table[i][2]
+ local hex = counter_table[i][3]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:counter2_'..material,
+ recipe = {
+ {'default:'..material,'default:'..material,'default:'..material,},
+ {'default:'..material, 'ma_pops_furniture:barrel','default:'..material,},
+ {'default:'..material,'default:'..material,'default:'..material,},
+ }
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:counter3_'..material,
+ recipe =
+ {'ma_pops_furniture:counter2_'..material, "ma_pops_furniture:barrel"}
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:counter_'..material,
+ recipe =
+ {'ma_pops_furniture:counter3_'..material}
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:counter1_'..material,
+ recipe =
+ {'ma_pops_furniture:counter2_'..material}
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:upcabinet_'..material,
+ recipe = {
+ {'default:'..material,'','default:'..material,},
+ {'default:'..material,'default:chest','default:'..material,},
+ {'default:'..material,'default:'..material,'default:'..material,},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:upcabinet_corner',
+ recipe = {
+ {'default:'..material,'default:'..material,'default:'..material,},
+ {'default:'..material,'default:'..material,'default:chest',},
+ {'default:'..material,'','',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:sink_'..material,
+ recipe = {
+ {'ma_pops_furniture:br_sink','ma_pops_furniture:counter_'..material,},
+ }
+})
+
+end
+
+local chair2_table = { --color
+{'black'},
+{'blue'},
+{'brown'},
+{'cyan'},
+{'dark_green'},
+{'dark_grey'},
+{'green'},
+{'grey'},
+{'magenta'},
+{'orange'},
+{'pink'},
+{'red'},
+{'violet'},
+{'yellow'},
+}
+
+for i in ipairs (chair2_table) do
+ local color = chair2_table[i][1]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_'..color,
+ recipe = {
+ {'wool:'..color, 'wool:'..color, 'wool:'..color, },
+ {'wool:'..color, 'wool:'..color, 'wool:'..color, },
+ {'group:wood', '', 'group:wood', },
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_'..color,
+ recipe = {
+ {'ma_pops_furniture:chair2_white', 'dye:'..color}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_white',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:white'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_black',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:black'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_blue',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:blue'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_brown',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:brown'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_cyan',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:cyan'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_dark_grey',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:dark_grey'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_grey',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:grey'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_green',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:green'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_magenta',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:magenta'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_orange',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:orange'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_pink',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:pink'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_red',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:red'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_violet',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:violet'}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_yellow',
+ recipe = {
+ {'ma_pops_furniture:chair2_'..color, 'dye:yellow'}
+ }
+})
+end
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_white',
+ recipe = {
+ {'wool:white', 'wool:white', 'wool:white', },
+ {'wool:white', 'wool:white', 'wool:white', },
+ {'group:wood', '', 'group:wood', },
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:chair2_rainbow',
+ recipe = {
+ {'wool:black', '', '', },
+ {'wool:blue', 'wool:yellow', 'wool:pink', },
+ {'default: acacia_tree', '', 'default: acacia_tree', },
+ }
+})
+
+local sofa_table = { --color
+{'black'},
+{'blue'},
+{'brown'},
+{'cyan'},
+{'dark_green'},
+{'dark_grey'},
+{'green'},
+{'grey'},
+{'magenta'},
+{'orange'},
+{'pink'},
+{'red'},
+{'violet'},
+{'white'},
+{'yellow'},
+}
+
+for i in ipairs (sofa_table) do
+ local color = sofa_table[i][1]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:sofa_'..color,
+ recipe = {
+ {'', '', '', },
+ {'wool:'..color, 'wool:'..color, 'wool:'..color, },
+ {'wool:'..color, 'wool:'..color, 'wool:'..color, },
+ }
+})
+end
+
+local sofa_table = { --color
+{'black'},
+{'blue'},
+{'brown'},
+{'cyan'},
+{'dark_green'},
+{'dark_grey'},
+{'green'},
+{'grey'},
+{'magenta'},
+{'orange'},
+{'pink'},
+{'red'},
+{'violet'},
+{'yellow'},
+}
+
+for i in ipairs (sofa_table) do
+ local color = sofa_table[i][1]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:sofa_'..color,
+ recipe = {
+ {'ma_pops_furniture:sofa_white', 'dye:'..color}
+ }
+})
+end
+
+local fs_table = { -- colors
+ 'black', 'blue', 'brown', 'cyan', 'dark_green', 'dark_grey', 'green',
+ 'grey', 'magenta', 'orange', 'pink', 'red', 'violet', 'yellow'
+}
+
+-- Register crafting recipes for creating the initial white curtains
+minetest.register_craft({
+ output = 'ma_pops_furniture:fs_white',
+ recipe = {
+ {'wool:white', 'wool:white', 'wool:white'},
+ {'group:wood', '', 'group:wood'},
+ }
+})
+
+-- Register crafting recipes for creating colored curtains
+for _, color in ipairs(fs_table) do
+ minetest.register_craft({
+ output = 'ma_pops_furniture:fs_' .. color,
+ recipe = {
+ {'wool:' .. color, 'wool:' .. color, 'wool:' .. color},
+ {'group:wood', '', 'group:wood'},
+ }
+ })
+end
+
+-- Register crafting recipes for dyeing curtains
+for _, color in ipairs(fs_table) do
+ minetest.register_craft({
+ output = 'ma_pops_furniture:fs_' .. color,
+ recipe = {
+ {'ma_pops_furniture:fs_white', 'dye:' .. color}
+ }
+ })
+end
+
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:fs_rainbow',
+ recipe = {
+ {'', '', '', },
+ {'wool:blue', 'wool:yellow', 'wool:pink', },
+ {'default: acacia_tree', '', 'default: acacia_tree', },
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:vcr_off',
+ recipe = {
+ {'','','',},
+ {'default:coalblock','default:coalblock','default:coalblock',},
+ {'default:coalblock','default:mese_crystal','default:coalblock',},
+ }
+})
+
+local unit_table = { --name, material
+{'Wood Entertainment Unit', 'wood'},
+{'Acacia Wood Entertainment Unit', 'acacia_wood'},
+{'Aspen Wood Entertainment Unit', 'aspen_wood'},
+{'Pine Wood Entertainment Unit', 'pine_wood'},
+{'Jungle Wood Entertainment Unit', 'junglewood'}
+}
+
+for i in ipairs (unit_table) do
+ local name = unit_table[i][1]
+ local material = unit_table[i][2]
+ local invimg = unit_table[i][3]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:e_u_'..material,
+ recipe = {
+ {'default:'..material,'default:'..material,'default:'..material,},
+ {'default:'..material,'default:chest','default:'..material,},
+ {'default:'..material,'','default:'..material,},
+ }
+})
+end
+
+minetest.register_craft({
+ output = "ma_pops_furniture:trampoline",
+ recipe = {
+ {"farming:string", "farming:string", "farming:string"},
+ {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
+ {"default:steel_ingot", "", "default:steel_ingot"}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:fireplace',
+ recipe = {
+ {'default:brick', 'default:brick', 'default:brick'},
+ {'default:brick', 'default:furnace', 'default:brick'},
+ {'default:brick', 'default:brick', 'default:brick'}
+ }
+})
+
+local lamp_table = { --name, color, colorize(hex or color name:intensity(1-255))
+{'Black', 'black', 'black:225'},
+{'Blue', 'blue', 'blue:225'},
+{'Brown', 'brown', 'brown:225'},
+{'Cyan', 'cyan', 'cyan:200'},
+{'Dark Green', 'dark_green', 'green:225'},
+{'Dark Grey', 'dark_grey', 'black:200'},
+{'Green', 'green', '#32cd32:150'},
+{'Grey', 'grey', 'black:100'},
+{'Magenta', 'magenta', 'magenta:200'},
+{'Orange', 'orange', 'orange:225'},
+{'Pink', 'pink', 'pink:225'},
+{'Red', 'red', 'red:225'},
+{'Violet', 'violet', 'violet:225'},
+{'White', 'white', 'white:1'},
+{'Yellow', 'yellow', 'yellow:225'},
+}
+
+for i in ipairs (lamp_table) do
+ local name = lamp_table[i][1]
+ local color = lamp_table[i][2]
+ local hex = lamp_table[i][3]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:lamp_off_'..color,
+ recipe = {
+ {'wool:white','wool:white', 'wool:white'},
+ {'wool:white', 'default:torch', 'wool:white'},
+ {'wool:'..color, 'wool:'..color, 'wool:'..color}
+ }
+})
+end
+
+local curtain_table = { --name, color, colorize(hex or color name:intensity(1-255))
+{'Black', 'black', 'black:225'},
+{'Blue', 'blue', 'blue:225'},
+{'Brown', 'brown', 'brown:225'},
+{'Cyan', 'cyan', 'cyan:200'},
+{'Dark Green', 'dark_green', 'green:225'},
+{'Dark Grey', 'dark_grey', 'black:200'},
+{'Green', 'green', '#32cd32:150'},
+{'Grey', 'grey', 'black:100'},
+{'Magenta', 'magenta', 'magenta:200'},
+{'Orange', 'orange', 'orange:225'},
+{'Pink', 'pink', 'pink:225'},
+{'Red', 'red', 'red:225'},
+{'Violet', 'violet', 'violet:225'},
+{'White', 'white', 'white:1'},
+{'Yellow', 'yellow', 'yellow:225'},
+}
+
+for i in ipairs (curtain_table) do
+ local name = curtain_table[i][1]
+ local color = curtain_table[i][2]
+ local hex = curtain_table[i][3]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:curtains_'..color,
+ recipe = {
+ {'default:acacia_tree','default:acacia_tree', 'default:acacia_tree'},
+ {'wool:'..color, '', 'wool:'..color},
+ {'wool:'..color, '', 'wool:'..color}
+ }
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:curtains_2_tall_'..color,
+ recipe =
+ {'ma_pops_furniture:curtains_'..color, 'ma_pops_furniture:curtains_'..color}
+})
+end
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:blinds',
+ recipe = {
+ {'default:stick', 'default:stick', 'default:stick'},
+ {'default:stick', 'dye:white', 'default:stick'},
+ {'default:stick', 'default:stick', 'default:stick'}
+ }
+})
+
+minetest.register_craft({
+ output = "ma_pops_furniture:stereo",
+ recipe = {
+ {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot", },
+ {"default:steel_ingot", "default:chest", "default:steel_ingot", },
+ {"default:stick", "", "default:stick", }
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:tv_off',
+ recipe = {
+ {'default:tree', 'default:tree', 'default:tree'},
+ {'default:tree', 'wool:black', 'default:tree'},
+ {'default:tree', 'default:tree', 'default:tree'}
+ }
+})
+
+local c_table = { --name, material, invimg
+{'wood'},
+{'aspen_wood'},
+{'junglewood'},
+{'acacia_wood'},
+{'pine_wood'},
+{'cobble'}
+}
+
+for i in ipairs (c_table) do
+ local material = c_table[i][1]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:c_'..material,
+ recipe = {
+ {'', '', ''},
+ {'default:'..material, 'default:'..material, 'default:'..material},
+ {'default:'..material, '', 'default:'..material}
+ }
+})
+end
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:computer',
+ recipe = {
+ {'default:stone','default:stone','default:stone',},
+ {'default:glass','default:mese_crystal','default:stone',},
+ {'default:stone','default:copper_lump','default:stone',},
+ }
+})
+
+local materials = { -- name
+ 'wood',
+ 'aspen_wood',
+ 'junglewood',
+ 'acacia_wood',
+ 'pine_wood',
+ 'cobble'
+}
+
+for _, material in ipairs(materials) do
+ -- Recipe using specific material slabs
+ minetest.register_craft({
+ output = 'ma_pops_furniture:table_' .. material,
+ recipe = {
+ {'stairs:slab_' .. material, 'stairs:slab_' .. material, 'stairs:slab_' .. material},
+ {'', 'default:stick', ''},
+ {'', 'default:stick', ''}
+ }
+ })
+end
+
+-- Optional: A generic recipe for 'wood' material (if needed)
+minetest.register_craft({
+ output = 'ma_pops_furniture:table_wood',
+ recipe = {
+ {'stairs:slab_wood', 'stairs:slab_wood', 'stairs:slab_wood'},
+ {'', 'default:stick', ''},
+ {'', 'default:stick', ''}
+ }
+})
+
+
+local hedge_table = { --name, material, invimg
+{'leaves'},
+{'pine_needles'},
+{'jungleleaves'},
+{'acacia_leaves'},
+{'aspen_leaves'}
+}
+
+for i in ipairs (hedge_table) do
+ local material = hedge_table[i][1]
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:hedge_'..material,
+ recipe = {
+ {'', '', ''},
+ {'default:'..material, 'default:'..material, 'default:'..material},
+ {'default:'..material, 'default:'..material, 'default:'..material}
+ }
+})
+end
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:birdbath',
+ recipe = {
+ {'default:stone','bucket:bucket_water','default:stone',},
+ {'','default:stone','',},
+ {'default:stone','default:stone','default:stone',},
+ }
+})
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:tile_kitchen',
+ recipe = {
+ {'default:stone_block','dye:white','default:stone_block',},
+ {'dye:black','default:stone_block','dye:black',},
+ {'default:stone_block','dye:white','default:stone_block',},
+ }
+})
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:tile_floor_kitchen',
+ recipe = {
+ {'default:stone_block','ma_pops_furniture:hammer',},
+ }
+})
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:doorbell 4',
+ recipe = {
+ {'','default:stone','',},
+ {'','default:mese_crystal','',},
+ {'','','',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:ac',
+ recipe = {
+ {'default:coral_skeleton','default:coral_skeleton','default:coral_skeleton',},
+ {'default:coral_skeleton','ma_pops_furniture:fan_blade','default:coral_skeleton',},
+ {'default:coral_skeleton','default:mese_crystal','default:coral_skeleton',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:fan_off',
+ recipe = {
+ {'default:coral_skeleton','default:coral_skeleton','default:coral_skeleton',},
+ {'default:coral_skeleton','ma_pops_furniture:fan_blade','default:coral_skeleton',},
+ {'default:coral_skeleton','default:coral_skeleton','default:coral_skeleton',},
+ }
+})
+
+minetest.register_craftitem("ma_pops_furniture:fan_blade", {
+ description = 'Fan Blade',
+ inventory_image = "mp_blade.png",
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:fan_blade',
+ recipe = {
+ {'default:coral_skeleton','','default:coral_skeleton',},
+ {'','default:coral_skeleton','',},
+ {'default:coral_skeleton','','default:coral_skeleton',},
+ }
+})
+
+minetest.register_craftitem("ma_pops_furniture:knife", {
+ description = 'Knife',
+ inventory_image = "mp_knife.png",
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:knife',
+ recipe = {
+ {'default:steel_ingot','','',},
+ {'','default:steel_ingot','',},
+ {'','','default:stick',},
+ }
+})
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:grill',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','','default:steel_ingot',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:fridge_white',
+ recipe = {
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ {'default:steel_ingot','default:snow','default:steel_ingot',},
+ {'default:steel_ingot','default:steel_ingot','default:steel_ingot',},
+ }
+})
+
+--added craft
+local fridges_list = {
+ {"black", "Darkened Fridge", color1},
+ {"blue", "Blue Fridge", color2},
+ {"green", "Green Fridge", color3},
+ {"orange", "Orange Fridge", color5},
+ {"red", "Red Fridge", color6},
+ {"yellow", "Yellow Fridge", color7},
+ {"pink", "Pink Fridge", color8}
+}
+
+for i, fridge in ipairs(fridges_list) do
+ local colour = fridge[1]
+ local fridgedesc = fridge[2]
+ local colour2 = fridge[3]
+
+minetest.register_craft({
+ type = "shapeless",
+ output = 'ma_pops_furniture:fridge_'..colour,
+ recipe =
+ {'ma_pops_furniture:fridge_white', 'dye:'..colour}
+})
+end
+
+--added craft
+minetest.register_craft({
+ output = 'ma_pops_furniture:stone_path_1 5',
+ recipe = {
+ {'default:stone','default:stone',},
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:hammer',
+ recipe = {
+ {'','default:steel_ingot', ''},
+ {'', 'default:stick', 'default:steel_ingot'},
+ {'default:stick', '', ''}
+ }
+})
+
+minetest.register_craft({
+ output = 'ma_pops_furniture:shears',
+ recipe = {
+ {'','default:steel_ingot', ''},
+ {'default:stick', '', 'default:steel_ingot'},
+ {'', 'default:stick', ''}
+ }
+})
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/dining_room.lua b/mods/ma_pops_furniture/dining_room.lua
new file mode 100644
index 00000000..4150395c
--- /dev/null
+++ b/mods/ma_pops_furniture/dining_room.lua
@@ -0,0 +1,189 @@
+local chair_table = { --name, material, invimg
+{'Stone Chair', 'cobble', 'mp_chair_stone.png'},
+{'Wood Chair', 'wood', 'mp_chair_wood.png'},
+{'Acacia Wood Chair', 'acacia_wood', 'mp_chair_acacia_wood.png'},
+{'Aspen Wood Chair', 'aspen_wood', 'mp_chair_aspen_wood.png'},
+{'Pine Wood Chair', 'pine_wood', 'mp_chair_pine_wood.png'},
+{'Jungle Wood Chair', 'junglewood', 'mp_chair_junglewood.png'}
+}
+
+for i in ipairs (chair_table) do
+ local name = chair_table[i][1]
+ local material = chair_table[i][2]
+ local invimg = chair_table[i][3]
+
+minetest.register_node('ma_pops_furniture:chair_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ groups = {choppy=2, oddly_breakably_by_hand=2, furniture=1, flammable=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.5, 0.3125, -0.3125, -0.0625, 0.4375}, -- NodeBox10
+ {0.3125, -0.5, 0.3125, 0.4375, -0.0625, 0.4375}, -- NodeBox11
+ {-0.4375, -0.5, -0.4375, -0.3125, -0.0625, -0.3125}, -- NodeBox12
+ {0.3125, -0.5, -0.4375, 0.4375, -0.0625, -0.3125}, -- NodeBox13
+ {-0.4375, -0.0625, -0.4375, 0.4375, 0.0625, 0.4375}, -- NodeBox14
+ {-0.4375, 0.0625, 0.3125, 0.4375, 0.8125, 0.4375}, -- NodeBox15
+ }
+ }
+})
+end
+
+local table_table = { --name, material, invimg
+{'Stone Table', 'cobble', 'mp_table_stone.png'},
+{'Wood Table', 'wood', 'mp_table_wood.png'},
+{'Acacia Wood Table', 'acacia_wood', 'mp_table_wood_acacia.png'},
+{'Aspen Wood Table', 'aspen_wood', 'mp_table_wood_aspen.png'},
+{'Pine Wood Table', 'pine_wood', 'mp_table_wood_pine.png'},
+{'Jungle Wood Table', 'junglewood', 'mp_table_wood_jungle.png'}
+}
+
+for i in ipairs (table_table) do
+ local name = table_table[i][1]
+ local material = table_table[i][2]
+ local invimg = table_table[i][3]
+
+minetest.register_node('ma_pops_furniture:table_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ align_style="world",
+ groups = {snappy = 2, oddly_breakable_by_hand = 2, furniture = 1, flammable = 1, table = 1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.125, -0.5, -0.125, 0.125, 0.375, 0.125}, -- NodeBox8
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5}, -- NodeBox9
+ }
+ },
+
+ after_dig_node = function(pos) ma_pops_furniture.check_table(pos, material, false, true) end,
+ after_place_node = function(pos) ma_pops_furniture.check_table(pos, material, true, true) end,
+ on_punch = function(pos) ma_pops_furniture.check_table(pos, material, true, true) end
+})
+end
+
+local table2_table = { --name, material, invimg
+{'Stone Table', 'cobble', 'mp_table_stone.png'},
+{'Wood Table', 'wood', 'mp_table_wood.png'},
+{'Acacia Wood Table', 'acacia_wood', 'mp_table_wood_acacia.png'},
+{'Aspen Wood Table', 'aspen_wood', 'mp_table_wood_aspen.png'},
+{'Pine Wood Table', 'pine_wood', 'mp_table_wood_pine.png'},
+{'Jungle Wood Table', 'junglewood', 'mp_table_wood_jungle.png'}
+}
+
+for i in ipairs (table2_table) do
+ local name = table2_table[i][1]
+ local material = table2_table[i][2]
+ local invimg = table2_table[i][3]
+
+minetest.register_node('ma_pops_furniture:table2_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ align_style="world",
+ groups = {snappy = 2, oddly_breakable_by_hand = 2, furniture = 1, flammable = 1, table = 1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5}, -- NodeBox5
+ {-0.4375, -0.5, -0.4375, -0.3125, 0.375, -0.3125}, -- NodeBox6
+ {-0.4375, -0.5, 0.3125, -0.3125, 0.375, 0.4375}, -- NodeBox7
+ {0.3125, -0.5, 0.3125, 0.4375, 0.375, 0.4375}, -- NodeBox8
+ {0.3125, -0.5, -0.4375, 0.4375, 0.375, -0.3125}, -- NodeBox9
+ }
+ },
+})
+end
+
+local table_c_table = { --name, material, invimg
+{'Stone Corner Table', 'cobble', 'mp_table_stone.png'},
+{'Wood Corner Table', 'wood', 'mp_table_wood.png'},
+{'Acacia Corner Wood Table', 'acacia_wood', 'mp_table_wood_acacia.png'},
+{'Aspen Corner Wood Table', 'aspen_wood', 'mp_table_wood_aspen.png'},
+{'Pine Corner Wood Table', 'pine_wood', 'mp_table_wood_pine.png'},
+{'Jungle Corner Wood Table', 'junglewood', 'mp_table_wood_jungle.png'}
+}
+
+for i in ipairs (table_c_table) do
+ local name = table_c_table[i][1]
+ local material = table_c_table[i][2]
+ local invimg = table_c_table[i][3]
+
+minetest.register_node('ma_pops_furniture:table_c_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ align_style="world",
+ groups = {snappy = 2, oddly_breakable_by_hand = 2, furniture = 1, flammable = 1, table = 1, not_in_creative_inventory = 1},
+ drop = 'ma_pops_furniture:table_'..material,
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.3125, -0.5, 0.5, 0.5, 0.5}, -- NodeBox1
+ {-0.375, -0.5, -0.125, -0.125, 0.3125, 0.125}, -- NodeBox3
+ }
+ },
+
+ after_dig_node = function(pos) ma_pops_furniture.check_table(pos, material, false, true) end,
+ after_place_node = function(pos) ma_pops_furniture.check_table(pos, material, true, true) end,
+ on_punch = function(pos) ma_pops_furniture.check_table(pos, material, true, true) end
+})
+end
+
+local table_center_table = { --name, material, invimg
+{'Stone Center Table', 'cobble', 'mp_table_stone.png'},
+{'Wood Center Table', 'wood', 'mp_table_wood.png'},
+{'Acacia Center Wood Table', 'acacia_wood', 'mp_table_wood_acacia.png'},
+{'Aspen Center Wood Table', 'aspen_wood', 'mp_table_wood_aspen.png'},
+{'Pine Center Wood Table', 'pine_wood', 'mp_table_wood_pine.png'},
+{'Jungle Center Wood Table', 'junglewood', 'mp_table_wood_jungle.png'}
+}
+
+for i in ipairs (table_center_table) do
+ local name = table_center_table[i][1]
+ local material = table_center_table[i][2]
+ local invimg = table_center_table[i][3]
+
+minetest.register_node('ma_pops_furniture:table_center_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ align_style="user",
+ groups = {snappy = 2, oddly_breakable_by_hand = 2, furniture = 1, flammable = 1, table = 1, not_in_creative_inventory = 1},
+ drop = 'ma_pops_furniture:table_'..material,
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.3125, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+
+ after_dig_node = function(pos) ma_pops_furniture.check_table(pos, material, false, true) end,
+ after_place_node = function(pos) ma_pops_furniture.check_table(pos, material, true, true) end,
+ on_punch = function(pos) ma_pops_furniture.check_table(pos, material, true, true) end
+})
+end
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/formspecs.lua b/mods/ma_pops_furniture/formspecs.lua
new file mode 100644
index 00000000..6f9507e6
--- /dev/null
+++ b/mods/ma_pops_furniture/formspecs.lua
@@ -0,0 +1,9 @@
+ma_pops_furniture.fireplace_formspec =
+ 'size[8,6]'..
+ default.gui_bg..
+ default.gui_bg_img..
+ default.gui_slots..
+ 'background[8,6;0,0;default_brick.png;true]'..
+ 'list[current_name;fuel;1,0;1,1;]'..
+ 'list[current_player;main;0,2.5;8,4;]'
+ default.get_hotbar_bg(0,4.85)
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/fridge.lua b/mods/ma_pops_furniture/fridge.lua
new file mode 100644
index 00000000..3966fad6
--- /dev/null
+++ b/mods/ma_pops_furniture/fridge.lua
@@ -0,0 +1,300 @@
+--
+-- Freezer for mintest: a device which turns water (in buckets) into ice
+-- And does a couple of other tricks, discovering which is left as a pleasant
+-- surprise for the player.
+--
+
+-- enable extra popsicle types provided there are both vessels and fruits/veggies available
+-- fruit + glass -> juice; juice @ freezer -> popsicle + empty glass
+
+--
+-- Formspecs
+--
+
+local function active_formspec(fuel_percent, item_percent)
+ local formspec =
+ "size[8,8.5]"..
+ default.gui_bg..
+ default.gui_bg_img..
+ default.gui_slots..
+ "list[current_name;src;2.5,1;1,1;]"..
+ "image[3.75,1.5;1,1;gui_furnace_arrow_bg.png^[lowpart:"..
+ (item_percent)..":gui_furnace_arrow_fg.png^[transformR270]"..
+ "list[current_name;dst;4.75,0.96;3,2;]"..
+ "list[current_player;main;0,4.25;8,1;]"..
+ "list[current_player;main;0,5.5;8,3;8]"..
+ "listring[current_name;dst]"..
+ "listring[current_player;main]"..
+ "listring[current_name;src]"..
+ "listring[current_player;main]"..
+ default.get_hotbar_bg(0, 4.25)
+ return formspec
+end
+
+local inactive_formspec =
+ "size[8,8.5]"..
+ default.gui_bg..
+ default.gui_bg_img..
+ default.gui_slots..
+ "list[current_name;src;2.5,1.5;1,1;]"..
+ "image[3.75,1.5;1,1;gui_furnace_arrow_bg.png^[transformR270]"..
+ "list[current_name;dst;4.75,0.96;3,2;]"..
+ "list[current_player;main;0,4.25;8,1;]"..
+ "list[current_player;main;0,5.5;8,3;8]"..
+ "listring[current_name;dst]"..
+ "listring[current_player;main]"..
+ "listring[current_name;src]"..
+ "listring[current_player;main]"..
+ default.get_hotbar_bg(0, 4.25)
+
+--
+-- Node callback functions that are the same for active and inactive freezer
+--
+
+local function can_dig(pos, player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty("dst") and inv:is_empty("src")
+end
+
+
+local function allow_metadata_inventory_put(pos, listname, index, stack, player)
+ if minetest.is_protected(pos, player:get_player_name()) then
+ return 0
+ end
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ if listname == "src" then
+ return stack:get_count()
+ elseif listname == "dst" then
+ return 0
+ end
+end
+
+
+local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ local stack = inv:get_stack(from_list, from_index)
+ return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
+end
+
+
+local function allow_metadata_inventory_take(pos, listname, index, stack, player)
+ if minetest.is_protected(pos, player:get_player_name()) then
+ return 0
+ end
+ return stack:get_count()
+end
+
+
+local function swap_node(pos, name)
+ local node = minetest.get_node(pos)
+ if node.name == name then
+ return
+ end
+ node.name = name
+ minetest.swap_node(pos, node)
+end
+
+
+local function freezer_node_timer(pos, elapsed)
+ --
+ -- Inizialize metadata
+ --
+ local meta = minetest.get_meta(pos)
+
+ local src_time = meta:get_float("src_time") or 0
+
+
+ local inv = meta:get_inventory()
+ local srclist = inv:get_list("src")
+
+ local dstlist = inv:get_list("dst")
+
+ --
+ -- Cooking
+ --
+
+ -- takes both regular and river water
+ if inv:contains_item("src", "bucket:bucket_water") or
+ inv:contains_item("src", "bucket:bucket_river_water") then
+ if inv:room_for_item("dst", "default:ice") then
+ inv:remove_item("src", "bucket:bucket_water")
+ inv:remove_item("src", "bucket:bucket_river_water")
+ inv:add_item("dst", "default:ice")
+ inv:add_item("dst", "bucket:bucket_empty")
+ end
+ end
+
+ -- Check if we have cookable content
+ return
+end
+
+
+--
+-- Node definitions
+--
+color1 = minetest.setting_get("color1") or "292421"
+color2 = minetest.setting_get("color2") or "0000FF"
+color3 = minetest.setting_get("color3") or "00FF00"
+color4 = minetest.setting_get("color4") or "F5F5F5"
+color5 = minetest.setting_get("color5") or "FF6103"
+color6 = minetest.setting_get("color6") or "FF0000"
+color7 = minetest.setting_get("color7") or "FFFF00"
+color8 = minetest.setting_get("color8") or "FF69B4"
+
+local fridges_list = {
+ {"black", "Darkened Fridge", color1},
+ {"blue", "Blue Fridge", color2},
+ {"green", "Green Fridge", color3},
+ {"white", "White Fridge", color4},
+ {"orange", "Orange Fridge", color5},
+ {"red", "Red Fridge", color6},
+ {"yellow", "Yellow Fridge", color7},
+ {"pink", "Pink Fridge", color8}
+}
+
+for i, fridge in ipairs(fridges_list) do
+ local colour = fridge[1]
+ local fridgedesc = fridge[2]
+ local colour2 = fridge[3]
+
+ minetest.register_node("ma_pops_furniture:fridge_"..colour, {
+ description = fridgedesc,
+ drawtype = "nodebox",
+ tiles = {
+ "mp_dfridge_top.png^[colorize:#"..colour2..":70",
+ "mp_dfridge_bottom.png^[colorize:#"..colour2..":70",
+ "mp_dfridge_right.png^[colorize:#"..colour2..":70",
+ "mp_dfridge_left.png^[colorize:#"..colour2..":70",
+ "mp_dfridge_back.png^[colorize:#"..colour2..":70",
+ "mp_dfridge_front.png^[colorize:#"..colour2..":70"
+ },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ stack_max = 1,
+ groups = {snappy=1,choppy=2,oddly_breakable_by_hand=2,flammable=3},
+ sounds = default.node_sound_wood_defaults(),
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.3125, 0.5, 0.5, 0.5}, -- NodeBox1
+ {-0.5, -0.25, -0.375, 0.5, 0.5, -0.3125}, -- NodeBox2
+ {-0.5, -0.5, -0.375, 0.5, -0.3125, -0.3125}, -- NodeBox3
+ {0.375, 0, -0.4375, 0.4375, 0.5, -0.375}, -- NodeBox4
+ }
+ },
+
+ after_place_node = function(pos, placer, itemstack)
+ local node = minetest.env:get_node(pos)
+ local p = {x=pos.x, y=pos.y, z=pos.z}
+ local param2 = node.param2
+ node.name = "ma_pops_furniture:fridge_top_"..colour
+ pos.y = pos.y+1
+ if minetest.registered_nodes[minetest.env:get_node(pos).name].buildable_to then
+ minetest.env:set_node(pos, node)
+ else
+ minetest.env:remove_node(p)
+ return true
+ end
+ end,
+
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 6*4)
+ meta:set_string('formspec',
+ 'size [9,10]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;2,1.5;6,4;]'..
+ 'list[current_player;main;0.5,6.2;8,4;]')
+ end,
+
+ on_destruct = function(pos)
+ local node = minetest.env:get_node(pos)
+ local param2 = node.param2
+ local abovepos = {x=pos.x, y=pos.y+1, z=pos.z}
+ local abovenode = minetest.env:get_node(abovepos)
+ if abovenode.name == "ma_pops_furniture:fridge_top_"..colour and
+ abovenode.param2 == param2 then
+ minetest.env:remove_node(abovepos)
+ end
+ end,
+
+
+ })
+
+minetest.register_node("ma_pops_furniture:fridge_top_"..colour, {
+ description = fridgedesc,
+ drawtype = "nodebox",
+ tiles = {
+ "mp_ufridge_top.png^[colorize:#"..colour2..":70",
+ "default_wood.png^[colorize:#"..colour2..":70",
+ "mp_ufridge_right.png^[colorize:#"..colour2..":70",
+ "mp_ufridge_left.png^[colorize:#"..colour2..":70",
+ "mp_fridge_back.png^[colorize:#"..colour2..":70",
+ "mp_ufridge_front.png^[colorize:#"..colour2..":70"
+ },
+ paramtype = "light",
+ paramtype2 = "facedir",
+
+ groups = {snappy=1,choppy=2,oddly_breakable_by_hand=2,flammable=3,not_in_creative_inventory=1},
+ sounds = default.node_sound_wood_defaults(),
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.3125, 0.5, 0.5, 0.5}, -- NodeBox1
+ {-0.5, 0.3125, -0.375, 0.5, 0.5, -0.3125}, -- NodeBox2
+ {-0.5, -0.3125, -0.375, 0.5, 0.25, -0.3125}, -- NodeBox3
+ {-0.5, -0.5, -0.375, 0.5, -0.375, -0.3125}, -- NodeBox4
+ {0.375, -0.25, -0.4375, 0.4375, 0.125, -0.375}, -- NodeBox6
+ }
+ },
+
+ can_dig = can_dig,
+
+ on_timer = freezer_node_timer,
+
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", inactive_formspec)
+ local inv = meta:get_inventory()
+ inv:set_size('src', 1)
+ inv:set_size('dst', 6)
+ end,
+
+ on_metadata_inventory_move = function(pos)
+ local timer = minetest.get_node_timer(pos)
+ timer:start(1.0)
+ end,
+ on_metadata_inventory_put = function(pos)
+ -- start timer function, it will sort out whether freezer will work or not.
+ local timer = minetest.get_node_timer(pos)
+ timer:start(1.0)
+ end,
+ on_blast = function(pos)
+ local drops = {}
+ default.get_inventory_drops(pos, "src", drops)
+ default.get_inventory_drops(pos, "dst", drops)
+ drops[#drops+1] = "ma_pops_furniture:freezer"
+ minetest.remove_node(pos)
+ return drops
+ end,
+
+ allow_metadata_inventory_put = allow_metadata_inventory_put,
+ allow_metadata_inventory_move = allow_metadata_inventory_move,
+ allow_metadata_inventory_take = allow_metadata_inventory_take,
+})
+minetest.register_alias("fridges:fridge_"..colour, "fridges:fridge_bottom_"..colour)
+
+end
+
+minetest.register_craft({
+ output = "default:snowblock 3",
+ type = "shapeless",
+ recipe = {
+ "default:ice"
+ }
+})
diff --git a/mods/ma_pops_furniture/functions.lua b/mods/ma_pops_furniture/functions.lua
new file mode 100644
index 00000000..e26ea8b2
--- /dev/null
+++ b/mods/ma_pops_furniture/functions.lua
@@ -0,0 +1,198 @@
+local function top_face(pointed_thing)
+ if not pointed_thing then return end
+ return pointed_thing.above.y > pointed_thing.under.y
+end
+
+function ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ if not top_face(pointed_thing) then return end
+ local player_name = clicker:get_player_name()
+ local objs = minetest.get_objects_inside_radius(pos, 0.1)
+ local vel = clicker:get_player_velocity()
+ local ctrl = clicker:get_player_control()
+
+ for _, obj in pairs(objs) do
+ if obj:is_player() and obj:get_player_name() ~= player_name then
+ return
+ end
+ end
+
+ if default.player_attached[player_name] then
+ pos.y = pos.y - 0.5
+ clicker:setpos(pos)
+ clicker:set_eye_offset({x=0, y=0, z=0}, {x=0, y=0, z=0})
+ clicker:set_physics_override({ speed = 1,
+ jump = 1,
+ gravity = 1 })
+ default.player_attached[player_name] = false
+ default.player_set_animation(clicker, "stand", 30)
+
+ elseif not default.player_attached[player_name] and node.param2 <= 3 and
+ not ctrl.sneak and vector.equals(vel, {x=0,y=0,z=0}) then
+
+ clicker:set_eye_offset({x=0, y=-7, z=2}, {x=0, y=0, z=0})
+ clicker:set_physics_override({ speed = 0,
+ jump = 0,
+ gravity = 0 })
+ clicker:setpos(pos)
+ default.player_attached[player_name] = true
+ default.player_set_animation(clicker, "sit", 30)
+
+ if node.param2 == 0 then clicker:set_look_yaw(3.15)
+ elseif node.param2 == 1 then clicker:set_look_yaw(7.9)
+ elseif node.param2 == 2 then clicker:set_look_yaw(6.28)
+ elseif node.param2 == 3 then clicker:set_look_yaw(4.75) end
+ end
+end
+
+function ma_pops_furniture.sit_dig(pos, digger)
+ for _, player in pairs(minetest.get_objects_inside_radius(pos, 0.1)) do
+ if player:is_player() and
+ default.player_attached[player:get_player_name()] then
+ return false
+ end
+ end
+ return true
+end
+
+
+
+ma_pops_furniture.window_operate = function( pos, old_node_state_name, new_node_state_name )
+
+ local offsets = {-1,1,-2,2,-3,3};
+ local stop_up = 0;
+ local stop_down = 0;
+
+ for i,v in ipairs(offsets) do
+
+ local node = minetest.get_node_or_nil( {x=pos.x, y=(pos.y+v), z=pos.z } );
+ if( node and node.name and node.name==old_node_state_name
+ and ( (v > 0 and stop_up == 0 )
+ or (v < 0 and stop_down == 0 ))) then
+
+ minetest.add_node({x=pos.x, y=(pos.y+v), z=pos.z }, {name = new_node_state_name, param2 = node.param2})
+
+ -- found a diffrent node - no need to search further up
+ elseif( v > 0 and stop_up == 0 ) then
+ stop_up = 1;
+
+ elseif( v < 0 and stop_down == 0 ) then
+ stop_down = 1;
+ end
+ end
+end
+
+--[[
+minetest.register_abm({
+ nodenames = {"ma_pops_furniture:table_wood"},
+ interval = 1, --This will be checked every second
+
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ if pos then
+ if minetest.get_item_group(minetest.get_node({x = pos.x+1, y = pos.y, z = pos.z}).name, "table") == 1
+ and minetest.get_item_group(minetest.get_node({x = pos.x-1, y = pos.y, z = pos.z}).name, "table") == 1 then
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_c2_wood"})
+
+ elseif minetest.get_item_group(minetest.get_node({x = pos.x, y = pos.y, z = pos.z+1}).name, "table") == 1
+ and minetest.get_item_group(minetest.get_node({x = pos.x, y = pos.y, z = pos.z-1}).name, "table") == 1 then
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_c2_wood"})
+ end
+ end
+ end
+})
+--]]
+
+function ma_pops_furniture.check_table(pos, material, check_this, check_others)
+ if pos then
+ local north_table = minetest.get_node({x = pos.x, y = pos.y, z = pos.z+1})
+ local north_table_exists = minetest.get_item_group(north_table.name, "table") == 1
+
+ local east_table = minetest.get_node({x = pos.x+1, y = pos.y, z = pos.z})
+ local east_table_exists = minetest.get_item_group(east_table.name, "table") == 1
+
+ local south_table = minetest.get_node({x = pos.x, y = pos.y, z = pos.z-1})
+ local south_table_exists = minetest.get_item_group(south_table.name, "table") == 1
+
+ local west_table = minetest.get_node({x = pos.x-1, y = pos.y, z = pos.z})
+ local west_table_exists = minetest.get_item_group(west_table.name, "table") == 1
+
+ if check_this then
+ if north_table_exists and east_table_exists and south_table_exists and west_table_exists then
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_center_"..material})
+
+ elseif east_table_exists and west_table_exists then
+ if north_table_exists then
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_c_"..material, param2 = 3})
+ elseif south_table_exists then
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_c_"..material, param2 = 1})
+ else
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_center_"..material})
+ end
+
+ elseif north_table_exists and south_table_exists then
+ if east_table_exists then
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_c_"..material, param2 = 0})
+ elseif west_table_exists then
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_c_"..material, param2 = 2})
+ else
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_center_"..material})
+ end
+
+ elseif north_table_exists ~= east_table_exists ~= south_table_exists ~= west_table_exists then
+ local facedir
+
+ if north_table_exists then facedir = 3
+ elseif east_table_exists then facedir = 0
+ elseif south_table_exists then facedir = 1
+ else facedir = 2
+ end
+
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_c_"..material, param2 = facedir})
+
+ else
+ minetest.set_node(pos, {name = "ma_pops_furniture:table_"..material})
+ end
+ end
+
+ if check_others then
+ if north_table_exists then
+ if north_table.name:sub(24, 31) == "_center_" then
+ ma_pops_furniture.check_table({x = pos.x, y = pos.y, z = pos.z+1}, north_table.name:sub(32), true, false)
+ elseif north_table.name:sub(24, 26) == "_c_" then
+ ma_pops_furniture.check_table({x = pos.x, y = pos.y, z = pos.z+1}, north_table.name:sub(27), true, false)
+ else
+ ma_pops_furniture.check_table({x = pos.x, y = pos.y, z = pos.z+1}, north_table.name:sub(25), true, false)
+ end
+ end
+
+ if east_table_exists then
+ if east_table.name:sub(24, 31) == "_center_" then
+ ma_pops_furniture.check_table({x = pos.x+1, y = pos.y, z = pos.z}, east_table.name:sub(32), true, false)
+ elseif east_table.name:sub(24, 26) == "_c_" then
+ ma_pops_furniture.check_table({x = pos.x+1, y = pos.y, z = pos.z}, east_table.name:sub(27), true, false)
+ else
+ ma_pops_furniture.check_table({x = pos.x+1, y = pos.y, z = pos.z}, east_table.name:sub(25), true, false)
+ end
+ end
+
+ if south_table_exists then
+ if south_table.name:sub(24, 31) == "_center_" then
+ ma_pops_furniture.check_table({x = pos.x, y = pos.y, z = pos.z-1}, south_table.name:sub(32), true, false)
+ elseif south_table.name:sub(24, 26) == "_c_" then
+ ma_pops_furniture.check_table({x = pos.x, y = pos.y, z = pos.z-1}, south_table.name:sub(27), true, false)
+ else
+ ma_pops_furniture.check_table({x = pos.x, y = pos.y, z = pos.z-1}, south_table.name:sub(25), true, false)
+ end
+ end
+
+ if west_table_exists then
+ if west_table.name:sub(24, 31) == "_center_" then
+ ma_pops_furniture.check_table({x = pos.x-1, y = pos.y, z = pos.z}, west_table.name:sub(32), true, false)
+ elseif west_table.name:sub(24, 26) == "_c_" then
+ ma_pops_furniture.check_table({x = pos.x-1, y = pos.y, z = pos.z}, west_table.name:sub(27), true, false)
+ else
+ ma_pops_furniture.check_table({x = pos.x-1, y = pos.y, z = pos.z}, west_table.name:sub(25), true, false)
+ end
+ end
+ end
+ end
+end
diff --git a/mods/ma_pops_furniture/grill2.lua b/mods/ma_pops_furniture/grill2.lua
new file mode 100644
index 00000000..a69c1022
--- /dev/null
+++ b/mods/ma_pops_furniture/grill2.lua
@@ -0,0 +1,246 @@
+local minetest = minetest
+
+local grill_nodebox = {
+ type = "fixed",
+ fixed = {
+ {-0.450, -0.5, -0.450, -0.350, -0.3, -0.350},
+ {0.450, -0.5, -0.450, 0.350, -0.3, -0.350},
+ {-0.450, -0.5, 0.450, -0.350, -0.3, 0.350},
+ {0.450, -0.5, 0.450, 0.350, -0.3, 0.350},
+
+ {-0.4, -0.3, -0.4, -0.3, 0.0, -0.3},
+ {0.4, -0.3, -0.4, 0.3, 0.0, -0.3},
+ {-0.4, -0.3, 0.4, -0.3, 0.0, 0.3},
+ {0.4, -0.3, 0.4, 0.3, 0.0, 0.3},
+
+ {-0.4, -0.0, -0.4, 0.4, 0.2, 0.4},
+ {-0.5, 0.190, -0.5, 0.5, 0.4, 0.5},
+
+ {-0.4375, 0.4, 0.5, -0.5, 0.5, -0.5},
+ {0.4375, 0.4, 0.5, 0.5, 0.5, -0.5},
+ {-0.5, 0.4, 0.4375, 0.5, 0.5, 0.5},
+ {-0.5, 0.4, -0.4375, 0.5, 0.5, -0.5},
+ }
+}
+
+local top_closed_nodebox = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.375, -0.4375, 0.4375, -0.3125, 0.4375},
+ {-0.5, -0.5, -0.4375, -0.4375, -0.375, 0.5},
+ {0.4375, -0.5, -0.5, 0.5, -0.375, 0.4375},
+ {-0.5, -0.5, -0.5, 0.4375, -0.375, -0.4375},
+ {-0.4375, -0.5, 0.4375, 0.5, -0.375, 0.5},
+ }
+}
+
+local top_open_nodebox = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.4375, 0.3125, -0.4375, 0.5, 0.4375},
+ {0.4375, -0.5, 0.3125, 0.5, 0.4375, 0.4375},
+ {-0.4375, 0.4375, 0.3125, 0.5, 0.5, 0.4375},
+ {-0.5, -0.5, 0.3125, 0.4375, -0.4375, 0.4375},
+ {-0.4375, -0.4375, 0.4375, 0.4375, 0.4375, 0.5},
+ }
+}
+
+
+local grill_texture = 'default_stone.png'
+local grill_name = 'ma_pops_furniture:grill'
+local grill_on_name = 'ma_pops_furniture:grill_on'
+local grill2_off_name = 'ma_pops_furniture:grill2'
+local grill2_on_name = 'ma_pops_furniture:grill2_on'
+local grill2_on_no_light_name = 'ma_pops_furniture:grill2_on_nolight'
+local grill_top_name = 'ma_pops_furniture:grill2_top'
+local grill_top_open_name = 'ma_pops_furniture:grill2_top_open'
+
+local function above(pos)
+ return {x=pos.x, y=pos.y+1, z=pos.z}
+end
+
+local function below(pos)
+ return {x=pos.x, y=pos.y-1, z=pos.z}
+end
+
+local function after_dig(pos, oldnode, oldmetadata, digger)
+ local node_above = minetest.get_node(above(pos))
+
+ if node_above.name == grill_top_open_name or
+ node_above.name == grill_top_name then
+ minetest.dig_node(above(pos))
+ end
+end
+
+
+minetest.register_node(grill_name, {
+ description = "Grill",
+ tiles = {
+ "default_coal_block.png^mp_grillt.png",
+ grill_texture,
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png"
+},
+ drawtype = "nodebox",
+ drop = grill_name,
+ paramtype = "light",
+ sounds = moditems.WOOD_SOUNDS,
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ minetest.swap_node(pos, {name = grill_on_name})
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = grill_nodebox
+})
+
+minetest.register_node("ma_pops_furniture:grill_on", {
+ description = "Grill (on)",
+ tiles = {
+ "default_coal_block.png^mp_grillton.png",
+ grill_texture,
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png"
+ },
+ drawtype = "nodebox",
+ drop = grill_name,
+ paramtype = "light",
+ light_source = 10,
+ sounds = moditems.WOOD_SOUNDS,
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ minetest.swap_node(pos, {name = grill_name})
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1},
+ node_box = grill_nodebox
+})
+
+minetest.register_node(grill2_off_name, {
+ description = "Lidded Grill",
+ tiles = {
+ grill_texture .. "^mp_grillt.png",
+ grill_texture,
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ },
+ drawtype = "nodebox",
+ drop = grill2_off_name,
+ paramtype = "light",
+ sounds = moditems.WOOD_SOUNDS,
+
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ local node_above = minetest.get_node(above(pos))
+
+ if node_above.name == grill_top_open_name then
+ minetest.swap_node(pos, {name = grill2_on_name})
+ end
+
+ if node_above.name == grill_top_name then
+ minetest.swap_node(pos, {name = grill2_on_no_light_name})
+ end
+ end,
+
+ after_dig_node = after_dig,
+
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ on_construct = function(pos)
+ local pos_above = above(pos)
+ local node_above = minetest.get_node(pos_above)
+
+ minetest.place_node(pos_above, {name = grill_top_name})
+ if node_above.name == "air" then
+ --
+ end
+ end,
+ node_box = grill_nodebox
+})
+
+minetest.register_node(grill2_on_name, {
+ description = "Lidded Grill (on)",
+ tiles = {
+ grill_texture .. "^mp_grillton.png",
+ grill_texture,
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ },
+ drawtype = "nodebox",
+ drop = grill2_off_name,
+ paramtype = "light",
+ light_source = 10,
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ minetest.swap_node(pos, {name = grill2_off_name})
+ end,
+ after_dig_node = after_dig,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1},
+ node_box = grill_nodebox
+})
+
+minetest.register_node(grill2_on_no_light_name, {
+ description = "Lidded Grill (on)",
+ tiles = {
+ grill_texture .. "^mp_grillton.png",
+ grill_texture,
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ grill_texture .. "^mp_grills.png",
+ },
+ drawtype = "nodebox",
+ drop = grill2_off_name,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ minetest.swap_node(pos, {name=grill2_off_name})
+ end,
+ after_dig_node = after_dig,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1},
+ node_box = grill_nodebox
+})
+
+minetest.register_node(grill_top_name, {
+ description = "Grill lid",
+ tiles = {
+ grill_texture
+ },
+ drawtype = "nodebox",
+ drop = '',
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ -- open lid
+ if minetest.get_node(below(pos)).name == grill2_on_no_light_name then
+ minetest.swap_node(below(pos), {name = grill2_on_name})
+ end
+
+ minetest.swap_node(pos, {name = grill_top_open_name})
+ end,
+ groups = {not_in_creative_inventory = 1},
+ node_box = top_closed_nodebox
+})
+
+minetest.register_node(grill_top_open_name, {
+ description = "Grill lid",
+ tiles = {
+ grill_texture
+ },
+ drawtype = "nodebox",
+ drop = '',
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ -- close lid
+ if minetest.get_node(below(pos)).name == grill2_on_name then
+ minetest.swap_node(below(pos), {name = grill2_on_no_light_name})
+ end
+
+ minetest.swap_node(pos, {name = grill_top_name})
+ end,
+ groups = {not_in_creative_inventory = 1},
+ node_box = top_open_nodebox
+})
diff --git a/mods/ma_pops_furniture/init.lua b/mods/ma_pops_furniture/init.lua
new file mode 100644
index 00000000..d0f37a7a
--- /dev/null
+++ b/mods/ma_pops_furniture/init.lua
@@ -0,0 +1,71 @@
+ma_pops_furniture = {}
+
+--GreenDimond's code from waffle mod
+local MP = minetest.get_modpath(minetest.get_current_modname())
+local S, NS = dofile(MP.."/intllib.lua")
+
+ma_pops_furniture.intllib = S
+dofile(minetest.get_modpath('ma_pops_furniture')..'/intllib.lua')
+
+moditems = {} -- switcher
+
+if core.get_modpath("mcl_core") and mcl_core then -- means MineClone 2 is loaded, this is its core mod
+ moditems.IRON_ITEM = "mcl_core:iron_ingot" -- MCL version of iron ingot
+ moditems.COAL_ITEM = "mcl_core:coalblock" -- MCL version of coal block
+ moditems.CORAL_SKELETON = "mcl_nether:quartz_block" -- MCL version of green dye
+ moditems.SILVER_SANDSTONE = "mcl_nether:quartz_block" -- MCL version of green dye
+ moditems.INVENTORY = "mcl_inventory:crafting_formspec_bg2" -- MCL version of green dye
+ moditems.INFOBOX_CAN = {}
+ moditems.INFOBOX_DUMP = {}
+ moditems.BOXART = "bgcolor[#d0d0d0;false]listcolors[#9d9d9d;#9d9d9d;#5c5c5c;#000000;#ffffff]" -- trying to imitate MCL boxart
+
+else -- fallback, assume default (MineTest Game) is loaded, otherwise it will error anyway here.
+ moditems.IRON_ITEM = "default:steel_ingot" -- MTG iron ingot
+ moditems.COAL_ITEM = "default:coalblock" -- MTG coal block
+ moditems.CORAL_SKELETON = "default:coral_skeleton" -- MCL version of green dye
+ moditems.SILVER_SANDSTONE = "default:silver_sandstone" -- MCL version of green dye
+ moditems.INVENTORY = "default:silver_sandstone" -- MCL version of green dye
+ moditems.INFOBOX_CAN = "Trash Can"
+ moditems.INFOBOX_DUMP = "Dumpster"
+ moditems.BOXART = ""
+end
+
+-- actual use in the code down somewhere.
+material = moditems.IRON_ITEM
+sounds = moditems.WOOD_SOUNDS
+
+_doc_items_longdesc = moditems.STRING_ITEM
+
+local sounds
+
+if mcl_sounds then
+ sounds = mcl_sounds.node_sound_metal_defaults()
+else
+ if default.node_sound_metal_defaults then
+ sounds = default.node_sound_metal_defaults()
+ else
+ sounds = default.node_sound_stone_defaults()
+ end
+end
+
+dofile(minetest.get_modpath('ma_pops_furniture')..'/toaster.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/abm.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/bathroom.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/bedroom.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/kitchen.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/living_room.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/microwave.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/dining_room.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/outside.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/misc.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/oven.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/joyb.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/stereo.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/sofa.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/tv.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/toys.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/tools.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/functions.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/formspecs.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/fridge.lua')
+dofile(minetest.get_modpath('ma_pops_furniture')..'/crafts.lua')
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/intllib.lua b/mods/ma_pops_furniture/intllib.lua
new file mode 100644
index 00000000..458cc8c2
--- /dev/null
+++ b/mods/ma_pops_furniture/intllib.lua
@@ -0,0 +1,45 @@
+
+-- Fallback functions for when `intllib` is not installed.
+-- Code released under Unlicense .
+
+-- Get the latest version of this file at:
+-- https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua
+
+local function format(str, ...)
+ local args = { ... }
+ local function repl(escape, open, num, close)
+ if escape == "" then
+ local replacement = tostring(args[tonumber(num)])
+ if open == "" then
+ replacement = replacement..close
+ end
+ return replacement
+ else
+ return "@"..open..num..close
+ end
+ end
+ return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl))
+end
+
+local gettext, ngettext
+if minetest.get_modpath("intllib") then
+ if intllib.make_gettext_pair then
+ -- New method using gettext.
+ gettext, ngettext = intllib.make_gettext_pair()
+ else
+ -- Old method using text files.
+ gettext = intllib.Getter()
+ end
+end
+
+-- Fill in missing functions.
+
+gettext = gettext or function(msgid, ...)
+ return format(msgid, ...)
+end
+
+ngettext = ngettext or function(msgid, msgid_plural, n, ...)
+ return format(n==1 and msgid or msgid_plural, ...)
+end
+
+return gettext, ngettext
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/joyb.lua b/mods/ma_pops_furniture/joyb.lua
new file mode 100644
index 00000000..20b858c0
--- /dev/null
+++ b/mods/ma_pops_furniture/joyb.lua
@@ -0,0 +1,85 @@
+minetest.register_node("ma_pops_furniture:venext_console", {
+ description = "jOyBoX",
+ tiles = {
+ "mp_venext_top1.png",
+ "mp_venext_bottom.png",
+ "mp_venext_side.png",
+ "mp_venext_side2.png",
+ "mp_venext_back.png",
+ "mp_venext_front.png",
+ },
+ groups = {snappy=1,bendy=2,cracky=1},
+ sounds = moditems.WOOD_SOUNDS,
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = 'facedir',
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.5, -0.4375, 0.4375, -0.1875, 0.4375},
+ {-0.375, -0.1875, -0.375, 0.375, -0.0625, 0.375},
+ {0.3125, -0.4375, -0.5, 0.375, -0.375, -0.4375},
+ {0.1875, -0.4375, -0.5, 0.25, -0.375, -0.4375},
+ }
+ },
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ if itemstack:get_name() == 'ma_pops_furniture:cartridge' then
+ node.name = "ma_pops_furniture:jOyBoX_cart"
+ minetest.set_node(pos, node)
+ if not minetest.is_creative_enabled(player:get_player_name()) then
+ itemstack:take_item()
+ player:set_wielded_item(itemstack)
+ end
+ end
+ end,
+})
+
+minetest.register_node("ma_pops_furniture:jOyBoX_cart", {
+ description = "jOyBoX (with cartridge)",
+ tiles = {
+ "mp_venext_top.png",
+ "mp_venext_bottom.png",
+ "mp_venext_side.png",
+ "mp_venext_side2.png",
+ "mp_venext_back.png",
+ "mp_venext_front.png",
+ },
+ groups = {snappy=1,bendy=2,cracky=1},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.5, -0.4375, 0.4375, -0.1875, 0.4375},
+ {-0.375, -0.1875, -0.375, 0.375, -0.0625, 0.375},
+ {0.3125, -0.4375, -0.5, 0.375, -0.375, -0.4375},
+ {0.1875, -0.4375, -0.5, 0.25, -0.375, -0.4375},
+ {0.25, 0.1, -0.07, -0.25, -0.4375, 0.14},
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:cartridge", {
+ description = "cartridge",
+ tiles = {
+ "mp_cartridge.png",
+ "mp_cartridge_bottom.png",
+ "mp_cartridge.png",
+ "mp_cartridge.png",
+ "mp_cartridge_back.png",
+ "mp_cartridge_front.png",
+ },
+ groups = {snappy=1,bendy=2,cracky=1},
+ sounds = moditems.WOOD_SOUNDS,
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = 'facedir',
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {0.25, -0.1900, -0.07, -0.25, -0.5, 0.14},
+ }
+ }
+})
diff --git a/mods/ma_pops_furniture/kitchen.lua b/mods/ma_pops_furniture/kitchen.lua
new file mode 100644
index 00000000..b5bec2f6
--- /dev/null
+++ b/mods/ma_pops_furniture/kitchen.lua
@@ -0,0 +1,1278 @@
+local counter_table = { --name, color, colorize(hex or color name:intensity(1-255))
+{'Black', 'black', 'black:200'},
+{'Blue', 'blue', 'blue:125'},
+{'Brown', 'brown', 'brown:75'},
+{'Cyan', 'cyan', 'cyan:125'},
+{'Dark Green', 'dark_green', 'green:190'},
+--{'Dark Grey', 'dark_grey', 'black:200'},
+{'Green', 'green', '#32cd32:125'},
+--{'Grey', 'grey', 'black:150'},
+{'Magenta', 'magenta', 'magenta:190'},
+{'Orange', 'orange', 'orange:125'},
+{'Pink', 'pink', 'pink:190'},
+{'Red', 'red', 'red:125'},
+{'Violet', 'violet', 'violet:125'},
+{'White', 'white', 'white:125'},
+{'Yellow', 'yellow', 'yellow:125'},
+}
+
+for i in ipairs (counter_table) do
+ local name = counter_table[i][1]
+ local color = counter_table[i][2]
+ local hex = counter_table[i][3]
+
+minetest.register_node("ma_pops_furniture:counter_"..color, {
+ description = name.. " Counter (Vertical Drawers)",
+ tiles = {
+ "default_coral_skeleton.png^[colorize:"..hex,
+ "mp_enc_bottom.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_right.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_left.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sounds = moditems.WOOD_SOUNDS,
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 6*6)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;1.5,.2;6,6;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5},
+ {-0.4375, -0.375, -0.4375, -0.0625, 0.3125, -0.375},
+ {0.0625, -0.375, -0.4375, 0.4375, 0.3125, -0.375},
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5},
+ {-0.1875, -0.0625, -0.5, -0.125, 0, -0.4375},
+ {0.125, -0.0625, -0.5, 0.1875, 0, -0.4375},
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5},
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:counter_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:counter_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:counter_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:counter_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:counter_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:counter_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:counter_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:counter_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:counter_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:counter_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:counter_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:counter_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:counter_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:counter_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:counter_brown"
+ minetest.set_node(pos, node)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+
+minetest.register_node("ma_pops_furniture:counter2_"..color, {
+ description = name.. " Counter",
+ tiles = {
+ "default_coral_skeleton.png^[colorize:"..hex,
+ "mp_enc_bottom.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_right.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_left.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5},
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5},
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5},
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:counter2_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:counter2_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:counter2_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:counter2_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:counter2_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:counter2_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:counter2_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:counter2_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:counter2_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:counter2_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:counter2_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:counter2_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:counter2_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:counter2_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:counter2_brown"
+ minetest.set_node(pos, node)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+
+minetest.register_node("ma_pops_furniture:counter3_"..color, {
+ description = name.. " Counter (Horizontal Drawers)",
+ tiles = {
+ "default_coral_skeleton.png^[colorize:"..hex,
+ "mp_enc_bottom.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_right.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_left.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_front2.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sounds = moditems.WOOD_SOUNDS,
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 6*6)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;1.5,.2;6,6;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5},
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5},
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5},
+ {-0.4375, 0, -0.4375, 0.4375, 0.3125, -0.375},
+ {-0.4375, -0.375, -0.4375, 0.4375, -0.0625, -0.375},
+ {-0.1875, 0.125, -0.5, 0.1875, 0.1875, -0.4375},
+ {-0.1875, -0.25, -0.5, 0.1875, -0.1875, -0.4375},
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:counter3_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:counter3_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:counter3_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:counter3_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:counter3_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:counter3_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:counter3_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:counter3_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:counter3_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:counter3_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:counter3_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:counter3_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:counter3_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:counter3_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:counter3_brown"
+ minetest.set_node(pos, node)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+
+minetest.register_node("ma_pops_furniture:counter1_" ..color, {
+ description = name.. " Counter (Corner)",
+ tiles = {
+ "default_coral_skeleton.png^[colorize:"..hex,
+ "mp_corn_r_bottom.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ sounds = moditems.WOOD_SOUNDS,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {0.5, 0.5, 0.5, -0.5, -0.5, -0.5}, -- NodeBox1
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:counter1_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:counter1_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:counter1_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:counter1_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:counter1_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:counter1_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:counter1_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:counter1_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:counter1_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:counter1_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:counter1_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:counter1_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:counter1_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:counter1_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:counter1_brown"
+ minetest.set_node(pos, node)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+
+minetest.register_node("ma_pops_furniture:sink_" ..color, {
+ description = name.. " Counter (Sink)",
+ tiles = {
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_sink_top.png",
+ "mp_enc_bottom.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_right.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_left.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_back.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_enc_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sounds = moditems.WOOD_SOUNDS,
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 6*6)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;1.5,.2;6,6;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5}, -- NodeBox1
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5}, -- NodeBox2
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5}, -- NodeBox3
+ {-0.4375, -0.375, -0.4375, -0.0625, 0.3125, -0.375}, -- NodeBox4
+ {0.0625, -0.375, -0.4375, 0.4375, 0.3125, -0.375}, -- NodeBox5
+ {-0.1875, -0.0625, -0.5, -0.125, 0, -0.4375}, -- NodeBox6
+ {0.125, -0.0625, -0.5, 0.1875, 0, -0.4375}, -- NodeBox7
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:sink_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:sink_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:sink_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:sink_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:sink_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:sink_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:sink_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:sink_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:sink_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:sink_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:sink_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:sink_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:sink_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:sink_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:sink_brown"
+ minetest.set_node(pos, node)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+
+minetest.register_node("ma_pops_furniture:upcabinet_"..color, {
+description = name.." Upper Cabinets",
+ tiles = {
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_up_top.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_up_bottom.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_up_right.png",
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_up_left.png",
+ "default_coral_skeleton.png^[colorize:"..hex,
+ "default_coral_skeleton.png^[colorize:"..hex.."^mp_up_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 3*3)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;3,1.3;3,3;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.3125, -0.375, 0.5, 0.5, 0.5}, -- NodeBox1
+ {-0.4375, -0.25, -0.4375, -0.0625, 0.4375, -0.375}, -- NodeBox2
+ {0.0625, -0.25, -0.4375, 0.4375, 0.4375, -0.375}, -- NodeBox3
+ {-0.1875, -0.1875, -0.5, -0.125, 0, -0.4375}, -- NodeBox4
+ {0.125, -0.1875, -0.5, 0.1875, 0, -0.4375}, -- NodeBox5
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:upcabinet_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:upcabinet_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:upcabinet_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:upcabinet_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:upcabinet_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:upcabinet_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:upcabinet_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:upcabinet_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:upcabinet_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:upcabinet_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:upcabinet_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:upcabinet_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:upcabinet_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:upcabinet_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:upcabinet_brown"
+ minetest.set_node(pos, node)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+end
+
+local counter_table = { --name, material
+{'Wooden', 'wood'},
+{'Acacia', 'acacia_wood'},
+{'Jungle', 'junglewood' },
+{'Pine', 'pine_wood'},
+{'Aspen', 'aspen_wood'},
+}
+
+for i in ipairs (counter_table) do
+ local name = counter_table[i][1]
+ local material = counter_table[i][2]
+
+minetest.register_node("ma_pops_furniture:counter_"..material, {
+ description = name.. " Counter (Vertical Drawers)",
+ tiles = {
+ "default_"..material..".png",
+ "mp_enc_bottom.png",
+ "default_"..material..".png^mp_enc_right.png",
+ "default_"..material..".png^mp_enc_left.png",
+ "default_"..material..".png^mp_enc_back.png",
+ "default_"..material..".png^mp_enc_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 6*6)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;1.5,.2;6,6;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5},
+ {-0.4375, -0.375, -0.4375, -0.0625, 0.3125, -0.375},
+ {0.0625, -0.375, -0.4375, 0.4375, 0.3125, -0.375},
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5},
+ {-0.1875, -0.0625, -0.5, -0.125, 0, -0.4375},
+ {0.125, -0.0625, -0.5, 0.1875, 0, -0.4375},
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5},
+ }
+ },
+})
+
+minetest.register_node("ma_pops_furniture:counter2_"..material, {
+ description = name.. " Counter",
+ tiles = {
+ "default_"..material..".png",
+ "mp_enc_bottom.png",
+ "default_"..material..".png^mp_enc_right.png",
+ "default_"..material..".png^mp_enc_left.png",
+ "default_"..material..".png^mp_enc_back.png",
+ "default_"..material..".png^mp_enc_back.png",
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5},
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5},
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5},
+ }
+ },
+})
+
+minetest.register_node("ma_pops_furniture:counter3_"..material, {
+ description = name.. " Counter (Horizontal Drawers)",
+ tiles = {
+ "default_"..material..".png",
+ "mp_enc_bottom.png",
+ "default_"..material..".png^mp_enc_right.png",
+ "default_"..material..".png^mp_enc_left.png",
+ "default_"..material..".png^mp_enc_back.png",
+ "default_"..material..".png^mp_enc_front2.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 6*6)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;1.5,.2;6,6;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5},
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5},
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5},
+ {-0.4375, 0, -0.4375, 0.4375, 0.3125, -0.375},
+ {-0.4375, -0.375, -0.4375, 0.4375, -0.0625, -0.375},
+ {-0.1875, 0.125, -0.5, 0.1875, 0.1875, -0.4375},
+ {-0.1875, -0.25, -0.5, 0.1875, -0.1875, -0.4375},
+ }
+ },
+})
+
+minetest.register_node("ma_pops_furniture:counter1_" ..material, {
+ description = name.. " Counter (Corner)",
+ tiles = {
+ "default_"..material..".png",
+ "mp_corn_r_bottom.png",
+ "default_"..material..".png^mp_enc_back.png",
+ "default_"..material..".png^mp_enc_back.png",
+ "default_"..material..".png^mp_enc_back.png",
+ "default_"..material..".png^mp_enc_back.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {0.5, 0.5, 0.5, -0.5, -0.5, -0.5}, -- NodeBox1
+ }
+ },
+})
+
+minetest.register_node("ma_pops_furniture:sink_" ..material, {
+ description = name.. " Counter (Sink)",
+ tiles = {
+ "default_"..material..".png^mp_sink_top.png",
+ "mp_enc_bottom.png",
+ "default_"..material..".png^mp_enc_right.png",
+ "default_"..material..".png^mp_enc_left.png",
+ "default_"..material..".png^mp_enc_back.png",
+ "default_"..material..".png^mp_enc_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 6*6)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;1.5,.2;6,6;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.3125, 0.5, -0.4375, 0.5}, -- NodeBox1
+ {-0.5, -0.4375, -0.375, 0.5, 0.375, 0.5}, -- NodeBox2
+ {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5}, -- NodeBox3
+ {-0.4375, -0.375, -0.4375, -0.0625, 0.3125, -0.375}, -- NodeBox4
+ {0.0625, -0.375, -0.4375, 0.4375, 0.3125, -0.375}, -- NodeBox5
+ {-0.1875, -0.0625, -0.5, -0.125, 0, -0.4375}, -- NodeBox6
+ {0.125, -0.0625, -0.5, 0.1875, 0, -0.4375}, -- NodeBox7
+ }
+ },
+})
+
+minetest.register_node("ma_pops_furniture:upcabinet_"..material, {
+description = name.." Upper Cabinets",
+ tiles = {
+ "default_"..material..".png^mp_up_top.png",
+ "default_"..material..".png^mp_up_bottom.png",
+ "default_"..material..".png^mp_up_right.png",
+ "default_"..material..".png^mp_up_left.png",
+ "default_"..material..".png",
+ "default_"..material..".png^mp_up_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 3*3)
+ meta:set_string('formspec',
+ 'size [9,10.5]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;3,1.3;3,3;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.3125, -0.375, 0.5, 0.5, 0.5}, -- NodeBox1
+ {-0.4375, -0.25, -0.4375, -0.0625, 0.4375, -0.375}, -- NodeBox2
+ {0.0625, -0.25, -0.4375, 0.4375, 0.4375, -0.375}, -- NodeBox3
+ {-0.1875, -0.1875, -0.5, -0.125, 0, -0.4375}, -- NodeBox4
+ {0.125, -0.1875, -0.5, 0.1875, 0, -0.4375}, -- NodeBox5
+ }
+ },
+})
+end
+
+minetest.register_node("ma_pops_furniture:upcabinet_corner", {
+description = "Upper Cabinets(corner)",
+ tiles = {
+ "mp_grif_sides.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 3*3)
+ meta:set_string('formspec',
+ 'size [9,10]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;3,1.5;3,3;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.3125, -0.5, 0.5, 0.5, 0.5},
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:dw", {
+ description= "Dishwasher",
+ tiles = {
+ "mp_dw_top.png",
+ "mp_dw_bottom.png",
+ "mp_dw_left.png",
+ "mp_dw_right.png",
+ "mp_dw_back.png",
+ "mp_dw_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.5, -0.4375, 0.4375, -0.4375, 0.4375},
+ {-0.5, -0.4375, -0.4375, 0.5, 0.5, 0.5},
+ {-0.5, 0.3125, -0.5, 0.5, 0.5, -0.4375},
+ {-0.4375, -0.4375, -0.5, 0.4375, 0.25, 0.5},
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:oven_overhead", {
+ description= "Oven Overhead",
+ tiles = {
+ "mp_camp_top.png",
+ "mp_camp_bottom.png",
+ "mp_camp_left.png",
+ "mp_camp_right.png",
+ "mp_camp_back.png",
+ "mp_camp_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, 0.4375, -0.4375, 0.4375, 0.5, 0.4375},
+ {-0.5, 0.25, -0.5, 0.5, 0.4375, 0.5},
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:microwave", {
+ description = "Microwave",
+ tiles = {
+ "mp_mw_top.png",
+ "mp_mw_bottom.png",
+ "mp_mw_right.png",
+ "mp_mw_left.png",
+ "mp_mw_back.png",
+ "mp_mw_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.4375, -0.3125, 0.4375, 0.0625, 0.3125},
+ {-0.375, -0.5, -0.25, 0.375, -0.4375, 0.25},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:coffee_maker", {
+ description = "Coffee Maker",
+ tiles = {
+ "mp_cof_top.png",
+ "mp_cof_bottom.png",
+ "mp_cof_right.png",
+ "mp_cof_left.png",
+ "mp_cof_back.png",
+ "mp_cof_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.5, -0.0625, 0, -0.4375, 0.4375},
+ {-0.4375, -0.5, 0.3125, 0, 0.1875, 0.4375},
+ {-0.4375, -0.0625, 0, 0, 0.25, 0.4375},
+ {-0.375, -0.4375, 0, -0.0625, -0.125, 0.25},
+ {-0.25, -0.375, -0.125, -0.1875, -0.1875, 0.0625},
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:coffee_cup", {
+ description = "Coffee Cup",
+ tiles = {
+ "mp_cof_top.png",
+ "mp_cof_top.png",
+ "mp_cof_right.png",
+ "mp_cof_left.png",
+ "mp_cof_back.png",
+ "mp_cof_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy=2, oddly_breakably_by_hand=2, furniture=1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, 0, -0.0625, -0.1875, 0.3125}, -- NodeBox1
+ {-0.25, -0.3125, -0.125, -0.1875, -0.25, 0}, -- NodeBox2
+ {-0.25, -0.4375, -0.125, -0.1875, -0.375, 0}, -- NodeBox3
+ {-0.25, -0.375, -0.125, -0.1875, -0.3125, -0.0625}, -- NodeBox4
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:toaster", {
+ description = "Toaster",
+ tiles = {
+ "mp_toas_top.png",
+ "mp_toas_bottom.png",
+ "mp_toas_right.png",
+ "mp_toas_left.png",
+ "mp_toas_back.png",
+ "mp_toas_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, 0, 0.375, -0.0625, 0.3125},
+ {-0.4375, -0.1875, 0.0625, -0.375, -0.125, 0.25},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:faucet_kitchen", {
+ description = "Kitchen Faucet",
+ tiles = {
+ "mp_grif_top.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png",
+ "mp_grif_sides.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.0625, -0.5, 0.375, 0.0625, -0.1875, 0.4375},
+ {-0.0625, -0.1875, 0.0625, 0.0625, -0.125, 0.4375},
+ {-0.0625, -0.25, 0.0625, 0.0625, -0.1875, 0.125},
+ {0.125, -0.5, 0.3125, 0.25, -0.375, 0.4375},
+ {-0.25, -0.5, 0.3125, -0.125, -0.375, 0.4375},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:cutting_board", {
+ description = "Cutting Board",
+ tiles = {
+ "default_wood.png",
+ "default_wood.png",
+ "default_wood.png",
+ "default_wood.png",
+ "default_wood.png",
+ "default_wood.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.5, -0.25, 0.25, -0.4375, 0.25},
+ {0.25, -0.5, -0.0625, 0.4375, -0.4375, 0.0625},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:tile_kitchen", {
+ description = "White Kitchen Tile",
+ tiles = {
+ "mp_kitchen_tile.png",
+ "mp_kitchen_tile.png",
+ "mp_kitchen_tile.png",
+ "mp_kitchen_tile.png",
+ "mp_kitchen_tile.png",
+ "mp_kitchen_tile.png"
+ },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+})
+
+minetest.register_node("ma_pops_furniture:tile_floor_kitchen", {
+ description = "Checker Kitchen Floor Tile",
+ tiles = {
+ "mp_kitchen_floor_tile.png",
+ "mp_kitchen_floor_tile.png",
+ "mp_kitchen_floor_tile.png",
+ "mp_kitchen_floor_tile.png",
+ "mp_kitchen_floor_tile.png",
+ "mp_kitchen_floor_tile.png"
+ },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+})
+
+local trash_spec =
+ "size[8,9]" ..
+ "button[0,0;2,1;empty;Empty Trash]" ..
+ "list[context;trashlist;3,1;2,3;]" ..
+ "list[current_player;main;0,5;8,4;]"..
+ "listring[]"
+
+
+minetest.register_node('ma_pops_furniture:trash_can', {
+ description = 'Trash Can',
+ drawtype = 'nodebox',
+ tiles = {'default_steel_block.png'},
+ groups = {cracky=2, oddly_breakably_by_hand=2, furniture=1},
+ --inventory_image = 'fm_chair_stone.png',
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, -0.375, 0.375, 0.375, 0.375}, -- NodeBox1
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375}, -- NodeBox2
+ {-0.125, 0.4375, -0.3125, 0.125, 0.5, 0.3125}, -- NodeBox3
+ }
+ },
+ sounds = moditems.WOOD_SOUNDS,
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", trash_spec)
+ meta:set_string("infotext", "Trash Can")
+ local inv = meta:get_inventory()
+ inv:set_size("main", 8*4)
+ inv:set_size("trashlist", 2*3)
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty("main")
+ end,
+ on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
+ minetest.log("action", player:get_player_name() ..
+ " moves stuff in trash can at " .. minetest.pos_to_string(pos))
+ end,
+ on_metadata_inventory_put = function(pos, listname, index, stack, player)
+ minetest.log("action", player:get_player_name() ..
+ " moves stuff to trash can at " .. minetest.pos_to_string(pos))
+ end,
+ on_metadata_inventory_take = function(pos, listname, index, stack, player)
+ minetest.log("action", player:get_player_name() ..
+ " takes stuff from trash can at " .. minetest.pos_to_string(pos))
+ end,
+ on_receive_fields = function(pos, formname, fields, sender)
+ if fields.empty then
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_list("trashlist", {})
+ minetest.sound_play("trash", {to_player=sender:get_player_name(), gain = 1.0})
+ minetest.log("action", sender:get_player_name() ..
+ " empties trash can at " .. minetest.pos_to_string(pos))
+ meta:set_string("formspec", trash_spec)
+ end
+ end
+})
diff --git a/mods/ma_pops_furniture/living_room.lua b/mods/ma_pops_furniture/living_room.lua
new file mode 100644
index 00000000..869a89af
--- /dev/null
+++ b/mods/ma_pops_furniture/living_room.lua
@@ -0,0 +1,492 @@
+minetest.register_node('ma_pops_furniture:fireplace', {
+ description = 'Fireplace',
+ drawtype = 'mesh',
+ mesh = 'FM_fireplace_off.obj',
+ tiles = {{name='default_brick.png'},{name='xpanes_bar.png'}},
+ groups = {cracky=2, oddly_breakable_by_hand=6, furniture=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.STONE_SOUNDS,
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('fuel', 1)
+ inv:set_size('main', 8*4)
+ meta:set_string('formspec', ma_pops_furniture.fireplace_formspec)
+ meta:set_string('infotext', 'Fireplace')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('fuel')
+ end,
+})
+
+minetest.register_node('ma_pops_furniture:fireplace_on', {
+ description = 'Fireplace',
+ drawtype = 'mesh',
+ mesh = 'FM_fireplace_on.obj',
+ tiles = {{name='default_brick.png'},{name='xpanes_bar.png'},{name='default_tree.png'},{name='fire_basic_flame_animated.png', animation={type='vertical_frames', aspect_w=16, aspect_h=16, length=1}}},
+ groups = {cracky=2, oddly_breakable_by_hand=3, furniture=1, not_in_creative_inventory=1},
+ light_source = 14,
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ drops = 'ma_pops_furniture:fireplace',
+ sounds = moditems.STONE_SOUNDS,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('fuel')
+ end,
+})
+
+local c_table = { --name, material, invimg
+{'Stone Coffee Table', 'cobble'},
+{'Wood Coffee Table', 'wood'},
+{'Acacia Wood Coffee Table', 'acacia_wood'},
+{'Aspen Wood Coffee Table', 'aspen_wood'},
+{'Pine Wood Coffee Table', 'pine_wood'},
+{'Jungle Wood Coffee Table', 'junglewood'}
+}
+
+for i in ipairs (c_table) do
+ local name = c_table[i][1]
+ local material = c_table[i][2]
+ local invimg = c_table[i][3]
+
+minetest.register_node('ma_pops_furniture:c_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ groups = {choppy=2, oddly_breakably_by_hand=2, furniture=1, flammable=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'ma_pops_furniture:c_'..material then
+ node.name = 'ma_pops_furniture:end_table_'..material
+ minetest.set_node(pos, node)
+ else
+ end
+ end
+end,
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.5, -0.4, 0.0, -0.4},
+ {-0.5, -0.5, 0.5, -0.4, 0.0, 0.4},
+ {0.5, -0.5, -0.5, 0.4, 0.0, -0.4},
+ {0.5, -0.5, 0.5, 0.4, 0.0, 0.4},
+ {0.5, 0.1, 0.5, -0.5, 0.0, -0.5},
+ {0.5, -0.3, 0.5, -0.5, -0.4, -0.5},
+ },
+ }
+})
+end
+
+local end_table = { --name, material, invimg
+{'Stone End Table', 'cobble'},
+{'Wood End Table', 'wood'},
+{'Acacia Wood End Table', 'acacia_wood'},
+{'Aspen Wood End Table', 'aspen_wood'},
+{'Pine Wood End Table', 'pine_wood'},
+{'Jungle Wood End Table', 'junglewood'}
+}
+
+for i in ipairs (end_table) do
+ local name = end_table[i][1]
+ local material = end_table[i][2]
+ local invimg = end_table[i][3]
+
+minetest.register_node('ma_pops_furniture:end_table_'..material, {
+ description = name,
+ drawtype = 'nodebox',
+ tiles = {'default_'..material..'.png'},
+ groups = {choppy=2, oddly_breakably_by_hand=2, furniture=1, flammable=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.5, -0.4, 0.5, -0.4},
+ {-0.5, -0.5, 0.5, -0.4, 0.5, 0.4},
+ {0.5, -0.5, -0.5, 0.4, 0.5, -0.4},
+ {0.5, -0.5, 0.5, 0.4, 0.5, 0.4},
+ {0.5, 0.4, 0.5, -0.5, 0.5, -0.5},
+ {0.5, -0.3, 0.5, -0.5, -0.2, -0.5},
+ },
+ }
+})
+end
+
+local unit_table = { --name, material
+{'Wood Entertainment Unit', 'wood'},
+{'Acacia Wood Entertainment Unit', 'acacia_wood'},
+{'Aspen Wood Entertainment Unit', 'aspen_wood'},
+{'Pine Wood Entertainment Unit', 'pine_wood'},
+{'Jungle Wood Entertainment Unit', 'junglewood'}
+}
+
+for i in ipairs (unit_table) do
+ local name = unit_table[i][1]
+ local material = unit_table[i][2]
+ local invimg = unit_table[i][3]
+
+minetest.register_node("ma_pops_furniture:e_u_"..material, {
+ description= name,
+ tiles= {'default_'..material..'.png'},
+ drawtype= "nodebox",
+ paramtype= "light",
+ paramtype2 = "facedir",
+ sounds = moditems.WOOD_SOUNDS,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1},
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 3*3)
+ meta:set_string('formspec',
+ 'size [9,10]'..
+ 'bgcolor[#080808BB;true]'..
+ 'list[current_name;storage;3,1.5;3,3;]'..
+ 'list[current_player;main;0.5,6.5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ node_box= {
+ type= "fixed",
+ fixed= {
+ {-0.5, -0.5, -0.5, -0.4, 0.5, 0.5},
+ {0.5, -0.5, -0.5, 0.4, 0.5, 0.5},
+ {-0.5, 0.4, -0.5, 0.5, 0.5, 0.5},
+ {-0.5, -0.050, -0.5, 0.5, 0.050, 0.5},
+ {-0.5, -0.5, 0.5, 0.5, 0.5, 0.4},
+ },
+ }
+})
+end
+
+minetest.register_node('ma_pops_furniture:vcr_on', {
+ description= "VCR",
+ tiles = {
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png^mp_vcr_on.png"
+ },
+ drawtype= "nodebox",
+ paramtype= "light",
+ paramtype2 = "facedir",
+ drop = 'ma_pops_furniture:vcr_off',
+ sounds = moditems.WOOD_SOUNDS,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, not_in_creative_inventory=1, furniture = 1},
+ node_box= {
+ type= "fixed",
+ fixed= {
+ {-0.375, -0.5, -0.25, 0.375, -0.4375, 0.25},
+ {-0.4375, -0.4375, -0.3125, 0.4375, -0.25, 0.3125},
+ },
+ },
+ on_rightclick = function (pos, node, puncher)
+ node.name = "ma_pops_furniture:vcr_off"
+ minetest.set_node(pos, node)
+ end,
+})
+
+minetest.register_node('ma_pops_furniture:vcr_off', {
+ description= "VCR",
+ tiles = {
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png",
+ "default_coal_block.png^mp_vcr_off.png"
+ },
+ drawtype= "nodebox",
+ paramtype= "light",
+ paramtype2 = "facedir",
+ drop = 'ma_pops_furniture:vcr_off',
+ sounds = moditems.WOOD_SOUNDS,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ node_box= {
+ type= "fixed",
+ fixed= {
+ {-0.375, -0.5, -0.25, 0.375, -0.4375, 0.25},
+ {-0.4375, -0.4375, -0.3125, 0.4375, -0.25, 0.3125},
+ },
+ },
+ on_rightclick = function (pos, node, puncher)
+ node.name = "ma_pops_furniture:vcr_on"
+ minetest.set_node(pos, node)
+ end,
+})
+
+local chair2_table = { --name, color, colorize(hex or color name:intensity(1-255))
+{'Black', 'black', 'black:225'},
+{'Blue', 'blue', 'blue:225'},
+{'Brown', 'brown', 'brown:225'},
+{'Cyan', 'cyan', 'cyan:200'},
+{'Dark Green', 'dark_green', 'green:225'},
+{'Dark Grey', 'dark_grey', 'black:200'},
+{'Green', 'green', '#32cd32:150'},
+{'Grey', 'grey', 'black:100'},
+{'Magenta', 'magenta', 'magenta:200'},
+{'Orange', 'orange', 'orange:225'},
+{'Pink', 'pink', 'pink:225'},
+{'Red', 'red', 'red:225'},
+{'Violet', 'violet', 'violet:225'},
+{'White', 'white', 'white:1'},
+{'Yellow', 'yellow', 'yellow:225'},
+}
+
+for i in ipairs (chair2_table) do
+ local name = chair2_table[i][1]
+ local color = chair2_table[i][2]
+ local hex = chair2_table[i][3]
+
+local cb = "^([combine:16x16:0,0=mp_cb.png^[mask:mp_mask.png)"
+local cf = "^([combine:16x16:0,0=mp_cf.png^[mask:mp_mask.png)"
+
+minetest.register_node("ma_pops_furniture:chair2_"..color, {
+ description = name.." Chair",
+ tiles = {"wool_"..color..".png","wool_"..color..".png"..cb,"wool_"..color..".png"..cf,"wool_"..color..".png"..cf,"wool_"..color..".png"..cf,"wool_"..color..".png"..cf,},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1, fall_damage_add_percent=-80, bouncy=80},
+ sounds = {wood = {name="furn_bouncy", gain=0.8}},
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4, -0.5, -0.4, -0.3, -0.4, -0.3},
+ {-0.4, -0.5, 0.4, -0.3, -0.4, 0.3},
+ {0.4, -0.5, 0.4, 0.3, -0.4, 0.3},
+ {0.4, -0.5, -0.4, 0.3, -0.4, -0.3},
+ -----------------------------------
+ {-0.450, -0.4, -0.450, 0.450, 0.1, 0.450},
+ {-0.5, 0.1, -0.5, -0.3, 0.3, 0.0},
+ {0.5, 0.1, -0.5, 0.3, 0.3, 0.0},
+ {0.450, 0.1, -0.0, -0.450, 0.5, 0.450},
+ },
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:chair2_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:chair2_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:chair2_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:chair2_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:chair2_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:chair2_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:chair2_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:chair2_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:chair2_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:chair2_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:chair2_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:chair2_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:chair2_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:chair2_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:chair2_brown"
+ minetest.set_node(pos, node)
+ else
+ ma_pops_furniture.sit(pos, node, clicker)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+end
+
+local fs_table = { --name, color, colorize(hex or color name:intensity(1-255))
+{'Black', 'black', 'black:225'},
+{'Blue', 'blue', 'blue:225'},
+{'Brown', 'brown', 'brown:225'},
+{'Cyan', 'cyan', 'cyan:200'},
+{'Dark Green', 'dark_green', 'green:225'},
+{'Dark Grey', 'dark_grey', 'black:200'},
+{'Green', 'green', '#32cd32:150'},
+{'Grey', 'grey', 'black:100'},
+{'Magenta', 'magenta', 'magenta:200'},
+{'Orange', 'orange', 'orange:225'},
+{'Pink', 'pink', 'pink:225'},
+{'Red', 'red', 'red:225'},
+{'Violet', 'violet', 'violet:225'},
+{'White', 'white', 'white:1'},
+{'Yellow', 'yellow', 'yellow:225'},
+}
+
+for i in ipairs (fs_table) do
+ local name = fs_table[i][1]
+ local color = fs_table[i][2]
+ local hex = fs_table[i][3]
+
+minetest.register_node("ma_pops_furniture:fs_"..color, {
+ description = name.." Footstool",
+ tiles = {"wool_"..color..".png","wool_"..color..".png^mp_cb.png","wool_"..color..".png^mp_cf.png","wool_"..color..".png^mp_cf.png","wool_"..color..".png^mp_cf.png","wool_"..color..".png^mp_cf.png",},
+ drawtype = "nodebox",
+ paramtype = "light",
+ sounds = moditems.WOOD_SOUNDS,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4, -0.5, -0.4, -0.3, -0.4, -0.3},
+ {-0.4, -0.5, 0.4, -0.3, -0.4, 0.3},
+ {0.4, -0.5, 0.4, 0.3, -0.4, 0.3},
+ {0.4, -0.5, -0.4, 0.3, -0.4, -0.3},
+ -----------------------------------
+ {-0.450, -0.4, -0.450, 0.450, -0.1, 0.450},
+ },
+ },
+ on_punch = function(pos, node, clicker)
+ for _, obj in ipairs (minetest.get_connected_players()) do
+ local item = obj:get_wielded_item():get_name()
+ if item == 'dye:black' then
+ node.name = "ma_pops_furniture:fs_black"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:white' then
+ node.name = "ma_pops_furniture:fs_white"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:grey' then
+ node.name = "ma_pops_furniture:fs_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_grey' then
+ node.name = "ma_pops_furniture:fs_dark_grey"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:violet' then
+ node.name = "ma_pops_furniture:fs_violet"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:blue' then
+ node.name = "ma_pops_furniture:fs_blue"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:cyan' then
+ node.name = "ma_pops_furniture:fs_cyan"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:dark_green' then
+ node.name = "ma_pops_furniture:fs_dark_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:green' then
+ node.name = "ma_pops_furniture:fs_green"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:yellow' then
+ node.name = "ma_pops_furniture:fs_yellow"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:orange' then
+ node.name = "ma_pops_furniture:fs_orange"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:red' then
+ node.name = "ma_pops_furniture:fs_red"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:magenta' then
+ node.name = "ma_pops_furniture:fs_magenta"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:pink' then
+ node.name = "ma_pops_furniture:fs_pink"
+ minetest.set_node(pos, node)
+ else
+ if item == 'dye:brown' then
+ node.name = "ma_pops_furniture:fs_brown"
+ minetest.set_node(pos, node)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+})
+end
diff --git a/mods/ma_pops_furniture/microwave.lua b/mods/ma_pops_furniture/microwave.lua
new file mode 100644
index 00000000..3a8a496c
--- /dev/null
+++ b/mods/ma_pops_furniture/microwave.lua
@@ -0,0 +1,162 @@
+--microwave code by Wizzerine
+--item_percent code by Noodlemire
+
+local microwave_fs =
+ "size[9,9.5]"
+ .."background[0,0;9,4.5;mp_microwave_GUI.png]"
+ .."image_button[6.88,3.45;.85,.84;mp_microwave_start.png;btn_start;start]"
+ .."image[7.05,.05;2,.4;mp_mw_bar.png^[transformR270]"
+ .."list[current_player;main;.5,5;8,1;]"
+ .."list[current_player;main;.5,6.5;8,3;8]"
+ .."list[context;cook_slot;3.3,3;1,1;]"
+ .."label[1.5,0.4;Microwave]"
+ -- possibly add "fire" image?
+
+local function get_active_microwave_fs(item_percent)
+ return "size[9,9.5]"
+ .."background[0,0;9,4.5;mp_microwave_GUI.png]"
+ .."image_button[6.88,3.45;.85,.84;mp_microwave_start.png;btn_start;start]"
+ .."image[7.05,.05;2,.4;mp_mw_bar.png^[lowpart:"
+ ..(item_percent)..":mp_mw_bar_on.png^[transformR270]"
+ .."list[current_player;main;.5,5;8,1;]"
+ .."list[current_player;main;.5,6.5;8,3;8]"
+ .."list[context;cook_slot;3.3,3;1,1;]"
+ .."label[1.5,0.4;Microwave]"
+ -- possibly add "fire" image?
+end
+
+--x,y;w,h
+
+-- Adding recipe API so we don't end up hardcoding items
+ma_pops_furniture.microwave = {}
+local microwave = ma_pops_furniture.microwave
+microwave.recipes = {}
+function microwave.register_recipe(input, output) microwave.recipes[input] = output end
+
+local function update_formspec(progress, goal, meta)
+ local formspec
+
+ if progress > 0 and progress <= goal then
+ local item_percent = math.floor(progress / goal * 100)
+ formspec = get_active_microwave_fs(item_percent)
+ else
+ formspec = microwave_fs
+ end
+
+ meta:set_string("formspec", formspec)
+end
+
+local function recalculate(pos)
+ local meta, timer = minetest.get_meta(pos), minetest.get_node_timer(pos)
+ local inv = meta:get_inventory()
+ local stack = inv:get_stack("cook_slot", 1)
+ local goal = 3 * stack:get_count()
+
+ local k = microwave.recipes[stack:get_name()]
+ if not k then return end
+
+ timer:stop()
+ update_formspec(0, goal, meta)
+ timer:start(1)
+end
+
+local function do_cook_all(pos)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ local stack = meta:get_inventory():get_stack("cook_slot", 1)
+ local food_uncooked = inv:remove_item("cook_slot", inv:get_stack("cook_slot", 1)) -- Clear the slot
+ local food_cooked = microwave.recipes[food_uncooked:get_name()] .. " " .. tostring(food_uncooked:get_count()) -- Get the cooked food
+ inv:add_item("cook_slot", food_cooked) -- Put the cooked food in the slot
+end
+
+minetest.register_node("ma_pops_furniture:microwave", {
+ description = "Microwave",
+ tiles = {"mp_mw_top.png", "mp_mw_bottom.png", "mp_mw_right.png", "mp_mw_left.png", "mp_mw_back.png", "mp_mw_front.png"},
+ paramtype2 = "facedir",
+ groups = {cracky = 2}, -- currently no pipeworks compat as I don't know how it works
+ sounds = moditems.STONE_SOUNDS,
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.4375, -0.4375, -0.3125, 0.4375, 0.0625, 0.3125},
+ {-0.375, -0.5, -0.25, 0.375, -0.4375, 0.25},
+ },
+ },
+ can_dig = function(pos, player)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ return inv:is_empty("cook_slot")
+ end,
+
+ on_timer = function(pos, elapsed)
+ local meta = minetest.get_meta(pos)
+ local stack = meta:get_inventory():get_stack("cook_slot", 1)
+ local goal = 3 * stack:get_count()
+ local cooking_time = meta:get_int("cooking_time") or 0
+ cooking_time = cooking_time + 1
+
+ update_formspec(cooking_time, goal, meta)
+ meta:set_int("cooking_time", cooking_time)
+
+ --Keep cooking until there is nothing left to cook.
+ if cooking_time <= goal then
+ return true
+ else
+ do_cook_all(pos)
+ meta:set_int("cooking_time", 0)
+ update_formspec(0, goal, meta)
+ return false
+ end
+ end,
+
+ --on_metadata_inventory_put = recalculate,
+ --on_metadata_inventory_take = recalculate,
+
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", microwave_fs)
+ local inv = meta:get_inventory()
+ inv:set_size("cook_slot", 1)
+ end,
+
+ on_receive_fields = function(pos, _, fields)
+ if fields.quit then return end
+ if fields.btn_start then
+ recalculate(pos)
+ end
+ end,
+
+ on_blast = function(pos)
+ local drops = {}
+ default.get_inventory_drops(pos, "cook_slot", drops)
+ table.insert(drops, "ma_pops_furniture:microwave")
+ minetest.remove_node(pos)
+ return drops
+ end,
+
+ allow_metadata_inventory_put = function(pos, list, index, stack, player)
+ return microwave.recipes[stack:get_name()] and stack:get_count() or 0
+ end,
+
+ --Only allow items to be taken if the microwave hasn't started yet
+ allow_metadata_inventory_take = function(pos, listname, index, stack, player)
+ if not minetest.get_node_timer(pos):is_started() then
+ return stack:get_count()
+ else
+ return 0
+ end
+ end
+})
+
+-- Recipe Registration
+microwave.register_recipe("default:ice", "default:water_source")
+-- No milk bucket as this doesn't support substitutes for now
+microwave.register_recipe("mobs_mc:chicken_raw", "mobs_mc:chicken_cooked")
+--[[ We don't need to check mod existance when registering recipe
+Recipe won't even be executed if there is no raw chicken in input ]]--
+microwave.register_recipe("mobs_mc:beef_raw", "mobs_mc:beef_cooked")
+microwave.register_recipe("mobs:meat_raw", "mobs:meat")
+microwave.register_recipe("farming:coffee_cup", "farming:coffee_cup_hot") -- What a crutch there was...
+microwave.register_recipe("farming:corn", "farming:corn_cob")
+-- Add needed recipes as you go, note that other mods can add more recipes too
diff --git a/mods/ma_pops_furniture/misc.lua b/mods/ma_pops_furniture/misc.lua
new file mode 100644
index 00000000..76efbc3b
--- /dev/null
+++ b/mods/ma_pops_furniture/misc.lua
@@ -0,0 +1,973 @@
+minetest.register_node("ma_pops_furniture:smoke_detector", {
+ description = "Smoke Detector",
+ tiles = {
+ "mp_t.png",
+ "mp_b.png",
+ "mp_si.png",
+ "mp_si.png",
+ "mp_si.png",
+ "mp_si.png"
+ },
+ groups = {cracky = 3, oddly_breakable_by_hand = 3},
+ on_timer = function(pos,elapsed)
+ if minetest.find_node_near(pos, 20, {"fire:basic_flame"}, false) then
+ local node = minetest.get_node(pos)
+ node.name = "ma_pops_furniture:smoke_detector_on"
+ minetest.remove_node(pos)
+ minetest.add_node(pos, node)
+ minetest.get_node_timer(pos):start(0.0)
+ else
+ -- Update every 10 seconds.
+ minetest.get_node_timer(pos):start(10.0)
+ end
+ end,
+ after_place_node = function(pos, placer, itemstack, pointed_thing)
+ minetest.get_node_timer(pos):start(0.0)
+ end,
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, 0.375, -0.375, 0.375, 0.5, 0.375}, -- NodeBox1
+ {-0.3125, 0.3125, -0.3125, 0.3125, 0.375, 0.3125}, -- NodeBox2
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:smoke_detector_on", {
+ description = "Smoke Detector",
+ tiles = {
+ "mp_t.png",
+ "mp_b.png",
+ "mp_si.png",
+ "mp_si.png",
+ "mp_si.png",
+ "mp_si.png"
+ },
+ on_destruct = function(pos)
+ local meta = minetest.get_meta(pos)
+ if meta then
+ local tmp = meta:to_table()
+ if tmp then
+ if tmp.fields.sound_handle then
+ minetest.sound_stop(tmp.fields.sound_handle)
+ tmp.fields.sound_handle = nil
+ meta:from_table(tmp)
+ end
+ end
+ end
+ end,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ local meta = minetest.get_meta(pos)
+ if meta then
+ local tmp = meta:to_table()
+ if tmp then
+ if tmp.fields.sound_handle then
+ minetest.sound_stop(tmp.fields.sound_handle)
+ tmp.fields.sound_handle = nil
+ minetest.get_node_timer(pos):start(3.0)
+ meta:from_table(tmp)
+ end
+ end
+ end
+ end,
+ drop = 'ma_pops_furniture:smoke_detector',
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, not_in_creative_inventory= 1},
+ on_timer = function(pos,elapsed)
+ if minetest.find_node_near(pos, 20, {"fire:basic_flame"}, false) then
+ -- Play sound.
+ local meta = minetest.get_meta(pos)
+ if meta then
+ local tmp = meta:to_table()
+ if tmp then
+ if not tmp.fields.sound_handle then
+ local handle = minetest.sound_play("mp_smoke_detector", {pos = pos, gain = 2.1,max_hear_distance = 96,loop = true})
+ tmp.fields.sound_handle = handle
+ end
+ end
+ meta:from_table(tmp)
+ end
+ -- Update every 1.0 second.
+ minetest.get_node_timer(pos):start(1.0)
+ else
+ local meta = minetest.get_meta(pos)
+ if meta then
+ local tmp = meta:to_table()
+ if tmp then
+ minetest.sound_stop(tmp.fields.sound_handle)
+ if tmp.fields.sound_handle then
+ tmp.fields.sound_handle = nil
+ meta:from_table(tmp)
+ end
+ end
+ end
+ local node = minetest.get_node(pos)
+ node.name = "ma_pops_furniture:smoke_detector"
+ minetest.remove_node(pos)
+ minetest.add_node(pos, node)
+ minetest.get_node_timer(pos):start(0.0)
+ end
+ end,
+ drawtype = "nodebox",
+ paramtype = "light",
+ light_source = light,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, 0.375, -0.375, 0.375, 0.5, 0.375}, -- NodeBox1
+ {-0.3125, 0.3125, -0.3125, 0.3125, 0.375, 0.3125}, -- NodeBox2
+ }
+ }
+})
+
+minetest.register_lbm({
+ label = "Replace all smoke detector's that are turned on.",
+ name = "ma_pops_furniture:replace_smoke_detector_on",
+ nodenames = {"ma_pops_furniture:smoke_detector_on"},
+ run_at_every_load = true,
+ action = function(pos, node)
+ node.name = "ma_pops_furniture:smoke_detector"
+ minetest.remove_node(pos)
+ minetest.add_node(pos, node)
+ minetest.get_node_timer(pos):start(0.0)
+ end
+})
+
+minetest.register_node("ma_pops_furniture:barrel", {
+ description = "Barrel",
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ tiles = {
+ "mp_barrel.png", --top
+ "mp_barrel.png", --bottom
+ "mp_barrel.png^[transformR90", --right
+ "mp_barrel.png^[transformR90", --left
+ "mp_barrel_top.png", --back
+ "mp_barrel_top.png" --front
+ },
+ is_ground_content = false,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, wood = 1},
+ on_construct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ local inv = meta:get_inventory()
+ inv:set_size('main', 8*4)
+ inv:set_size('storage', 9*3)
+ meta:set_string('formspec',
+ 'size [9,9]'..
+ 'bgcolor[#080808BB;false]'..
+ 'list[current_name;storage;0,0.2;9,3;]'..
+ 'list[current_player;main;0.5,5;8,4;]')
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty('storage') and inv:is_empty('storage1')
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
+ }
+ }
+})
+
+minetest.register_node("ma_pops_furniture:blinds", {
+ description = "Blinds",
+ tiles = {"mp_blinds.png"},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1},
+ sunlight_propagates = true;
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.5, 0.5, 0.5, 0.4, 0.4},
+ {-0.5, 0.3, 0.5, 0.5, 0.2, 0.4},
+ {-0.5, 0.1, 0.5, 0.5, 0.0, 0.4},
+ {-0.5, -0.1, 0.5, 0.5, -0.2, 0.4},
+ {-0.5, -0.3, 0.5, 0.5, -0.4, 0.4},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:light", {
+ description = "Ceiling Light",
+ tiles = {
+ "default_coral_skeleton.png",
+ "mp_ceiling_light_bottom.png",
+ "mp_ceiling_light_side.png",
+ "mp_ceiling_light_side.png",
+ "mp_ceiling_light_side.png",
+ "mp_ceiling_light_side.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:light_on"
+ minetest.set_node(pos, node)
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, 0.4375, -0.25, 0.25, 0.5, 0.25}, -- NodeBox1
+ {-0.125, 0.3125, -0.125, 0.125, 0.4375, 0.125}, -- NodeBox2
+ {-0.1875, -0.0625, -0.1875, 0.1875, 0.3125, 0.1875}, -- NodeBox3
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:light_on", {
+ description = "Ceiling Light",
+ tiles = {
+ "default_coral_skeleton.png",
+ "mp_ceiling_light_bottom.png",
+ "mp_ceiling_light_side.png",
+ "mp_ceiling_light_side.png",
+ "mp_ceiling_light_side.png",
+ "mp_ceiling_light_side.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ drop = "ma_pops_furniture:light",
+ light_source = 14,
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:light"
+ minetest.set_node(pos, node)
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, not_in_creative_inventory = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, 0.4375, -0.25, 0.25, 0.5, 0.25}, -- NodeBox1
+ {-0.125, 0.3125, -0.125, 0.125, 0.4375, 0.125}, -- NodeBox2
+ {-0.1875, -0.0625, -0.1875, 0.1875, 0.3125, 0.1875}, -- NodeBox3
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:ceiling_lamp", {
+ description = "Ceiling Lamp",
+ tiles = {
+ "default_stone.png",
+ "default_stone.png^mp_light_off.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png"
+},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:ceiling_lamp_on"
+ minetest.set_node(pos, node)
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.050, 0.5, -0.050, 0.050, -0.2, 0.050},
+ {-0.1, -0.0, -0.1, 0.1, -0.2, 0.1},
+ {-0.2, -0.1, -0.2, 0.2, -0.2, 0.2},
+ {-0.3, -0.2, -0.3, 0.3, -0.5, 0.3},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:ceiling_lamp_on", {
+ description = "Ceiling Lamp On",
+ tiles = {
+ "default_stone.png",
+ "default_stone.png^mp_light_on.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png"
+},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ light_source = 14,
+ drop = 'ma_pops_furniture:ceiling_lamp',
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:ceiling_lamp"
+minetest.set_node(pos, node)
+end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, not_in_creative_inventory = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.050, 0.5, -0.050, 0.050, -0.2, 0.050},
+ {-0.1, -0.0, -0.1, 0.1, -0.2, 0.1},
+ {-0.2, -0.1, -0.2, 0.2, -0.2, 0.2},
+ {-0.3, -0.2, -0.3, 0.3, -0.5, 0.3},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:fan_on", {
+ description = "Fan (on)",
+ tiles = {
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ {
+ name = "mp_fan_on.png",
+ animation = {
+ type = "vertical_frames",
+ aspect_w = 16,
+ aspect_h = 16,
+ length = 0.3
+ },
+ },
+},
+ drawtype = "nodebox",
+ drop = 'ma_pops_furniture:fan_off',
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+node.name = "ma_pops_furniture:fan_off"
+minetest.set_node(pos, node)
+end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.5, 0.2, 0.5, -0.5, -0.2},
+ },
+ }
+})
+minetest.register_node("ma_pops_furniture:fan_off", {
+ description = "Fan",
+ tiles = {
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "mp_fan_off.png",
+ "mp_fan_off.png",
+},
+ drawtype = "nodebox",
+ drop = 'ma_pops_furniture:fan_off',
+ paramtype2 = "facedir",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+node.name = "ma_pops_furniture:fan_on"
+minetest.set_node(pos, node)
+end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.5, 0.2, 0.5, -0.5, -0.2},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:ac", {
+ description = "AC",
+ tiles = {
+ "mp_ac_top.png",
+ "mp_ac_top.png",
+ "mp_ac_top.png",
+ "mp_ac_top.png",
+ "mp_ac_b.png",
+ "mp_ac_f.png",
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ drop = 'ma_pops_furniture:ac',
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {0.5, 0.5, 0.5, -0.5, -0.5, -0.4},
+ {0.5, 0.2, -0.5, -0.5, -0.5, -0.4},
+ },
+ }
+})
+
+-- Define a table for lamp colors
+local lamp_table = {
+ {'Black', 'black', 'black:225'},
+ {'Blue', 'blue', 'blue:225'},
+ {'Brown', 'brown', 'brown:225'},
+ {'Cyan', 'cyan', 'cyan:200'},
+ {'Dark Green', 'dark_green', 'green:225'},
+ {'Dark Grey', 'dark_grey', 'black:200'},
+ {'Green', 'green', '#32cd32:150'},
+ {'Grey', 'grey', 'black:100'},
+ {'Magenta', 'magenta', 'magenta:200'},
+ {'Orange', 'orange', 'orange:225'},
+ {'Pink', 'pink', 'pink:225'},
+ {'Red', 'red', 'red:225'},
+ {'Violet', 'violet', 'violet:225'},
+ {'White', 'white', 'white:1'},
+ {'Yellow', 'yellow', 'yellow:225'}
+}
+
+-- Create a lookup table for dye names and corresponding lamp colors
+local dye_to_color = {
+ ['dye:black'] = 'black',
+ ['dye:white'] = 'white',
+ ['dye:grey'] = 'grey',
+ ['dye:dark_grey'] = 'dark_grey',
+ ['dye:violet'] = 'violet',
+ ['dye:blue'] = 'blue',
+ ['dye:cyan'] = 'cyan',
+ ['dye:dark_green'] = 'dark_green',
+ ['dye:green'] = 'green',
+ ['dye:yellow'] = 'yellow',
+ ['dye:orange'] = 'orange',
+ ['dye:red'] = 'red',
+ ['dye:magenta'] = 'magenta',
+ ['dye:pink'] = 'pink',
+ ['dye:brown'] = 'brown'
+}
+
+-- Register lamps for each color
+for _, lamp in ipairs(lamp_table) do
+ local name = lamp[1]
+ local color = lamp[2]
+ local hex = lamp[3]
+
+ -- Register the "on" version of the lamp
+ minetest.register_node("ma_pops_furniture:lamp_"..color, {
+ description = name .. " Lamp",
+ tiles = {
+ "mp_lt.png",
+ "mp_lb_middle.png^[colorize:" .. hex .. "^mp_lb.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png"
+ },
+ drawtype = "nodebox",
+ light_source = 14,
+ paramtype = "facedir",
+ drop = 'ma_pops_furniture:lamp_off_'..color,
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:lamp_off_"..color
+ minetest.set_node(pos, node)
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.4375, -0.25, 0.25, -0.0625, 0.25},
+ {-0.375, -0.0625, -0.375, 0.375, 0.5, 0.375},
+ {-0.1875, -0.4375, -0.1875, 0.1875, -0.5, 0.1875}
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ local item = clicker:get_wielded_item():get_name()
+ local new_color = dye_to_color[item]
+ if new_color then
+ node.name = "ma_pops_furniture:lamp_" .. new_color
+ minetest.set_node(pos, node)
+ end
+ end
+ })
+
+ -- Register the "off" version of the lamp
+ minetest.register_node("ma_pops_furniture:lamp_off_"..color, {
+ description = name .. " Lamp",
+ tiles = {
+ "mp_lt.png",
+ "mp_lb_middle.png^[colorize:" .. hex .. "^mp_lb.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png",
+ "mp_ls.png^[colorize:" .. hex .. "^mp_ls_top.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "facedir",
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:lamp_"..color
+ minetest.set_node(pos, node)
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.4375, -0.25, 0.25, -0.0625, 0.25},
+ {-0.375, -0.0625, -0.375, 0.375, 0.5, 0.375},
+ {-0.1875, -0.4375, -0.1875, 0.1875, -0.5, 0.1875}
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ local item = clicker:get_wielded_item():get_name()
+ local new_color = dye_to_color[item]
+ if new_color then
+ node.name = "ma_pops_furniture:lamp_off_" .. new_color
+ minetest.set_node(pos, node)
+ end
+ end
+ })
+end
+
+
+local curtain_table = {
+ {'Black', 'black', 'black:225'},
+ {'Blue', 'blue', 'blue:225'},
+ {'Brown', 'brown', 'brown:225'},
+ {'Cyan', 'cyan', 'cyan:200'},
+ {'Dark Green', 'dark_green', 'green:225'},
+ {'Dark Grey', 'dark_grey', 'black:200'},
+ {'Green', 'green', '#32cd32:150'},
+ {'Grey', 'grey', 'black:100'},
+ {'Magenta', 'magenta', 'magenta:200'},
+ {'Orange', 'orange', 'orange:225'},
+ {'Pink', 'pink', 'pink:225'},
+ {'Red', 'red', 'red:225'},
+ {'Violet', 'violet', 'violet:225'},
+ {'White', 'white', 'white:1'},
+ {'Yellow', 'yellow', 'yellow:225'},
+}
+
+for i in ipairs(curtain_table) do
+ local name = curtain_table[i][1]
+ local color = curtain_table[i][2]
+ local hex = curtain_table[i][3]
+
+ -- Small Curtains
+ minetest.register_node("ma_pops_furniture:curtains_"..color, {
+ description = name.." Curtains",
+ tiles = {"default_acacia_tree.png","wool_"..color..".png^mp_curtainb.png","wool_"..color..".png^mp_curtains.png","wool_"..color..".png^mp_curtains.png","wool_"..color..".png^mp_curtains.png","wool_"..color..".png^mp_curtains.png"},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1},
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:curtains_closed_"..color
+ minetest.set_node(pos, node)
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.3, 0.5, -0.2, 0.5, 0.4},
+ {-0.5, -0.5, 0.5, -0.3, -0.3, 0.4},
+ {-0.5, 0.5, 0.5, 0.5, 0.2, 0.4},
+ {-0.5, 0.5, 0.5, 0.5, 0.440, 0.350},
+ {0.5, -0.3, 0.5, 0.2, 0.5, 0.4},
+ {0.5, -0.5, 0.5, 0.3, -0.3, 0.4},
+ },
+ },
+ on_punch = function(pos, node, clicker)
+ local item = clicker:get_wielded_item():get_name()
+ local color_map = {
+ ["dye:black"] = "black",
+ ["dye:white"] = "white",
+ ["dye:grey"] = "grey",
+ ["dye:dark_grey"] = "dark_grey",
+ ["dye:violet"] = "violet",
+ ["dye:blue"] = "blue",
+ ["dye:cyan"] = "cyan",
+ ["dye:dark_green"] = "dark_green",
+ ["dye:green"] = "green",
+ ["dye:yellow"] = "yellow",
+ ["dye:orange"] = "orange",
+ ["dye:red"] = "red",
+ ["dye:magenta"] = "magenta",
+ ["dye:pink"] = "pink",
+ ["dye:brown"] = "brown",
+ }
+ local new_color = color_map[item]
+ if new_color then
+ node.name = "ma_pops_furniture:curtains_"..new_color
+ minetest.set_node(pos, node)
+ end
+ end,
+ })
+
+ -- Closed Small Curtains
+ minetest.register_node("ma_pops_furniture:curtains_closed_"..color, {
+ description = name.." Closed Curtains",
+ tiles = {"default_acacia_tree.png","wool_"..color..".png^mp_curtainb.png","wool_"..color..".png^mp_curtains.png","wool_"..color..".png^mp_curtains.png","wool_"..color..".png^mp_curtains.png","wool_"..color..".png^mp_curtains.png"},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1, furniture = 1},
+ drop = "ma_pops_furniture:curtains_"..color,
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:curtains_"..color
+ minetest.set_node(pos, node)
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, 0.5, 0.5, 0.5, 0.4},
+ {-0.5, 0.5, 0.5, 0.5, 0.440, 0.350},
+ },
+ },
+ on_punch = function(pos, node, clicker)
+ local item = clicker:get_wielded_item():get_name()
+ local color_map = {
+ ["dye:black"] = "black",
+ ["dye:white"] = "white",
+ ["dye:grey"] = "grey",
+ ["dye:dark_grey"] = "dark_grey",
+ ["dye:violet"] = "violet",
+ ["dye:blue"] = "blue",
+ ["dye:cyan"] = "cyan",
+ ["dye:dark_green"] = "dark_green",
+ ["dye:green"] = "green",
+ ["dye:yellow"] = "yellow",
+ ["dye:orange"] = "orange",
+ ["dye:red"] = "red",
+ ["dye:magenta"] = "magenta",
+ ["dye:pink"] = "pink",
+ ["dye:brown"] = "brown",
+ }
+ local new_color = color_map[item]
+ if new_color then
+ node.name = "ma_pops_furniture:curtains_closed_"..new_color
+ minetest.set_node(pos, node)
+ end
+ end,
+ })
+
+ -- Tall Curtains
+ minetest.register_node("ma_pops_furniture:curtains_2_tall_"..color, {
+ description = name.." Tall Curtains",
+ tiles = {"wool_"..color..".png"},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1},
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:curtains_2_tall_closed_"..color
+ minetest.set_node(pos, node)
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.3, 0.5, -0.2, 0.5, 0.4},
+ {-0.5, -0.5, 0.5, -0.3, -0.3, 0.4},
+ {-0.5, 0.5, 0.5, 0.5, 0.2, 0.4},
+ {0.5, -0.3, 0.5, 0.2, 0.5, 0.4},
+ {0.5, -0.5, 0.5, 0.3, -0.3, 0.4},
+ {-0.5, -0.5, 0.5, -0.3, -1.2, 0.4},
+ {0.5, -0.5, 0.5, 0.3, -1.2, 0.4},
+ },
+ },
+ on_punch = function(pos, node, clicker)
+ local item = clicker:get_wielded_item():get_name()
+ local color_map = {
+ ["dye:black"] = "black",
+ ["dye:white"] = "white",
+ ["dye:grey"] = "grey",
+ ["dye:dark_grey"] = "dark_grey",
+ ["dye:violet"] = "violet",
+ ["dye:blue"] = "blue",
+ ["dye:cyan"] = "cyan",
+ ["dye:dark_green"] = "dark_green",
+ ["dye:green"] = "green",
+ ["dye:yellow"] = "yellow",
+ ["dye:orange"] = "orange",
+ ["dye:red"] = "red",
+ ["dye:magenta"] = "magenta",
+ ["dye:pink"] = "pink",
+ ["dye:brown"] = "brown",
+ }
+ local new_color = color_map[item]
+ if new_color then
+ node.name = "ma_pops_furniture:curtains_2_tall_"..new_color
+ minetest.set_node(pos, node)
+ end
+ end,
+ })
+
+ -- Closed Tall Curtains
+ minetest.register_node("ma_pops_furniture:curtains_2_tall_closed_"..color, {
+ description = name.." Closed Tall Curtains",
+ tiles = {"wool_"..color..".png"},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, not_in_creative_inventory = 1, furniture = 1},
+ drop = "ma_pops_furniture:curtains_2_tall_"..color,
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:curtains_2_tall_"..color
+ minetest.set_node(pos, node)
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, 0.5, 0.5, 0.5, 0.4},
+ {-0.5, -1.2, 0.5, 0.5, -0.5, 0.4},
+ },
+ },
+ })
+end
+
+-- On placement check if there is a small curtain directly below
+minetest.register_on_placenode(function(pos, new_node, placer, itemstack, pointed_thing)
+ if new_node.name:match("ma_pops_furniture:curtains_") then
+ local node_below = minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z})
+ if node_below.name:match("ma_pops_furniture:curtains_") and node_below.name ~= new_node.name then
+ local color = new_node.name:match("_(%w+)$")
+ local tall_node_name = "ma_pops_furniture:curtains_2_tall_"..color
+ minetest.set_node(pos, {name = tall_node_name})
+ minetest.set_node({x=pos.x, y=pos.y-1, z=pos.z}, {name = "air"})
+ end
+ end
+end)
+
+
+minetest.register_node("ma_pops_furniture:computer", {
+ description = "Computer",
+ tiles = {
+ "mp_s.png^mp_top.png",
+ "mp_s.png",
+ "mp_s.png",
+ "mp_s.png",
+ "mp_s.png",
+ "mp_s.png^mp_f.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {snappy=2, oddly_breakable_by_hand=2, furniture=1, flammable=1},
+ sounds = moditems.WOOD_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.1875, 0.5, -0.25, 0.5},
+ {-0.5, -0.5, -0.5, 0.1875, -0.375, -0.25},
+ {0.25, -0.5, -0.5, 0.4375, -0.375, -0.25},
+ {-0.125, -0.25, 0.0625, 0.125, -0.0625, 0.25},
+ {-0.3125, -0.125, -0.25, 0.3125, 0.5, 0.3125},
+ {-0.25, 0, 0.3125, 0.25, 0.375, 0.5},
+ }
+ }
+})
+
+-- Define the stool color table
+local stool_table = {
+ {'Black', 'black', 'black:225'},
+ {'Blue', 'blue', 'blue:150'},
+ {'Brown', 'brown', 'brown:100'},
+ {'Cyan', 'cyan', 'cyan:150'},
+ {'Dark Green', 'dark_green', 'green:200'},
+ {'Green', 'green', '#32cd32:150'},
+ {'Magenta', 'magenta', 'magenta:200'},
+ {'Orange', 'orange', 'orange:150'},
+ {'Pink', 'pink', 'pink:150'},
+ {'Red', 'red', 'red:150'},
+ {'Violet', 'violet', 'violet:150'},
+ {'White', 'white', 'white:150'},
+ {'Yellow', 'yellow', 'yellow:150'}
+}
+
+-- Create a lookup table for dye names and corresponding stool colors
+local dye_to_stool = {
+ ['dye:black'] = 'black',
+ ['dye:white'] = 'white',
+ ['dye:grey'] = 'grey',
+ ['dye:dark_grey'] = 'dark_grey',
+ ['dye:violet'] = 'violet',
+ ['dye:blue'] = 'blue',
+ ['dye:cyan'] = 'cyan',
+ ['dye:dark_green'] = 'dark_green',
+ ['dye:green'] = 'green',
+ ['dye:yellow'] = 'yellow',
+ ['dye:orange'] = 'orange',
+ ['dye:red'] = 'red',
+ ['dye:magenta'] = 'magenta',
+ ['dye:pink'] = 'pink',
+ ['dye:brown'] = 'brown'
+}
+
+-- Register each stool color
+for _, stool in ipairs(stool_table) do
+ local name = stool[1]
+ local color = stool[2]
+ local hex = stool[3]
+
+ minetest.register_node("ma_pops_furniture:stool_"..color, {
+ description = name.." Stool",
+ tiles = {
+ "mp_stool_top1.png^mp_stool_top.png^[colorize:"..hex.."^mp_stool_top1.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, furniture = 1},
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, -0.375, -0.1875, 0.375, -0.1875},
+ {-0.375, -0.5, 0.1875, -0.1875, 0.375, 0.375},
+ {0.1875, -0.5, 0.1875, 0.375, 0.375, 0.375},
+ {0.1875, -0.5, -0.375, 0.375, 0.375, -0.1875},
+ {-0.375, 0.1875, -0.375, 0.375, 0.375, 0.375},
+ {0.25, -0.375, -0.1875, 0.3125, -0.3125, 0.1875},
+ {-0.1875, -0.375, -0.3125, 0.1875, -0.3125, -0.25},
+ {-0.1875, -0.375, 0.25, 0.1875, -0.3125, 0.3125},
+ {-0.3125, -0.375, -0.1875, -0.25, -0.3125, 0.1875},
+ {-0.3125, 0.375, -0.3125, 0.3125, 0.4375, 0.3125}
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ local item = clicker:get_wielded_item():get_name()
+ local new_color = dye_to_stool[item]
+ if new_color then
+ node.name = "ma_pops_furniture:stool_"..new_color
+ minetest.set_node(pos, node)
+ else
+ ma_pops_furniture.sit(pos, node, clicker)
+ end
+ end
+ })
+end
+
+
+minetest.register_node("ma_pops_furniture:stairs", {
+ description= "Stairs",
+ tiles = {
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png",
+ "default_coral_skeleton.png^mp_stairs_side.png",
+ "default_coral_skeleton.png^mp_stairs_side.png^[transformFX",
+ "default_coral_skeleton.png^mp_stairs_back.png",
+ "default_coral_skeleton.png^mp_stairs_front.png"
+ },
+ drawtype = "mesh",
+ mesh= "stairs.obj",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {cracky = 3, oddly_breakable_by_hand = 3},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.375, 0, 0.5, 0.5, 0.5}, -- NodeBox18
+ {-0.5, -0.125, -0.5, 0.5, 0, 0}, -- NodeBox19
+ {-0.0625, -0.375, -0.3125, 0.0625, -0.125, -0.1875}, -- NodeBox20
+ {-0.0625, -0.25, 0.1875, 0.0625, 0.375, 0.3125}, -- NodeBox21
+ {-0.0625, -0.375, -0.1875, 0.0625, -0.25, 0.3125}, -- NodeBox23
+ {-0.0625, -0.25, 0.0625, 0.0625, -0.125, 0.1875}, -- NodeBox24
+ }
+ }
+})
+
+-- Register the base lamp
+minetest.register_node("ma_pops_furniture:lamp_1", {
+ description = "Floor Lamp",
+ tiles = {
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png"
+ },
+ groups = {cracky=2, oddly_breakable_by_hand=3},
+ on_construct = function(pos)
+ if minetest.get_node(vector.add(pos, vector.new(0, 1, 0))).name == "air" then
+ minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "ma_pops_furniture:lamp_2_off"})
+ end
+
+ if minetest.get_node(vector.add(pos, vector.new(0, 1, 0))).name ~= "air" and
+ minetest.get_node(vector.add(pos, vector.new(0, 1, 0))).name ~= "ma_pops_furniture:lamp_2_off" then
+ minetest.set_node({x = pos.x, y = pos.y, z = pos.z},{name = "air"})
+ end
+ end,
+ on_dig = function(pos, node, player)
+ minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "air"})
+ minetest.set_node({x = pos.x, y = pos.y, z = pos.z}, {name = "air"})
+ end,
+ drawtype = "nodebox",
+ paramtype = "light",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, -0.25, 0.25, -0.375, 0.25}, -- NodeBox16
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.5, 0.0625} -- NodeBox17
+ }
+ }
+})
+
+-- Register the "on" version of the lamp
+minetest.register_node("ma_pops_furniture:lamp_2_on", {
+ description = "Floor Lamp 2",
+ tiles = {
+ "mp_lt.png",
+ "mp_lt.png",
+ "default_stone.png^mp_ls_top.png",
+ "default_stone.png^mp_ls_top.png",
+ "default_stone.png^mp_ls_top.png",
+ "default_stone.png^mp_ls_top.png"
+ },
+ groups = {cracky=2, oddly_breakable_by_hand=3, not_in_creative_inventory = 1},
+ drop = "test:node_1",
+ on_dig = function(pos, node, player)
+ minetest.set_node({x = pos.x, y = pos.y - 1, z = pos.z}, {name = "air"})
+ minetest.set_node({x = pos.x, y = pos.y, z = pos.z}, {name = "air"})
+ end,
+ drawtype = "nodebox",
+ paramtype = "light",
+ light_source = 14,
+ drop = 'ma_pops_furniture:lamp_2_off',
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:lamp_2_off"
+ minetest.set_node(pos, node)
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.0625, -0.375, 0.375, 0.5, 0.375}, -- NodeBox16
+ {-0.0625, -0.5, -0.0625, 0.0625, 0.1875, 0.0625} -- NodeBox17
+ }
+ },
+})
+
+-- Register the "off" version of the lamp
+minetest.register_node("ma_pops_furniture:lamp_2_off", {
+ description = "Floor Lamp 2",
+ tiles = {
+ "mp_lt.png",
+ "mp_lt.png",
+ "default_stone.png^mp_ls_top.png",
+ "default_stone.png^mp_ls_top.png",
+ "default_stone.png^mp_ls_top.png",
+ "default_stone.png^mp_ls_top.png"
+ },
+ groups = {cracky=2, oddly_breakable_by_hand=3, not_in_creative_inventory = 1},
+ drop = "test:node_1",
+ on_dig = function(pos, node, player)
+ minetest.set_node({x = pos.x, y = pos.y - 1, z = pos.z}, {name = "air"})
+ minetest.set_node({x = pos.x, y = pos.y, z = pos.z}, {name = "air"})
+ end,
+ drawtype = "nodebox",
+ paramtype = "light",
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:lamp_2_on"
+ minetest.set_node(pos, node)
+ end,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.0625, -0.375, 0.375, 0.5, 0.375}, -- NodeBox16
+ {-0.0625, -0.5, -0.0625, 0.0625, 0.1875, 0.0625} -- NodeBox17
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ change_lamp_color(pos, node, clicker)
+ end
+})
diff --git a/mods/ma_pops_furniture/mod.conf b/mods/ma_pops_furniture/mod.conf
new file mode 100644
index 00000000..5d7c9df7
--- /dev/null
+++ b/mods/ma_pops_furniture/mod.conf
@@ -0,0 +1,7 @@
+name = ma_pops_furniture
+description = A revamp of Ma and Pop's Furniture Mod
+optional_depends = default, fire, wool, dye, farming, mobs_mc, mcl_farming
+
+author = GamingAssociation39
+title = Ma & Pop's Furniture Mod
+release = 27688
diff --git a/mods/ma_pops_furniture/models/FM_birdbath.obj b/mods/ma_pops_furniture/models/FM_birdbath.obj
new file mode 100644
index 00000000..1283a227
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_birdbath.obj
@@ -0,0 +1,96 @@
+# Blender v2.75 (sub 4) OBJ File: 'birdbath.blend'
+# www.blender.org
+o Cube.001
+v -0.400000 -0.500000 0.400000
+v -0.400000 -0.400000 0.400000
+v -0.400000 -0.500000 -0.400000
+v -0.400000 -0.400000 -0.400000
+v 0.400000 -0.500000 0.400000
+v 0.400000 -0.400000 0.400000
+v 0.400000 -0.500000 -0.400000
+v 0.400000 -0.400000 -0.400000
+v -0.100000 -0.400000 0.100000
+v -0.100000 -0.400000 -0.100000
+v 0.100000 -0.400000 -0.100000
+v 0.100000 -0.400000 0.100000
+v -0.100000 0.200000 0.100000
+v -0.100000 0.200000 -0.100000
+v 0.100000 0.200000 -0.100000
+v 0.100000 0.200000 0.100000
+v -0.500000 0.200000 0.500000
+v -0.500000 0.200000 -0.500000
+v 0.500000 0.200000 -0.500000
+v 0.500000 0.200000 0.500000
+v -0.500000 0.450000 0.500000
+v -0.500000 0.450000 -0.500000
+v 0.500000 0.450000 -0.500000
+v 0.500000 0.450000 0.500000
+v -0.404231 0.376539 0.404231
+v -0.404231 0.376539 -0.404231
+v 0.404231 0.376539 -0.404231
+v 0.404231 0.376539 0.404231
+vt 0.100000 0.100000
+vt 0.900000 0.100000
+vt 0.900000 0.000000
+vt 0.100000 0.000000
+vt 0.100000 0.900000
+vt 0.900000 0.900000
+vt 0.400000 0.400000
+vt 0.400000 0.600000
+vt 0.400000 0.100000
+vt 0.600000 0.100000
+vt 0.600000 0.700000
+vt 0.400000 0.700000
+vt 0.600000 0.400000
+vt 0.600000 0.600000
+vt 1.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.700000
+vt 1.000000 0.700000
+vt 1.000000 0.950000
+vt 0.000000 0.950000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+vt 0.095769 0.095769
+vt 0.095769 0.904231
+vt 0.904231 0.095769
+vt 0.904231 0.904231
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.608600 0.793500 0.000000
+vn 0.000000 0.793500 -0.608600
+vn 0.000000 0.793500 0.608600
+vn -0.608600 0.793500 0.000000
+g Cube.001_Cube.001_Material
+s off
+f 2/1/1 4/2/1 3/3/1 1/4/1
+f 4/1/2 8/2/2 7/3/2 3/4/2
+f 8/2/3 6/1/3 5/4/3 7/3/3
+f 6/2/4 2/1/4 1/4/4 5/3/4
+f 1/1/5 3/5/5 7/6/5 5/2/5
+f 4/5/6 2/1/6 9/7/6 10/8/6
+f 12/9/3 11/10/3 15/11/3 16/12/3
+f 2/1/6 6/2/6 12/13/6 9/7/6
+f 8/6/6 4/5/6 10/8/6 11/14/6
+f 6/2/6 8/6/6 11/14/6 12/13/6
+f 13/7/5 16/13/5 20/15/5 17/16/5
+f 10/10/1 9/9/1 13/12/1 14/11/1
+f 9/9/4 12/10/4 16/11/4 13/12/4
+f 11/10/2 10/9/2 14/12/2 15/11/2
+f 20/17/3 19/18/3 23/19/3 24/20/3
+f 15/14/5 14/8/5 18/21/5 19/22/5
+f 16/13/5 15/14/5 19/22/5 20/15/5
+f 14/8/5 13/7/5 17/16/5 18/21/5
+f 22/21/7 21/16/7 25/23/7 26/24/7
+f 18/18/1 17/17/1 21/20/1 22/19/1
+f 17/17/4 20/18/4 24/19/4 21/20/4
+f 19/18/2 18/17/2 22/20/2 23/19/2
+f 21/16/8 24/15/8 28/25/8 25/23/8
+f 23/22/9 22/21/9 26/24/9 27/26/9
+f 24/15/10 23/22/10 27/26/10 28/25/10
+g Cube.001_Cube.001_Water.001
+f 28/15/6 27/22/6 26/21/6 25/16/6
diff --git a/mods/ma_pops_furniture/models/FM_fireplace_empty.obj b/mods/ma_pops_furniture/models/FM_fireplace_empty.obj
new file mode 100644
index 00000000..e3251fef
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_fireplace_empty.obj
@@ -0,0 +1,233 @@
+# Blender v2.75 (sub 0) OBJ File: ''
+# www.blender.org
+mtllib fireplace_empty.mtl
+o Cube.009_Cube.010
+v -1.875625 -0.331071 -0.956249
+v -1.882333 -0.303344 0.871280
+v -1.871355 -0.873750 -0.947999
+v -1.878062 -0.846023 0.879529
+v -1.970013 -0.331819 -0.956584
+v -1.976720 -0.304092 0.870945
+v -1.965742 -0.874498 -0.948335
+v -1.972450 -0.846771 0.879194
+vn 1.000000 0.007900 0.003500
+vn 0.007900 -0.999900 0.015200
+vn -1.000000 -0.007900 -0.003500
+vn -0.007900 0.999900 -0.015200
+vn 0.003700 -0.015200 -0.999900
+vn -0.003700 0.015200 0.999900
+usemtl None
+s off
+f 2//1 4//1 3//1 1//1
+f 4//2 8//2 7//2 3//2
+f 8//3 6//3 5//3 7//3
+f 6//4 2//4 1//4 5//4
+f 1//5 3//5 7//5 5//5
+f 6//6 8//6 4//6 2//6
+o Cube.008_Cube.009
+v -1.914666 1.976250 0.049641
+v -1.914666 2.249527 0.049641
+v -1.914666 1.976250 -0.283576
+v -1.914666 2.249527 -0.283576
+v -0.054283 1.976250 0.049641
+v -0.054283 2.249527 0.049641
+v -0.054283 1.976250 -0.283576
+v -0.054283 2.249527 -0.283576
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+usemtl None
+s off
+f 10//7 12//7 11//7 9//7
+f 12//8 16//8 15//8 11//8
+f 16//9 14//9 13//9 15//9
+f 14//10 10//10 9//10 13//10
+f 9//11 11//11 15//11 13//11
+f 14//12 16//12 12//12 10//12
+o Cube.007_Cube.008
+v -1.911818 1.599512 0.223404
+v -1.911818 1.990067 0.223404
+v -1.911818 1.599512 -0.401498
+v -1.911818 1.990067 -0.401498
+v -0.072114 1.599512 0.223404
+v -0.072114 1.990067 0.223404
+v -0.072114 1.599512 -0.401498
+v -0.072114 1.990067 -0.401498
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+usemtl None
+s off
+f 18//13 20//13 19//13 17//13
+f 20//14 24//14 23//14 19//14
+f 24//15 22//15 21//15 23//15
+f 22//16 18//16 17//16 21//16
+f 17//17 19//17 23//17 21//17
+f 22//18 24//18 20//18 18//18
+o Cube.006_Cube.007
+v -1.919788 1.283157 0.334455
+v -1.919788 1.673711 0.334455
+v -1.919788 1.283157 -0.532109
+v -1.919788 1.673711 -0.532109
+v -0.007317 1.283157 0.334455
+v -0.007317 1.673711 0.334455
+v -0.007317 1.283157 -0.532109
+v -0.007317 1.673711 -0.532109
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+usemtl None
+s off
+f 26//19 28//19 27//19 25//19
+f 28//20 32//20 31//20 27//20
+f 32//21 30//21 29//21 31//21
+f 30//22 26//22 25//22 29//22
+f 25//23 27//23 31//23 29//23
+f 30//24 32//24 28//24 26//24
+o Cube.005_Cube.006
+v -1.931623 1.001512 0.792719
+v -1.931623 1.392066 0.792719
+v -1.931623 1.001512 -0.918889
+v -1.931623 1.392066 -0.918889
+v 0.044714 1.001512 0.792719
+v 0.044714 1.392066 0.792719
+v 0.044714 1.001512 -0.918889
+v 0.044714 1.392066 -0.918889
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+usemtl None
+s off
+f 34//25 36//25 35//25 33//25
+f 36//26 40//26 39//26 35//26
+f 40//27 38//27 37//27 39//27
+f 38//28 34//28 33//28 37//28
+f 33//29 35//29 39//29 37//29
+f 38//30 40//30 36//30 34//30
+o Cube.004_Cube.005
+v 0.056191 0.896049 -1.031979
+v 0.056191 0.899351 0.941779
+v -1.943517 0.930250 -1.032036
+v -1.943517 0.933552 0.941721
+v 0.057803 0.990334 -1.032137
+v 0.057804 0.993636 0.941621
+v -1.941904 1.024536 -1.032194
+v -1.941904 1.027838 0.941564
+vn -0.017100 -0.999900 0.001700
+vn -0.999900 0.017100 -0.000000
+vn 0.017100 0.999900 -0.001700
+vn 0.999900 -0.017100 0.000000
+vn -0.000000 -0.001700 -1.000000
+vn 0.000000 0.001700 1.000000
+usemtl None
+s off
+f 42//31 44//31 43//31 41//31
+f 44//32 48//32 47//32 43//32
+f 48//33 46//33 45//33 47//33
+f 46//34 42//34 41//34 45//34
+f 41//35 43//35 47//35 45//35
+f 46//36 48//36 44//36 42//36
+o Cube.003_Cube.004
+v 0.016186 -0.966420 -0.971699
+v 0.016186 -0.915652 0.951856
+v -1.983521 -0.932231 -0.972601
+v -1.983521 -0.881463 0.950953
+v 0.017799 -0.872167 -0.974186
+v 0.017799 -0.821400 0.949368
+v -1.981909 -0.837978 -0.975089
+v -1.981909 -0.787210 0.948466
+vn -0.017100 -0.999500 0.026400
+vn -0.999900 0.017100 -0.000500
+vn 0.017100 0.999500 -0.026400
+vn 0.999900 -0.017100 0.000500
+vn 0.000000 -0.026400 -0.999700
+vn -0.000000 0.026400 0.999700
+usemtl None
+s off
+f 50//37 52//37 51//37 49//37
+f 52//38 56//38 55//38 51//38
+f 56//39 54//39 53//39 55//39
+f 54//40 50//40 49//40 53//40
+f 49//41 51//41 55//41 53//41
+f 54//42 56//42 52//42 50//42
+o Cube.002_Cube.003
+v 0.016186 -0.997135 -0.902221
+v 0.016186 1.002865 -0.902221
+v -1.983521 -0.997135 -0.936423
+v -1.983521 1.002865 -0.936423
+v 0.017799 -0.997135 -0.996509
+v 0.017799 1.002865 -0.996509
+v -1.981909 -0.997135 -1.030711
+v -1.981909 1.002865 -1.030711
+vn -0.017100 0.000000 0.999900
+vn -0.999900 0.000000 -0.017100
+vn 0.017100 0.000000 -0.999900
+vn 0.999900 0.000000 0.017100
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+usemtl None
+s off
+f 58//43 60//43 59//43 57//43
+f 60//44 64//44 63//44 59//44
+f 64//45 62//45 61//45 63//45
+f 62//46 58//46 57//46 61//46
+f 57//47 59//47 63//47 61//47
+f 62//48 64//48 60//48 58//48
+o Cube.001_Cube.002
+v 0.048057 -1.004804 0.990161
+v 0.048057 0.995196 0.990161
+v -1.951651 -1.004804 0.955960
+v -1.951651 0.995196 0.955960
+v 0.049669 -1.004804 0.895873
+v 0.049669 0.995196 0.895873
+v -1.950038 -1.004804 0.861672
+v -1.950038 0.995196 0.861672
+vn -0.017100 0.000000 0.999900
+vn -0.999900 0.000000 -0.017100
+vn 0.017100 0.000000 -0.999900
+vn 0.999900 0.000000 0.017100
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 -0.000000
+usemtl None
+s off
+f 66//49 68//49 67//49 65//49
+f 68//50 72//50 71//50 67//50
+f 72//51 70//51 69//51 71//51
+f 70//52 66//52 65//52 69//52
+f 65//53 67//53 71//53 69//53
+f 70//54 72//54 68//54 66//54
+o Cube_Cube.001
+v -0.047151 -1.000000 1.000000
+v -0.047151 1.000000 1.000000
+v -0.047151 -1.000000 -1.000000
+v -0.047151 1.000000 -1.000000
+v 0.047151 -1.000000 1.000000
+v 0.047151 1.000000 1.000000
+v 0.047151 -1.000000 -1.000000
+v 0.047151 1.000000 -1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+usemtl None
+s off
+f 74//55 76//55 75//55 73//55
+f 76//56 80//56 79//56 75//56
+f 80//57 78//57 77//57 79//57
+f 78//58 74//58 73//58 77//58
+f 73//59 75//59 79//59 77//59
+f 78//60 80//60 76//60 74//60
diff --git a/mods/ma_pops_furniture/models/FM_fireplace_off.obj b/mods/ma_pops_furniture/models/FM_fireplace_off.obj
new file mode 100644
index 00000000..68dc5c5b
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_fireplace_off.obj
@@ -0,0 +1,201 @@
+# Blender v2.75 (sub 4) OBJ File: 'fireplace_empty.blend'
+# www.blender.org
+o Fireplace_off_Plane
+v 0.447003 -0.064595 -0.457685
+v 0.447003 -0.364595 -0.457685
+v -0.447003 -0.064595 -0.457685
+v -0.447003 -0.364595 -0.457685
+v -0.500000 -0.400000 -0.500000
+v -0.500000 0.500000 -0.500000
+v 0.500000 -0.400000 -0.500000
+v 0.500000 0.500000 -0.500000
+v -0.500000 -0.400000 0.500000
+v -0.500000 0.500000 0.500000
+v 0.500000 -0.400000 0.500000
+v 0.500000 0.500000 0.500000
+v -0.400000 0.500000 -0.500000
+v 0.400000 0.500000 -0.500000
+v 0.400000 -0.400000 -0.500000
+v -0.400000 -0.400000 -0.500000
+v 0.400000 0.500000 0.500000
+v -0.400000 0.500000 0.500000
+v -0.400000 -0.400000 0.500000
+v 0.400000 -0.400000 0.500000
+v -0.500000 -0.300000 -0.500000
+v -0.500000 0.400000 -0.500000
+v 0.500000 0.400000 -0.500000
+v 0.500000 -0.300000 -0.500000
+v 0.500000 0.400000 0.500000
+v 0.500000 -0.300000 0.500000
+v -0.500000 0.400000 0.500000
+v -0.500000 -0.300000 0.500000
+v 0.400000 0.400000 0.500000
+v 0.400000 -0.300000 0.500000
+v -0.400000 0.400000 0.500000
+v -0.400000 -0.300000 0.500000
+v -0.400000 0.400000 -0.500000
+v -0.400000 -0.300000 -0.500000
+v 0.400000 0.400000 -0.500000
+v 0.400000 -0.300000 -0.500000
+v -0.400000 0.400000 0.400000
+v -0.400000 -0.300000 0.400000
+v 0.400000 0.400000 0.400000
+v 0.400000 -0.300000 0.400000
+v -0.400000 -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.400000 -0.500000 0.500000
+v -0.500000 -0.500000 0.500000
+v 0.400000 -0.500000 -0.500000
+v -0.400000 -0.500000 0.500000
+v -0.400000 0.675000 -0.500000
+v 0.400000 0.675000 -0.500000
+v -0.400000 0.675000 0.500000
+v 0.400000 0.675000 0.500000
+v 0.400000 0.675000 0.500000
+v -0.400000 0.675000 0.500000
+v 0.400000 0.675000 -0.500000
+v -0.400000 0.675000 -0.500000
+v 0.240000 0.675000 0.500000
+v -0.240000 0.675000 0.500000
+v 0.240000 0.675000 -0.500000
+v -0.240000 0.675000 -0.500000
+v 0.240000 0.875000 0.500000
+v -0.240000 0.875000 0.500000
+v 0.240000 0.875000 -0.500000
+v -0.240000 0.875000 -0.500000
+v 0.120000 0.875000 0.500000
+v -0.120000 0.875000 0.500000
+v 0.120000 0.875000 -0.500000
+v -0.120000 0.875000 -0.500000
+v 0.120000 1.075000 0.500000
+v -0.120000 1.075000 0.500000
+v 0.120000 1.075000 -0.500000
+v -0.120000 1.075000 -0.500000
+vt 0.980066 -0.232831
+vt 1.103207 -0.232831
+vt 1.103207 -0.355973
+vt 0.980066 -0.355973
+vt -0.128207 -0.232831
+vt -0.128207 -0.355973
+vt -0.005066 -0.232831
+vt -0.005066 -0.355973
+vt -0.005066 -0.479114
+vt -0.128207 -0.479114
+vt 1.103207 0.629159
+vt 1.103207 0.752301
+vt -0.128207 0.752301
+vt -0.128207 0.629159
+vt 1.103207 -0.479114
+vt 0.980066 -0.479114
+vt -0.005066 0.752301
+vt 0.980066 0.752301
+vt 0.980066 0.629159
+vt -0.005066 0.629159
+vt -0.128207 0.967798
+vt 1.103207 0.967798
+vt 1.103207 1.214081
+vt -0.128207 1.214081
+vt 0.121793 -0.355973
+vt 1.353207 -0.355973
+vt 1.353207 -0.158946
+vt 0.121793 -0.158946
+vt 1.353207 -0.011176
+vt 0.121793 -0.011176
+vt 1.353207 0.629159
+vt 0.121793 0.629159
+vt 0.121793 0.432133
+vt 1.353207 0.432133
+vt 0.121793 0.284363
+vt 1.353207 0.284363
+vt 1.103207 1.460364
+vt -0.128207 1.460364
+vt 0.341929 1.457455
+vt 0.633902 1.457455
+vt 0.633902 1.214144
+vt 0.341929 1.214144
+vt 0.339730 1.214081
+vt 0.635270 1.214081
+vt 0.635270 1.460364
+vt 0.339730 1.460364
+vt 0.191961 1.214081
+vt 0.783040 1.214081
+vt 0.783040 0.967798
+vt 0.191961 0.967798
+vt 0.980066 0.967798
+vt -0.005066 0.967798
+vt -0.043259 1.000983
+vt -0.043259 0.631558
+vt 1.057632 0.631558
+vt 1.057632 1.000983
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn -1.000000 0.000000 -0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 -1.000000 0.000000
+g Fireplace_off_Plane_FirePlace
+s off
+f 36/1/1 24/2/1 7/3/1 15/4/1
+f 24/5/2 26/2/2 11/3/2 7/6/2
+f 32/7/3 28/5/3 9/6/3 19/8/3
+f 28/2/4 21/5/4 5/6/4 9/3/4
+f 5/6/1 16/8/1 41/9/1 42/10/1
+f 17/11/5 12/12/5 8/13/5 14/14/5
+f 10/15/5 18/3/5 13/6/5 6/10/5
+f 15/4/1 7/3/1 44/15/1 47/16/1
+f 16/6/6 15/14/6 20/11/6 19/3/6
+f 26/2/3 30/1/3 20/4/3 11/3/3
+f 30/1/3 32/7/3 19/8/3 20/4/3
+f 21/5/1 34/7/1 16/8/1 5/6/1
+f 34/7/1 36/1/1 15/4/1 16/8/1
+f 13/17/1 14/18/1 35/19/1 33/20/1
+f 36/14/5 34/6/5 38/4/5 40/19/5
+f 6/13/1 13/17/1 33/20/1 22/14/1
+f 22/14/1 33/20/1 34/7/1 21/5/1
+f 17/18/3 18/17/3 31/20/3 29/19/3
+f 29/19/3 31/20/3 32/7/3 30/1/3
+f 12/12/3 17/18/3 29/19/3 25/11/3
+f 25/11/3 29/19/3 30/1/3 26/2/3
+f 10/12/4 6/13/4 22/14/4 27/11/4
+f 27/11/4 22/14/4 21/5/4 28/2/4
+f 18/17/3 10/13/3 27/14/3 31/20/3
+f 31/20/3 27/14/3 28/5/3 32/7/3
+f 8/13/2 12/12/2 25/11/2 23/14/2
+f 23/14/2 25/11/2 26/2/2 24/5/2
+f 14/18/1 8/12/1 23/11/1 35/19/1
+f 35/19/1 23/11/1 24/2/1 36/1/1
+f 37/20/1 39/19/1 40/1/1 38/7/1
+f 35/14/4 36/5/4 40/1/4 39/19/4
+f 34/5/2 33/14/2 37/19/2 38/1/2
+f 33/6/6 35/14/6 39/19/6 37/4/6
+f 47/14/6 44/13/6 43/12/6 45/11/6
+f 42/10/6 41/6/6 48/3/6 46/15/6
+f 20/3/4 15/6/4 47/10/4 45/15/4
+f 7/6/2 11/3/2 43/15/2 44/10/2
+f 16/6/2 19/3/2 48/15/2 41/10/2
+f 9/3/4 5/6/4 42/10/4 46/15/4
+f 11/3/3 20/4/3 45/16/3 43/15/3
+f 19/8/3 9/6/3 46/10/3 48/9/3
+f 60/21/4 58/22/4 62/23/4 64/24/4
+f 17/12/2 14/13/2 50/21/2 52/22/2
+f 13/13/4 18/12/4 51/22/4 49/21/4
+f 56/25/5 54/26/5 58/27/5 60/28/5
+f 49/6/3 51/3/3 54/3/3 56/6/3
+f 64/28/5 62/27/5 66/29/5 68/30/5
+f 57/22/2 59/21/2 63/24/2 61/23/2
+f 53/31/5 55/32/5 59/33/5 57/34/5
+f 52/11/3 50/14/3 55/14/3 53/11/3
+f 61/34/5 63/33/5 67/35/5 65/36/5
+f 68/24/4 66/23/4 70/37/4 72/38/4
+f 65/23/2 67/24/2 71/38/2 69/37/2
+f 71/35/5 72/30/5 70/29/5 69/36/5
+f 72/39/1 71/40/1 67/41/1 68/42/1
+f 66/43/3 65/44/3 69/45/3 70/46/3
+f 64/47/1 68/43/1 67/44/1 63/48/1 59/49/1 60/50/1
+f 58/50/3 57/49/3 61/48/3 65/44/3 66/43/3 62/47/3
+f 18/17/3 17/18/3 52/51/3 54/52/3 58/50/3 57/49/3 53/51/3 51/52/3
+f 13/17/1 49/52/1 55/51/1 59/49/1 60/50/1 56/52/1 50/51/1 14/18/1
+g Fireplace_off_Plane_Grate
+f 1/53/1 2/54/1 4/55/1 3/56/1
diff --git a/mods/ma_pops_furniture/models/FM_fireplace_on.obj b/mods/ma_pops_furniture/models/FM_fireplace_on.obj
new file mode 100644
index 00000000..1b9620ab
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_fireplace_on.obj
@@ -0,0 +1,271 @@
+# Blender v2.75 (sub 4) OBJ File: 'fireplace_empty.blend'
+# www.blender.org
+o Fireplace_on_Plane.002
+v -0.351930 -0.351930 0.025126
+v -0.351930 0.351930 0.025126
+v 0.351930 -0.351930 0.025126
+v 0.351930 0.351930 0.025126
+v 0.400000 0.041703 0.249151
+v -0.400000 0.041703 0.249151
+v 0.400000 -0.008757 0.370972
+v -0.400000 -0.008757 0.370972
+v 0.400000 -0.302859 0.249151
+v -0.400000 -0.302859 0.249151
+v 0.400000 -0.252399 0.127330
+v -0.400000 -0.252399 0.127330
+v 0.400000 -0.130578 0.076870
+v -0.400000 -0.130578 0.076870
+v 0.400000 -0.008757 0.127330
+v -0.400000 -0.008757 0.127330
+v 0.400000 -0.079822 -0.150849
+v -0.400000 -0.079822 -0.150849
+v 0.400000 -0.112470 -0.072030
+v -0.400000 -0.112470 -0.072030
+v 0.400000 -0.191289 -0.039382
+v -0.400000 -0.191289 -0.039382
+v 0.400000 -0.302756 -0.150849
+v -0.400000 -0.302756 -0.150849
+v 0.400000 -0.270108 -0.229668
+v -0.400000 -0.270108 -0.229668
+v 0.400000 -0.191289 -0.262316
+v -0.400000 -0.191289 -0.262316
+v 0.400000 -0.112470 -0.229668
+v -0.400000 -0.112470 -0.229668
+v 0.447003 -0.064595 -0.457685
+v 0.447003 -0.364595 -0.457685
+v -0.447003 -0.064595 -0.457685
+v -0.447003 -0.364595 -0.457685
+v -0.500000 -0.400000 -0.500000
+v -0.500000 0.500000 -0.500000
+v 0.500000 -0.400000 -0.500000
+v 0.500000 0.500000 -0.500000
+v -0.500000 -0.400000 0.500000
+v -0.500000 0.500000 0.500000
+v 0.500000 -0.400000 0.500000
+v 0.500000 0.500000 0.500000
+v -0.400000 0.500000 -0.500000
+v 0.400000 0.500000 -0.500000
+v 0.400000 -0.400000 -0.500000
+v -0.400000 -0.400000 -0.500000
+v 0.400000 0.500000 0.500000
+v -0.400000 0.500000 0.500000
+v -0.400000 -0.400000 0.500000
+v 0.400000 -0.400000 0.500000
+v -0.500000 -0.300000 -0.500000
+v -0.500000 0.400000 -0.500000
+v 0.500000 0.400000 -0.500000
+v 0.500000 -0.300000 -0.500000
+v 0.500000 0.400000 0.500000
+v 0.500000 -0.300000 0.500000
+v -0.500000 0.400000 0.500000
+v -0.500000 -0.300000 0.500000
+v 0.400000 0.400000 0.500000
+v 0.400000 -0.300000 0.500000
+v -0.400000 0.400000 0.500000
+v -0.400000 -0.300000 0.500000
+v -0.400000 0.400000 -0.500000
+v -0.400000 -0.300000 -0.500000
+v 0.400000 0.400000 -0.500000
+v 0.400000 -0.300000 -0.500000
+v -0.400000 0.400000 0.400000
+v -0.400000 -0.300000 0.400000
+v 0.400000 0.400000 0.400000
+v 0.400000 -0.300000 0.400000
+v -0.400000 -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.400000 -0.500000 0.500000
+v -0.500000 -0.500000 0.500000
+v 0.400000 -0.500000 -0.500000
+v -0.400000 -0.500000 0.500000
+v -0.400000 0.675000 -0.500000
+v 0.400000 0.675000 -0.500000
+v -0.400000 0.675000 0.500000
+v 0.400000 0.675000 0.500000
+v 0.400000 0.675000 0.500000
+v -0.400000 0.675000 0.500000
+v 0.400000 0.675000 -0.500000
+v -0.400000 0.675000 -0.500000
+v 0.240000 0.675000 0.500000
+v -0.240000 0.675000 0.500000
+v 0.240000 0.675000 -0.500000
+v -0.240000 0.675000 -0.500000
+v 0.240000 0.875000 0.500000
+v -0.240000 0.875000 0.500000
+v 0.240000 0.875000 -0.500000
+v -0.240000 0.875000 -0.500000
+v 0.120000 0.875000 0.500000
+v -0.120000 0.875000 0.500000
+v 0.120000 0.875000 -0.500000
+v -0.120000 0.875000 -0.500000
+v 0.120000 1.075000 0.500000
+v -0.120000 1.075000 0.500000
+v 0.120000 1.075000 -0.500000
+v -0.120000 1.075000 -0.500000
+vt 0.980066 -0.232831
+vt 1.103207 -0.232831
+vt 1.103207 -0.355973
+vt 0.980066 -0.355973
+vt -0.128207 -0.232831
+vt -0.128207 -0.355973
+vt -0.005066 -0.232831
+vt -0.005066 -0.355973
+vt -0.005066 -0.479114
+vt -0.128207 -0.479114
+vt 1.103207 0.629159
+vt 1.103207 0.752301
+vt -0.128207 0.752301
+vt -0.128207 0.629159
+vt 1.103207 -0.479114
+vt 0.980066 -0.479114
+vt -0.005066 0.752301
+vt 0.980066 0.752301
+vt 0.980066 0.629159
+vt -0.005066 0.629159
+vt -0.128207 0.967798
+vt 1.103207 0.967798
+vt 1.103207 1.214081
+vt -0.128207 1.214081
+vt 0.121793 -0.355973
+vt 1.353207 -0.355973
+vt 1.353207 -0.158946
+vt 0.121793 -0.158946
+vt 1.353207 -0.011176
+vt 0.121793 -0.011176
+vt 1.353207 0.629159
+vt 0.121793 0.629159
+vt 0.121793 0.432133
+vt 1.353207 0.432133
+vt 0.121793 0.284363
+vt 1.353207 0.284363
+vt 1.103207 1.460364
+vt -0.128207 1.460364
+vt 0.344398 1.457912
+vt 0.635804 1.457912
+vt 0.635804 1.215073
+vt 0.344398 1.215073
+vt 0.339730 1.214081
+vt 0.635270 1.214081
+vt 0.635270 1.460364
+vt 0.339730 1.460364
+vt 0.191961 1.214081
+vt 0.783040 1.214081
+vt 0.783040 0.967798
+vt 0.191961 0.967798
+vt 0.980066 0.967798
+vt -0.005066 0.967798
+vt -0.043259 1.000983
+vt -0.043259 0.631558
+vt 1.057632 0.631558
+vt 1.057632 1.000983
+vt 0.621575 0.000134
+vt 0.621575 0.999866
+vt 0.496809 0.999866
+vt 0.496810 0.000134
+vt 0.122515 0.000134
+vt 0.122514 0.999866
+vt -0.002251 0.999866
+vt -0.002250 0.000134
+vt 0.995869 0.000134
+vt 0.995869 0.999866
+vt 0.871105 0.999866
+vt 0.871105 0.000134
+vt 0.746340 0.000134
+vt 0.746340 0.999866
+vt 0.372044 0.999866
+vt 0.372045 0.000134
+vt 0.999900 0.000100
+vt 0.999900 0.999900
+vt 0.000100 0.999900
+vt 0.000100 0.000100
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn -1.000000 0.000000 -0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 0.923900 0.382700
+vn -0.000000 -0.923900 -0.382700
+vn 0.000000 -0.382700 -0.923900
+vn 0.000000 0.923900 -0.382700
+vn 0.000000 0.382700 -0.923900
+vn -0.000000 0.382700 0.923900
+g Fireplace_on_Plane.002_FirePlace
+s off
+f 66/1/1 54/2/1 37/3/1 45/4/1
+f 54/5/2 56/2/2 41/3/2 37/6/2
+f 62/7/3 58/5/3 39/6/3 49/8/3
+f 58/2/4 51/5/4 35/6/4 39/3/4
+f 35/6/1 46/8/1 71/9/1 72/10/1
+f 47/11/5 42/12/5 38/13/5 44/14/5
+f 40/15/5 48/3/5 43/6/5 36/10/5
+f 45/4/1 37/3/1 74/15/1 77/16/1
+f 46/6/6 45/14/6 50/11/6 49/3/6
+f 56/2/3 60/1/3 50/4/3 41/3/3
+f 60/1/3 62/7/3 49/8/3 50/4/3
+f 51/5/1 64/7/1 46/8/1 35/6/1
+f 64/7/1 66/1/1 45/4/1 46/8/1
+f 43/17/1 44/18/1 65/19/1 63/20/1
+f 66/14/5 64/6/5 68/4/5 70/19/5
+f 36/13/1 43/17/1 63/20/1 52/14/1
+f 52/14/1 63/20/1 64/7/1 51/5/1
+f 47/18/3 48/17/3 61/20/3 59/19/3
+f 59/19/3 61/20/3 62/7/3 60/1/3
+f 42/12/3 47/18/3 59/19/3 55/11/3
+f 55/11/3 59/19/3 60/1/3 56/2/3
+f 40/12/4 36/13/4 52/14/4 57/11/4
+f 57/11/4 52/14/4 51/5/4 58/2/4
+f 48/17/3 40/13/3 57/14/3 61/20/3
+f 61/20/3 57/14/3 58/5/3 62/7/3
+f 38/13/2 42/12/2 55/11/2 53/14/2
+f 53/14/2 55/11/2 56/2/2 54/5/2
+f 44/18/1 38/12/1 53/11/1 65/19/1
+f 65/19/1 53/11/1 54/2/1 66/1/1
+f 67/20/1 69/19/1 70/1/1 68/7/1
+f 65/14/4 66/5/4 70/1/4 69/19/4
+f 64/5/2 63/14/2 67/19/2 68/1/2
+f 63/6/6 65/14/6 69/19/6 67/4/6
+f 77/14/6 74/13/6 73/12/6 75/11/6
+f 72/10/6 71/6/6 78/3/6 76/15/6
+f 50/3/4 45/6/4 77/10/4 75/15/4
+f 37/6/2 41/3/2 73/15/2 74/10/2
+f 46/6/2 49/3/2 78/15/2 71/10/2
+f 39/3/4 35/6/4 72/10/4 76/15/4
+f 41/3/3 50/4/3 75/16/3 73/15/3
+f 49/8/3 39/6/3 76/10/3 78/9/3
+f 90/21/4 88/22/4 92/23/4 94/24/4
+f 47/12/2 44/13/2 80/21/2 82/22/2
+f 43/13/4 48/12/4 81/22/4 79/21/4
+f 86/25/5 84/26/5 88/27/5 90/28/5
+f 79/6/3 81/3/3 84/3/3 86/6/3
+f 94/28/5 92/27/5 96/29/5 98/30/5
+f 87/22/2 89/21/2 93/24/2 91/23/2
+f 83/31/5 85/32/5 89/33/5 87/34/5
+f 82/11/3 80/14/3 85/14/3 83/11/3
+f 91/34/5 93/33/5 97/35/5 95/36/5
+f 98/24/4 96/23/4 100/37/4 102/38/4
+f 95/23/2 97/24/2 101/38/2 99/37/2
+f 101/35/5 102/30/5 100/29/5 99/36/5
+f 102/39/1 101/40/1 97/41/1 98/42/1
+f 96/43/3 95/44/3 99/45/3 100/46/3
+f 94/47/1 98/43/1 97/44/1 93/48/1 89/49/1 90/50/1
+f 88/50/3 87/49/3 91/48/3 95/44/3 96/43/3 92/47/3
+f 48/17/3 47/18/3 82/51/3 84/52/3 88/50/3 87/49/3 83/51/3 81/52/3
+f 43/17/1 79/52/1 85/51/1 89/49/1 90/50/1 86/52/1 80/51/1 44/18/1
+g Fireplace_on_Plane.002_Grate
+f 31/53/1 32/54/1 34/55/1 33/56/1
+g Fireplace_on_Plane.002_Wood
+f 5/57/7 6/58/7 8/59/7 7/60/7
+f 9/61/8 10/62/8 12/63/8 11/64/8
+f 11/65/9 12/66/9 14/67/9 13/68/9
+f 15/69/10 16/70/10 6/58/10 5/57/10
+f 13/68/11 14/67/11 16/70/11 15/69/11
+f 17/57/7 18/58/7 20/59/7 19/60/7
+f 19/60/12 20/59/12 22/71/12 21/72/12
+f 23/61/8 24/62/8 26/63/8 25/64/8
+f 25/65/9 26/66/9 28/67/9 27/68/9
+f 29/69/10 30/70/10 18/58/10 17/57/10
+f 27/68/11 28/67/11 30/70/11 29/69/11
+g Fireplace_on_Plane.002_Fire
+f 1/73/1 2/74/1 4/75/1 3/76/1
diff --git a/mods/ma_pops_furniture/models/FM_lcd_tv.obj b/mods/ma_pops_furniture/models/FM_lcd_tv.obj
new file mode 100644
index 00000000..6cb612d7
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_lcd_tv.obj
@@ -0,0 +1,282 @@
+# Blender v2.79 (sub 7) OBJ File: ''
+# www.blender.org
+o Cube.001
+v 0.997299 -0.405856 0.054064
+v 0.997299 0.792446 0.054064
+v -0.988460 0.792446 0.054064
+v -0.988460 -0.405856 0.054064
+v -0.988460 0.792446 -0.048275
+v -0.988460 -0.405856 -0.048275
+v -0.926462 0.730449 -0.048275
+v 0.935302 0.730449 -0.048275
+v 0.935302 0.730449 -0.036038
+v -0.926462 0.730449 -0.036038
+v 0.997299 -0.405856 -0.048275
+v 0.997299 0.792446 -0.048275
+v 0.964361 -0.496219 -0.032477
+v 0.939937 -0.405063 0.001871
+v 0.891382 -0.418073 0.001871
+v 0.915807 -0.509229 -0.032477
+v 0.886933 -0.401467 -0.045364
+v 0.911358 -0.492623 -0.079712
+v 0.935487 -0.388457 -0.045364
+v 0.959912 -0.479613 -0.079712
+v 0.959912 -0.479613 0.084787
+v 0.935487 -0.388456 0.050439
+v 0.886932 -0.401467 0.050439
+v 0.911358 -0.492623 0.084787
+v 0.891383 -0.418073 0.003203
+v 0.915807 -0.509229 0.037552
+v 0.939937 -0.405064 0.003203
+v 0.964361 -0.496219 0.037552
+v -0.906280 -0.509229 -0.032477
+v -0.881854 -0.418073 0.001871
+v -0.930408 -0.405063 0.001871
+v -0.954834 -0.496219 -0.032477
+v -0.925959 -0.388457 -0.045364
+v -0.950383 -0.479613 -0.079712
+v -0.877405 -0.401467 -0.045364
+v -0.901829 -0.492623 -0.079712
+v -0.901830 -0.492623 0.084787
+v -0.877405 -0.401466 0.050439
+v -0.925959 -0.388456 0.050439
+v -0.950383 -0.479613 0.084787
+v -0.930408 -0.405064 0.003203
+v -0.954834 -0.496219 0.037552
+v -0.881854 -0.418073 0.003203
+v -0.906279 -0.509229 0.037552
+v 0.910700 -0.020382 0.099698
+v 0.910700 0.769627 0.056819
+v -0.901861 0.769627 0.056819
+v -0.901861 -0.020382 0.099698
+v -0.901861 0.764564 -0.036457
+v -0.901861 -0.025445 0.006422
+v 0.910700 0.764564 -0.036457
+v 0.910700 -0.025445 0.006422
+v -0.926462 -0.343858 -0.048275
+v 0.935302 -0.343858 -0.048275
+v -0.926462 -0.343858 -0.036038
+v 0.935302 -0.343858 -0.036038
+vt 1.000000 0.375000
+vt 1.000000 1.000000
+vt 0.500000 1.000000
+vt 0.500000 0.375000
+vt 1.000000 0.187500
+vt 0.687500 0.187500
+vt 0.687500 0.250000
+vt 1.000000 0.250000
+vt 0.500000 1.000000
+vt 0.000000 1.000000
+vt 0.000000 0.968750
+vt 0.500000 0.968750
+vt 1.000000 0.281250
+vt 0.687500 0.281250
+vt 0.687500 0.343750
+vt 1.000000 0.343750
+vt 1.000000 0.125000
+vt 1.000000 0.062500
+vt 0.500000 0.062500
+vt 0.500000 0.125000
+vt 0.500000 -0.000000
+vt 0.500000 0.062500
+vt 1.000000 0.062500
+vt 1.000000 0.000000
+vt 0.593750 0.281250
+vt 0.593750 0.343750
+vt 0.609375 0.343750
+vt 0.609375 0.281250
+vt 0.625000 0.343750
+vt 0.625000 0.281250
+vt 0.593750 0.281250
+vt 0.593750 0.343750
+vt 0.609375 0.343750
+vt 0.609375 0.281250
+vt 0.625000 0.343750
+vt 0.625000 0.281250
+vt 0.625000 0.281250
+vt 0.609375 0.281250
+vt 0.609375 0.312500
+vt 0.625000 0.312500
+vt 0.625000 0.312500
+vt 0.609375 0.312500
+vt 0.609375 0.343750
+vt 0.625000 0.343750
+vt 0.593750 0.281250
+vt 0.593750 0.343750
+vt 0.609375 0.343750
+vt 0.609375 0.281250
+vt 0.625000 0.343750
+vt 0.625000 0.281250
+vt 0.593750 0.281250
+vt 0.593750 0.343750
+vt 0.609375 0.343750
+vt 0.609375 0.281250
+vt 0.625000 0.343750
+vt 0.625000 0.281250
+vt 0.625000 0.281250
+vt 0.609375 0.281250
+vt 0.609375 0.312500
+vt 0.625000 0.312500
+vt 0.625000 0.312500
+vt 0.609375 0.312500
+vt 0.609375 0.343750
+vt 0.625000 0.343750
+vt 0.640625 0.281250
+vt 0.640625 0.343750
+vt 0.656250 0.343750
+vt 0.656250 0.281250
+vt 0.671875 0.343750
+vt 0.671875 0.281250
+vt 0.640625 0.281250
+vt 0.640625 0.343750
+vt 0.656250 0.343750
+vt 0.656250 0.281250
+vt 0.671875 0.343750
+vt 0.671875 0.281250
+vt 0.671875 0.281250
+vt 0.656250 0.281250
+vt 0.656250 0.312500
+vt 0.671875 0.312500
+vt 0.671875 0.312500
+vt 0.656250 0.312500
+vt 0.656250 0.343750
+vt 0.671875 0.343750
+vt 0.640625 0.281250
+vt 0.640625 0.343750
+vt 0.656250 0.343750
+vt 0.656250 0.281250
+vt 0.671875 0.343750
+vt 0.671875 0.281250
+vt 0.640625 0.281250
+vt 0.640625 0.343750
+vt 0.656250 0.343750
+vt 0.656250 0.281250
+vt 0.671875 0.343750
+vt 0.671875 0.281250
+vt 0.671875 0.281250
+vt 0.656250 0.281250
+vt 0.656250 0.312500
+vt 0.671875 0.312500
+vt 0.671875 0.312500
+vt 0.656250 0.312500
+vt 0.656250 0.343750
+vt 0.671875 0.343750
+vt 0.421875 0.000000
+vt 0.421875 0.343750
+vt 0.015625 0.343750
+vt 0.015625 0.000000
+vt 0.000000 0.343750
+vt 0.000000 0.000000
+vt 0.500000 0.375000
+vt 0.500000 1.000000
+vt 0.000000 1.000000
+vt 0.000000 0.375000
+vt 0.437500 0.000000
+vt 0.437500 0.343750
+vt 0.015625 0.375000
+vt 0.015625 0.343750
+vt 0.421875 0.343750
+vt 0.421875 0.375000
+vt 0.421875 0.343750
+vt 0.015625 0.375000
+vt 0.421875 0.375000
+vt 0.484375 0.968750
+vt 0.484375 0.406250
+vt 0.500000 0.375000
+vt 0.500000 1.000000
+vt 0.015625 0.406250
+vt 0.000000 0.375000
+vt 0.015625 0.968750
+vt 0.000000 1.000000
+vt 0.484375 0.375000
+vt 0.484375 1.000000
+vt 0.500000 1.000000
+vt 0.500000 0.375000
+vt 0.000000 0.375000
+vt 0.015625 0.375000
+vt 0.015625 1.000000
+vt 0.421875 0.375000
+vt 0.421875 0.406250
+vt 0.000000 0.406250
+vt 0.968750 0.406250
+vt 0.968750 0.968750
+vt 0.031250 0.968750
+vt 0.031250 0.406250
+vn -0.0000 0.0000 1.0000
+vn -1.0000 0.0000 -0.0000
+vn 0.0000 -1.0000 0.0000
+vn 1.0000 -0.0000 0.0000
+vn 0.0000 1.0000 0.0000
+vn 0.0885 -0.3304 0.9397
+vn -0.9659 -0.2588 -0.0000
+vn -0.0885 0.3304 -0.9397
+vn 0.9659 0.2588 -0.0000
+vn 0.2432 -0.9077 -0.3420
+vn -0.2432 0.9077 0.3420
+vn -0.0885 0.3304 0.9397
+vn 0.0885 -0.3304 -0.9397
+vn 0.2432 -0.9077 0.3420
+vn -0.2432 0.9077 -0.3420
+vn -0.0885 -0.3304 0.9397
+vn -0.9659 0.2588 -0.0000
+vn 0.0885 0.3304 -0.9397
+vn 0.9659 -0.2588 0.0000
+vn -0.2432 -0.9077 -0.3420
+vn 0.2432 0.9077 0.3420
+vn 0.0885 0.3304 0.9397
+vn -0.0885 -0.3304 -0.9397
+vn -0.2432 -0.9077 0.3420
+vn 0.2432 0.9077 -0.3420
+vn -0.0000 0.0542 0.9985
+vn 0.0000 -0.0542 -0.9985
+vn -0.0000 -0.9985 0.0542
+vn 0.0000 0.9985 -0.0542
+vn 0.0000 -0.0000 -1.0000
+g Cube.001_Cube.001_None
+s off
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 4/5/2 3/6/2 5/7/2 6/8/2
+f 7/9/3 8/10/3 9/11/3 10/12/3
+f 11/13/4 12/14/4 2/15/4 1/16/4
+f 4/17/3 6/18/3 11/19/3 1/20/3
+f 5/21/5 3/22/5 2/23/5 12/24/5
+f 13/25/6 14/26/6 15/27/6 16/28/6
+f 16/28/7 15/27/7 17/29/7 18/30/7
+f 18/31/8 17/32/8 19/33/8 20/34/8
+f 20/34/9 19/33/9 14/35/9 13/36/9
+f 16/37/10 18/38/10 20/39/10 13/40/10
+f 17/41/11 15/42/11 14/43/11 19/44/11
+f 21/45/12 22/46/12 23/47/12 24/48/12
+f 24/48/7 23/47/7 25/49/7 26/50/7
+f 26/51/13 25/52/13 27/53/13 28/54/13
+f 28/54/9 27/53/9 22/55/9 21/56/9
+f 24/57/14 26/58/14 28/59/14 21/60/14
+f 25/61/15 23/62/15 22/63/15 27/64/15
+f 29/65/16 30/66/16 31/67/16 32/68/16
+f 32/68/17 31/67/17 33/69/17 34/70/17
+f 34/71/18 33/72/18 35/73/18 36/74/18
+f 36/74/19 35/73/19 30/75/19 29/76/19
+f 32/77/20 34/78/20 36/79/20 29/80/20
+f 33/81/21 31/82/21 30/83/21 35/84/21
+f 37/85/22 38/86/22 39/87/22 40/88/22
+f 40/88/17 39/87/17 41/89/17 42/90/17
+f 42/91/23 41/92/23 43/93/23 44/94/23
+f 44/94/19 43/93/19 38/95/19 37/96/19
+f 40/97/24 42/98/24 44/99/24 37/100/24
+f 41/101/25 39/102/25 38/103/25 43/104/25
+f 45/105/26 46/106/26 47/107/26 48/108/26
+f 48/108/2 47/107/2 49/109/2 50/110/2
+f 50/111/27 49/112/27 51/113/27 52/114/27
+f 52/115/4 51/116/4 46/106/4 45/105/4
+f 48/117/28 50/118/28 52/119/28 45/120/28
+f 49/121/29 47/107/29 46/122/29 51/123/29
+f 7/124/30 53/125/30 6/126/30 5/127/30
+f 53/125/30 54/128/30 11/129/30 6/126/30
+f 8/130/30 7/124/30 5/127/30 12/131/30
+f 54/128/30 8/130/30 12/131/30 11/129/30
+f 53/132/4 7/133/4 10/134/4 55/135/4
+f 8/10/2 54/136/2 56/137/2 9/138/2
+f 54/136/5 53/139/5 55/140/5 56/141/5
+g Cube.001_Cube.001_None_screen.png
+s 1
+f 55/142/30 10/143/30 9/144/30 56/145/30
diff --git a/mods/ma_pops_furniture/models/FM_sofa.obj b/mods/ma_pops_furniture/models/FM_sofa.obj
new file mode 100644
index 00000000..8efab14a
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_sofa.obj
@@ -0,0 +1,119 @@
+# Blender v2.75 (sub 4) OBJ File: 'sofa.blend'
+# www.blender.org
+o Sofa_Cube.001
+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
+v 0.500000 0.500000 0.200000
+v 0.500000 -0.500000 0.200000
+v -0.500000 0.500000 0.200000
+v -0.500000 -0.500000 0.200000
+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.000000 -0.500000
+v -0.500000 0.000000 0.200000
+v 0.500000 0.000000 0.200000
+v -0.447309 -0.124050 -0.450000
+v -0.447309 0.273622 -0.450000
+v -0.447309 -0.124050 0.250000
+v -0.447309 0.273622 0.250000
+v -0.647309 -0.124050 -0.450000
+v -0.647309 0.273622 -0.450000
+v -0.647309 -0.124050 0.250000
+v -0.647309 0.273622 0.250000
+v 0.652691 -0.124050 -0.450000
+v 0.652691 0.273622 -0.450000
+v 0.652691 -0.124050 0.250000
+v 0.652691 0.273622 0.250000
+v 0.452691 -0.124050 -0.450000
+v 0.452691 0.273622 -0.450000
+v 0.452691 -0.124050 0.250000
+v 0.452691 0.273622 0.250000
+vt 0.700422 0.500000
+vt 1.000000 0.500000
+vt 1.000000 0.000000
+vt 0.700422 0.000000
+vt 0.001407 0.500000
+vt 0.001407 0.000000
+vt 0.992586 0.555593
+vt 0.008821 0.555593
+vt 0.008821 0.063711
+vt 0.992586 0.063711
+vt 0.001407 0.700000
+vt 0.001407 1.000000
+vt 1.000000 1.000000
+vt 1.000000 0.700000
+vt 0.996225 0.617831
+vt 0.996225 0.915144
+vt 0.005182 0.915144
+vt 0.005182 0.617831
+vt 1.000000 0.736766
+vt 0.001407 0.736766
+vt 0.001407 0.034817
+vt 1.000000 0.034817
+vt 0.700422 1.000000
+vt 0.008891 0.936254
+vt 0.008891 0.444442
+vt 0.992516 0.444442
+vt 0.992516 0.936254
+vt 0.158077 0.708334
+vt 0.691052 0.708334
+vt 0.691052 0.405549
+vt 0.158077 0.405549
+vt 0.841281 0.708334
+vt 0.993559 0.708334
+vt 0.993559 0.405549
+vt 0.841281 0.405549
+vt 0.774934 0.755988
+vt 0.074195 0.755988
+vt 0.074195 0.357896
+vt 0.774934 0.357896
+vt 0.841281 0.157374
+vt 0.841281 0.690348
+vt 0.993559 0.690348
+vt 0.993559 0.157374
+vt 0.003749 0.708334
+vt 0.156028 0.708334
+vt 0.156028 0.405549
+vt 0.003749 0.405549
+vt 0.003749 0.157374
+vt 0.003749 0.690348
+vt 0.156028 0.690348
+vt 0.156028 0.157374
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+s off
+f 16/1/1 12/2/1 2/3/1 8/4/1
+f 12/5/2 13/2/2 5/3/2 2/6/2
+f 15/1/3 14/5/3 4/6/3 10/4/3
+f 14/7/4 11/8/4 1/9/4 4/10/4
+f 8/11/5 2/12/5 5/13/5 10/14/5
+f 9/15/6 6/16/6 3/17/6 7/18/6
+f 15/19/6 16/20/6 11/21/6 14/22/6
+f 1/6/5 8/11/5 10/14/5 4/3/5
+f 13/2/3 15/1/3 10/4/3 5/3/3
+f 11/5/1 16/1/1 8/4/1 1/6/1
+f 6/13/3 9/23/3 15/1/3 13/2/3
+f 7/24/4 16/25/4 15/26/4 9/27/4
+f 3/12/2 6/13/2 13/2/2 12/5/2
+f 7/23/1 3/13/1 12/2/1 16/1/1
+f 18/28/1 20/29/1 19/30/1 17/31/1
+f 20/32/2 24/33/2 23/34/2 19/35/2
+f 24/36/3 22/37/3 21/38/3 23/39/3
+f 22/33/4 18/32/4 17/35/4 21/34/4
+f 17/40/5 19/41/5 23/42/5 21/43/5
+f 22/43/6 24/42/6 20/41/6 18/40/6
+f 26/37/1 28/36/1 27/39/1 25/38/1
+f 28/44/2 32/45/2 31/46/2 27/47/2
+f 32/29/3 30/28/3 29/31/3 31/30/3
+f 30/45/4 26/44/4 25/47/4 29/46/4
+f 25/48/5 27/49/5 31/50/5 29/51/5
+f 30/51/6 32/50/6 28/49/6 26/48/6
diff --git a/mods/ma_pops_furniture/models/FM_sofa_c.obj b/mods/ma_pops_furniture/models/FM_sofa_c.obj
new file mode 100644
index 00000000..67cee62e
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_sofa_c.obj
@@ -0,0 +1,96 @@
+# Blender v2.75 (sub 4) OBJ File: 'sofa.blend'
+# www.blender.org
+o Sofa_C_Cube.006
+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
+v 0.500000 0.500000 0.200000
+v 0.500000 -0.500000 0.200000
+v -0.500000 0.500000 0.200000
+v -0.500000 -0.500000 0.200000
+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.000000 -0.500000
+v -0.500000 0.000000 0.200000
+v 0.500000 0.000000 0.200000
+v -0.199400 0.500000 0.500000
+v -0.199400 -0.500000 0.500000
+v -0.199400 -0.500000 -0.500000
+v -0.199400 0.500000 0.200000
+v -0.199400 -0.500000 0.200000
+v -0.199400 0.000000 0.200000
+v -0.199400 0.000000 -0.500000
+v -0.199400 0.000000 0.500000
+v -0.199400 0.500000 -0.500000
+v -0.500000 0.500000 -0.500000
+vt 0.700000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 -0.000000
+vt 0.700000 -0.000000
+vt 0.700574 0.496063
+vt 1.000000 0.496063
+vt 0.700574 0.000000
+vt 0.000000 0.500000
+vt 0.000000 0.000000
+vt 0.696668 0.500000
+vt 0.696668 -0.000000
+vt 0.696625 0.688976
+vt 0.696625 0.984251
+vt 0.996032 0.984251
+vt 0.996032 0.688976
+vt 0.700593 0.694488
+vt 0.700593 0.992126
+vt 0.003968 0.992126
+vt 0.003968 0.694488
+vt 0.003968 0.000000
+vt 0.696625 0.000000
+vt 0.996032 0.000000
+vt 1.000000 1.000000
+vt 0.700000 1.000000
+vt 1.000000 0.992126
+vt 0.003906 0.992126
+vt 0.003906 0.496063
+vt -0.000000 1.000000
+vt 0.696668 1.000000
+vt 0.000000 0.688976
+vt 1.000000 0.694488
+vt 0.000000 0.984251
+vt 0.996094 0.500000
+vt 0.996094 -0.000000
+vt 0.003906 0.000000
+vt 0.996094 1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+s off
+f 16/1/1 12/2/1 2/3/1 8/4/1
+f 24/5/2 13/6/2 5/3/2 18/7/2
+f 15/1/3 14/8/3 4/9/3 10/4/3
+f 23/10/4 11/8/4 1/9/4 19/11/4
+f 21/12/5 18/13/5 5/14/5 10/15/5
+f 20/16/6 17/17/6 3/18/6 7/19/6
+f 22/16/6 16/19/6 11/20/6 23/7/6
+f 19/21/5 21/12/5 10/15/5 4/22/5
+f 13/2/3 15/1/3 10/4/3 5/3/3
+f 11/8/1 16/1/1 8/4/1 1/9/1
+f 6/23/3 9/24/3 15/1/3 13/2/3
+f 17/17/2 6/25/2 13/6/2 24/5/2
+f 7/24/1 3/23/1 12/2/1 16/1/1
+f 3/26/2 17/17/2 24/5/2 12/27/2
+f 7/28/4 16/8/4 22/10/4 20/29/4
+f 1/9/5 8/30/5 21/12/5 19/21/5
+f 9/31/6 6/25/6 17/17/6 20/16/6
+f 8/30/5 2/32/5 18/13/5 21/12/5
+f 14/33/4 23/10/4 19/11/4 4/34/4
+f 12/27/2 24/5/2 18/7/2 2/35/2
+f 23/10/4 14/33/4 26/36/4 25/29/4
+f 9/31/6 20/16/6 25/7/6 26/3/6
+f 22/1/1 23/8/1 25/28/1 20/24/1
+f 14/8/3 15/1/3 9/24/3 26/28/3
diff --git a/mods/ma_pops_furniture/models/FM_sofa_l.obj b/mods/ma_pops_furniture/models/FM_sofa_l.obj
new file mode 100644
index 00000000..0b5275c7
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_sofa_l.obj
@@ -0,0 +1,97 @@
+# Blender v2.75 (sub 4) OBJ File: 'sofa.blend'
+# www.blender.org
+o Sofa_L_Cube.004
+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
+v 0.500000 0.500000 0.200000
+v 0.500000 -0.500000 0.200000
+v -0.500000 0.500000 0.200000
+v -0.500000 -0.500000 0.200000
+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.000000 -0.500000
+v -0.500000 0.000000 0.200000
+v 0.500000 0.000000 0.200000
+v -0.447309 -0.124050 -0.450000
+v -0.447309 0.273622 -0.450000
+v -0.447309 -0.124050 0.250000
+v -0.447309 0.273622 0.250000
+v -0.647309 -0.124050 -0.450000
+v -0.647309 0.273622 -0.450000
+v -0.647309 -0.124050 0.250000
+v -0.647309 0.273622 0.250000
+vt 0.700422 0.500000
+vt 1.000000 0.500000
+vt 1.000000 0.000000
+vt 0.700422 0.000000
+vt 0.001407 0.500000
+vt 0.001407 0.000000
+vt 0.992586 0.555593
+vt 0.008821 0.555593
+vt 0.008821 0.063711
+vt 0.992586 0.063711
+vt 0.001407 0.700000
+vt 0.001407 1.000000
+vt 1.000000 1.000000
+vt 1.000000 0.700000
+vt 0.996225 0.617831
+vt 0.996225 0.915144
+vt 0.005182 0.915144
+vt 0.005182 0.617831
+vt 1.000000 0.736766
+vt 0.001407 0.736766
+vt 0.001407 0.034817
+vt 1.000000 0.034817
+vt 0.700422 1.000000
+vt 0.008891 0.936254
+vt 0.008891 0.444442
+vt 0.992516 0.444442
+vt 0.992516 0.936254
+vt 0.158077 0.708334
+vt 0.691052 0.708334
+vt 0.691052 0.405549
+vt 0.158077 0.405549
+vt 0.841281 0.708334
+vt 0.993559 0.708334
+vt 0.993559 0.405549
+vt 0.841281 0.405549
+vt 0.774934 0.755988
+vt 0.074195 0.755988
+vt 0.074195 0.357896
+vt 0.774934 0.357896
+vt 0.841281 0.157374
+vt 0.841281 0.690348
+vt 0.993559 0.690348
+vt 0.993559 0.157374
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+s off
+f 16/1/1 12/2/1 2/3/1 8/4/1
+f 12/5/2 13/2/2 5/3/2 2/6/2
+f 15/1/3 14/5/3 4/6/3 10/4/3
+f 14/7/4 11/8/4 1/9/4 4/10/4
+f 8/11/5 2/12/5 5/13/5 10/14/5
+f 9/15/6 6/16/6 3/17/6 7/18/6
+f 15/19/6 16/20/6 11/21/6 14/22/6
+f 1/6/5 8/11/5 10/14/5 4/3/5
+f 13/2/3 15/1/3 10/4/3 5/3/3
+f 11/5/1 16/1/1 8/4/1 1/6/1
+f 6/13/3 9/23/3 15/1/3 13/2/3
+f 7/24/4 16/25/4 15/26/4 9/27/4
+f 3/12/2 6/13/2 13/2/2 12/5/2
+f 7/23/1 3/13/1 12/2/1 16/1/1
+f 18/28/1 20/29/1 19/30/1 17/31/1
+f 20/32/2 24/33/2 23/34/2 19/35/2
+f 24/36/3 22/37/3 21/38/3 23/39/3
+f 22/33/4 18/32/4 17/35/4 21/34/4
+f 17/40/5 19/41/5 23/42/5 21/43/5
+f 22/43/6 24/42/6 20/41/6 18/40/6
diff --git a/mods/ma_pops_furniture/models/FM_sofa_m.obj b/mods/ma_pops_furniture/models/FM_sofa_m.obj
new file mode 100644
index 00000000..127f505e
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_sofa_m.obj
@@ -0,0 +1,67 @@
+# Blender v2.75 (sub 4) OBJ File: 'sofa.blend'
+# www.blender.org
+o Sofa_M_Cube.002
+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
+v 0.500000 0.500000 0.200000
+v 0.500000 -0.500000 0.200000
+v -0.500000 0.500000 0.200000
+v -0.500000 -0.500000 0.200000
+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.000000 -0.500000
+v -0.500000 0.000000 0.200000
+v 0.500000 0.000000 0.200000
+vt 0.700422 0.500000
+vt 1.000000 0.500000
+vt 1.000000 0.000000
+vt 0.700422 0.000000
+vt 0.001407 0.500000
+vt 0.001407 0.000000
+vt 0.992586 0.555593
+vt 0.008821 0.555593
+vt 0.008821 0.063711
+vt 0.992586 0.063711
+vt 0.001407 0.700000
+vt 0.001407 1.000000
+vt 1.000000 1.000000
+vt 1.000000 0.700000
+vt 0.996225 0.617831
+vt 0.996225 0.915144
+vt 0.005182 0.915144
+vt 0.005182 0.617831
+vt 1.000000 0.736766
+vt 0.001407 0.736766
+vt 0.001407 0.034817
+vt 1.000000 0.034817
+vt 0.700422 1.000000
+vt 0.008891 0.936254
+vt 0.008891 0.444442
+vt 0.992516 0.444442
+vt 0.992516 0.936254
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+s off
+f 16/1/1 12/2/1 2/3/1 8/4/1
+f 12/5/2 13/2/2 5/3/2 2/6/2
+f 15/1/3 14/5/3 4/6/3 10/4/3
+f 14/7/4 11/8/4 1/9/4 4/10/4
+f 8/11/5 2/12/5 5/13/5 10/14/5
+f 9/15/6 6/16/6 3/17/6 7/18/6
+f 15/19/6 16/20/6 11/21/6 14/22/6
+f 1/6/5 8/11/5 10/14/5 4/3/5
+f 13/2/3 15/1/3 10/4/3 5/3/3
+f 11/5/1 16/1/1 8/4/1 1/6/1
+f 6/13/3 9/23/3 15/1/3 13/2/3
+f 7/24/4 16/25/4 15/26/4 9/27/4
+f 3/12/2 6/13/2 13/2/2 12/5/2
+f 7/23/1 3/13/1 12/2/1 16/1/1
diff --git a/mods/ma_pops_furniture/models/FM_sofa_r.obj b/mods/ma_pops_furniture/models/FM_sofa_r.obj
new file mode 100644
index 00000000..0dea0480
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_sofa_r.obj
@@ -0,0 +1,97 @@
+# Blender v2.75 (sub 4) OBJ File: 'sofa.blend'
+# www.blender.org
+o Sofa_R_Cube.005
+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
+v 0.500000 0.500000 0.200000
+v 0.500000 -0.500000 0.200000
+v -0.500000 0.500000 0.200000
+v -0.500000 -0.500000 0.200000
+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.000000 -0.500000
+v -0.500000 0.000000 0.200000
+v 0.500000 0.000000 0.200000
+v 0.652691 -0.124050 -0.450000
+v 0.652691 0.273622 -0.450000
+v 0.652691 -0.124050 0.250000
+v 0.652691 0.273622 0.250000
+v 0.452691 -0.124050 -0.450000
+v 0.452691 0.273622 -0.450000
+v 0.452691 -0.124050 0.250000
+v 0.452691 0.273622 0.250000
+vt 0.700422 0.500000
+vt 1.000000 0.500000
+vt 1.000000 0.000000
+vt 0.700422 0.000000
+vt 0.001407 0.500000
+vt 0.001407 0.000000
+vt 0.992586 0.555593
+vt 0.008821 0.555593
+vt 0.008821 0.063711
+vt 0.992586 0.063711
+vt 0.001407 0.700000
+vt 0.001407 1.000000
+vt 1.000000 1.000000
+vt 1.000000 0.700000
+vt 0.996225 0.617831
+vt 0.996225 0.915144
+vt 0.005182 0.915144
+vt 0.005182 0.617831
+vt 1.000000 0.736766
+vt 0.001407 0.736766
+vt 0.001407 0.034817
+vt 1.000000 0.034817
+vt 0.700422 1.000000
+vt 0.008891 0.936254
+vt 0.008891 0.444442
+vt 0.992516 0.444442
+vt 0.992516 0.936254
+vt 0.074195 0.755988
+vt 0.774934 0.755988
+vt 0.774934 0.357896
+vt 0.074195 0.357896
+vt 0.003749 0.708334
+vt 0.156028 0.708334
+vt 0.156028 0.405549
+vt 0.003749 0.405549
+vt 0.691052 0.708334
+vt 0.158077 0.708334
+vt 0.158077 0.405549
+vt 0.691052 0.405549
+vt 0.003749 0.157374
+vt 0.003749 0.690348
+vt 0.156028 0.690348
+vt 0.156028 0.157374
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+s off
+f 16/1/1 12/2/1 2/3/1 8/4/1
+f 12/5/2 13/2/2 5/3/2 2/6/2
+f 15/1/3 14/5/3 4/6/3 10/4/3
+f 14/7/4 11/8/4 1/9/4 4/10/4
+f 8/11/5 2/12/5 5/13/5 10/14/5
+f 9/15/6 6/16/6 3/17/6 7/18/6
+f 15/19/6 16/20/6 11/21/6 14/22/6
+f 1/6/5 8/11/5 10/14/5 4/3/5
+f 13/2/3 15/1/3 10/4/3 5/3/3
+f 11/5/1 16/1/1 8/4/1 1/6/1
+f 6/13/3 9/23/3 15/1/3 13/2/3
+f 7/24/4 16/25/4 15/26/4 9/27/4
+f 3/12/2 6/13/2 13/2/2 12/5/2
+f 7/23/1 3/13/1 12/2/1 16/1/1
+f 18/28/1 20/29/1 19/30/1 17/31/1
+f 20/32/2 24/33/2 23/34/2 19/35/2
+f 24/36/3 22/37/3 21/38/3 23/39/3
+f 22/33/4 18/32/4 17/35/4 21/34/4
+f 17/40/5 19/41/5 23/42/5 21/43/5
+f 22/43/6 24/42/6 20/41/6 18/40/6
diff --git a/mods/ma_pops_furniture/models/FM_stone_path_1.obj b/mods/ma_pops_furniture/models/FM_stone_path_1.obj
new file mode 100644
index 00000000..51f796f9
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_stone_path_1.obj
@@ -0,0 +1,77 @@
+# Blender v2.75 (sub 4) OBJ File: 'stone_path.blend'
+# www.blender.org
+o Path_1_Plane.007
+v -0.400000 -0.500000 -0.100000
+v 0.100000 -0.500000 -0.100000
+v -0.400000 -0.500000 -0.400000
+v 0.100000 -0.500000 -0.400000
+v 0.400000 -0.500000 0.100000
+v -0.100000 -0.500000 0.100000
+v 0.400000 -0.500000 0.400000
+v -0.100000 -0.500000 0.400000
+v -0.200000 -0.400000 0.000000
+v -0.400000 -0.400000 0.000000
+v -0.400000 -0.500000 0.000000
+v -0.400000 -0.500000 0.400000
+v 0.200000 -0.400000 -0.000000
+v 0.400000 -0.400000 -0.000000
+v 0.200000 -0.400000 -0.400000
+v 0.400000 -0.400000 -0.400000
+v 0.400000 -0.500000 -0.400000
+v 0.200000 -0.500000 -0.400000
+v 0.400000 -0.500000 -0.000000
+v 0.200000 -0.500000 -0.000000
+v -0.200000 -0.500000 0.400000
+v -0.200000 -0.500000 0.000000
+v -0.200000 -0.400000 0.400000
+v -0.400000 -0.400000 0.400000
+v -0.100000 -0.400000 0.400000
+v 0.400000 -0.400000 0.400000
+v -0.100000 -0.400000 0.100000
+v 0.400000 -0.400000 0.100000
+v 0.100000 -0.400000 -0.400000
+v -0.400000 -0.400000 -0.400000
+v 0.100000 -0.400000 -0.100000
+v -0.400000 -0.400000 -0.100000
+vt 0.000000 0.500000
+vt 0.000000 0.000000
+vt 0.375000 0.000000
+vt 1.000000 0.000000
+vt 0.625000 1.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+vt 0.750000 1.000000
+vt 0.000000 0.625000
+vt 0.750000 0.500000
+vt 1.000000 0.500000
+vt 0.250000 0.000000
+vt 0.250000 0.500000
+vt 1.000000 0.375000
+vt 0.625000 0.625000
+vt 0.375000 0.375000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+s off
+f 10/1/1 11/1/1 12/2/1 24/2/1
+f 25/3/2 8/3/2 7/4/2 26/4/2
+f 29/5/3 4/5/3 3/6/3 30/6/3
+f 16/7/3 17/7/3 18/8/3 15/8/3
+f 30/6/1 3/6/1 1/9/1 32/9/1
+f 13/10/2 20/10/2 19/11/2 14/11/2
+f 24/2/4 23/12/4 9/13/4 10/1/4
+f 24/2/2 12/2/2 21/12/2 23/12/2
+f 15/8/1 18/8/1 20/10/1 13/10/1
+f 26/4/5 7/4/5 5/14/5 28/14/5
+f 31/15/5 2/15/5 4/5/5 29/5/5
+f 23/12/5 21/12/5 22/13/5 9/13/5
+f 14/11/5 19/11/5 17/7/5 16/7/5
+f 28/14/3 5/14/3 6/16/3 27/16/3
+f 32/9/2 1/9/2 2/15/2 31/15/2
+f 13/10/4 14/11/4 16/7/4 15/8/4
+f 32/9/4 31/15/4 29/5/4 30/6/4
+f 9/13/3 22/13/3 11/1/3 10/1/3
+f 27/16/1 6/16/1 8/3/1 25/3/1
+f 25/3/4 26/4/4 28/14/4 27/16/4
diff --git a/mods/ma_pops_furniture/models/FM_stone_path_2.obj b/mods/ma_pops_furniture/models/FM_stone_path_2.obj
new file mode 100644
index 00000000..670f398b
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_stone_path_2.obj
@@ -0,0 +1,77 @@
+# Blender v2.75 (sub 4) OBJ File: 'stone_path.blend'
+# www.blender.org
+o Path_2_Plane.005
+v -0.401461 -0.400000 -0.098539
+v 0.001461 -0.400000 -0.098539
+v -0.401461 -0.400000 -0.401461
+v 0.001461 -0.400000 -0.401461
+v -0.401461 -0.400000 0.401461
+v -0.098539 -0.400000 0.401461
+v -0.401461 -0.400000 -0.001461
+v -0.098539 -0.400000 -0.001461
+v -0.401461 -0.500000 0.401461
+v -0.098539 -0.500000 0.401461
+v -0.401461 -0.500000 -0.001461
+v -0.098539 -0.500000 -0.001461
+v 0.098539 -0.400000 -0.198539
+v 0.501461 -0.400000 -0.198539
+v 0.098539 -0.400000 -0.501461
+v 0.501461 -0.400000 -0.501461
+v -0.401461 -0.500000 -0.098539
+v 0.001461 -0.500000 -0.098539
+v -0.401461 -0.500000 -0.401461
+v 0.001461 -0.500000 -0.401461
+v 0.098539 -0.500000 -0.198539
+v 0.501461 -0.500000 -0.198539
+v 0.098539 -0.500000 -0.501461
+v 0.501461 -0.500000 -0.501461
+v 0.401461 -0.500000 -0.001461
+v -0.001461 -0.500000 -0.001461
+v 0.401461 -0.500000 0.301461
+v -0.001461 -0.500000 0.301461
+v 0.401461 -0.400000 -0.001461
+v -0.001461 -0.400000 -0.001461
+v 0.401461 -0.400000 0.301461
+v -0.001461 -0.400000 0.301461
+vt 0.000000 0.553758
+vt 0.446242 0.553758
+vt 0.446242 0.889249
+vt 0.000000 0.889249
+vt 0.000000 0.446242
+vt 0.000000 0.000000
+vt 0.553757 0.664509
+vt 1.000000 0.664509
+vt 1.000000 1.000000
+vt 0.553757 1.000000
+vt 0.889248 0.110752
+vt 0.889248 0.446242
+vt 0.335491 0.000000
+vt 0.335491 0.446242
+vt 0.443006 0.446242
+vt 0.443006 0.110752
+vn 0.000000 1.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 1.000000
+s off
+f 1/1/1 2/2/1 4/3/1 3/4/1
+f 7/5/2 11/5/2 9/6/2 5/6/2
+f 13/7/1 14/8/1 16/9/1 15/10/1
+f 31/11/3 27/11/3 25/12/3 29/12/3
+f 16/9/4 24/9/4 23/10/4 15/10/4
+f 5/6/1 6/13/1 8/14/1 7/5/1
+f 29/12/4 25/12/4 26/15/4 30/15/4
+f 5/6/5 9/6/5 10/13/5 6/13/5
+f 32/16/5 28/16/5 27/11/5 31/11/5
+f 8/14/4 12/14/4 11/5/4 7/5/4
+f 30/15/2 26/15/2 28/16/2 32/16/2
+f 6/13/3 10/13/3 12/14/3 8/14/3
+f 3/4/2 19/4/2 17/1/2 1/1/2
+f 32/16/1 31/11/1 29/12/1 30/15/1
+f 13/7/5 21/7/5 22/8/5 14/8/5
+f 1/1/5 17/1/5 18/2/5 2/2/5
+f 15/10/2 23/10/2 21/7/2 13/7/2
+f 2/2/3 18/2/3 20/3/3 4/3/3
+f 14/8/3 22/8/3 24/9/3 16/9/3
+f 4/3/4 20/3/4 19/4/4 3/4/4
diff --git a/mods/ma_pops_furniture/models/FM_stone_path_3.obj b/mods/ma_pops_furniture/models/FM_stone_path_3.obj
new file mode 100644
index 00000000..7f02a580
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_stone_path_3.obj
@@ -0,0 +1,94 @@
+# Blender v2.75 (sub 4) OBJ File: 'stone_path.blend'
+# www.blender.org
+o Path_3_Plane.006
+v -0.398223 -0.400000 -0.098539
+v 0.004699 -0.400000 -0.098539
+v -0.398223 -0.400000 -0.401461
+v 0.004699 -0.400000 -0.401461
+v -0.198223 -0.400000 0.501461
+v 0.104699 -0.400000 0.501461
+v -0.198223 -0.400000 0.098539
+v 0.104699 -0.400000 0.098539
+v 0.101777 -0.400000 -0.098539
+v 0.504699 -0.400000 -0.098539
+v 0.101777 -0.400000 -0.401461
+v 0.504699 -0.400000 -0.401461
+v 0.206537 -0.400000 0.396701
+v 0.499939 -0.400000 0.396701
+v 0.206537 -0.400000 0.003299
+v 0.499939 -0.400000 0.003299
+v -0.500061 -0.400000 0.396701
+v -0.500061 -0.400000 0.003299
+v -0.300061 -0.400000 0.396701
+v -0.300061 -0.400000 0.003299
+v -0.398223 -0.500000 -0.098539
+v 0.004699 -0.500000 -0.098539
+v -0.398223 -0.500000 -0.401461
+v 0.004699 -0.500000 -0.401461
+v -0.198223 -0.500000 0.501461
+v 0.104699 -0.500000 0.501461
+v -0.198223 -0.500000 0.098539
+v 0.104699 -0.500000 0.098539
+v 0.101777 -0.500000 -0.098539
+v 0.504699 -0.500000 -0.098539
+v 0.101777 -0.500000 -0.401461
+v 0.504699 -0.500000 -0.401461
+v 0.206537 -0.500000 0.396701
+v 0.499939 -0.500000 0.396701
+v 0.206537 -0.500000 0.003299
+v 0.499939 -0.500000 0.003299
+v -0.500061 -0.500000 0.396701
+v -0.500061 -0.500000 0.003299
+v -0.300061 -0.500000 0.396701
+v -0.300061 -0.500000 0.003299
+vt 0.101355 0.664509
+vt 0.502369 0.664509
+vt 0.502369 1.000000
+vt 0.101355 1.000000
+vt 0.300408 0.000000
+vt 0.601895 0.000000
+vt 0.601895 0.446242
+vt 0.300408 0.446242
+vt 0.598987 0.664509
+vt 1.000000 0.664509
+vt 1.000000 1.000000
+vt 0.598987 1.000000
+vt 0.703250 0.116023
+vt 0.995263 0.116023
+vt 0.995263 0.551722
+vt 0.703250 0.551722
+vt 0.000000 0.116023
+vt 0.199053 0.116023
+vt 0.199053 0.551722
+vt 0.000000 0.551722
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 1.000000 0.000000 0.000000
+s off
+f 1/1/1 2/2/1 4/3/1 3/4/1
+f 5/5/1 6/6/1 8/7/1 7/8/1
+f 9/9/1 10/10/1 12/11/1 11/12/1
+f 13/13/1 14/14/1 16/15/1 15/16/1
+f 17/17/1 19/18/1 20/19/1 18/20/1
+f 12/11/2 32/11/2 31/12/2 11/12/2
+f 7/8/3 27/8/3 25/5/3 5/5/3
+f 17/17/4 37/17/4 39/18/4 19/18/4
+f 15/16/3 35/16/3 33/13/3 13/13/3
+f 5/5/4 25/5/4 26/6/4 6/6/4
+f 13/13/4 33/13/4 34/14/4 14/14/4
+f 6/6/5 26/6/5 28/7/5 8/7/5
+f 14/14/5 34/14/5 36/15/5 16/15/5
+f 8/7/2 28/7/2 27/8/2 7/8/2
+f 3/4/3 23/4/3 21/1/3 1/1/3
+f 16/15/2 36/15/2 35/16/2 15/16/2
+f 11/12/3 31/12/3 29/9/3 9/9/3
+f 1/1/4 21/1/4 22/2/4 2/2/4
+f 18/20/3 38/20/3 37/17/3 17/17/3
+f 9/9/4 29/9/4 30/10/4 10/10/4
+f 2/2/5 22/2/5 24/3/5 4/3/5
+f 19/18/5 39/18/5 40/19/5 20/19/5
+f 10/10/5 30/10/5 32/11/5 12/11/5
+f 4/3/2 24/3/2 23/4/2 3/4/2
+f 20/19/2 40/19/2 38/20/2 18/20/2
diff --git a/mods/ma_pops_furniture/models/FM_stone_path_4.obj b/mods/ma_pops_furniture/models/FM_stone_path_4.obj
new file mode 100644
index 00000000..8270eb82
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_stone_path_4.obj
@@ -0,0 +1,76 @@
+# Blender v2.75 (sub 4) OBJ File: 'stone_path.blend'
+# www.blender.org
+o Path_4_Plane.004
+v -0.400000 -0.500000 -0.000000
+v -0.000000 -0.500000 -0.000000
+v -0.400000 -0.500000 -0.400000
+v -0.000000 -0.500000 -0.400000
+v 0.400000 -0.500000 0.000000
+v 0.000000 -0.500000 0.000000
+v 0.400000 -0.500000 0.400000
+v 0.000000 -0.500000 0.400000
+v 0.180000 -0.500000 -0.180000
+v 0.420000 -0.500000 -0.180000
+v 0.180000 -0.500000 -0.420000
+v 0.420000 -0.500000 -0.420000
+v -0.180000 -0.500000 0.180000
+v -0.420000 -0.500000 0.180000
+v -0.180000 -0.500000 0.420000
+v -0.420000 -0.500000 0.420000
+v -0.420000 -0.400000 0.420000
+v -0.180000 -0.400000 0.420000
+v -0.420000 -0.400000 0.180000
+v -0.180000 -0.400000 0.180000
+v 0.420000 -0.400000 -0.420000
+v 0.180000 -0.400000 -0.420000
+v 0.420000 -0.400000 -0.180000
+v 0.180000 -0.400000 -0.180000
+v 0.000000 -0.400000 0.400000
+v 0.400000 -0.400000 0.400000
+v 0.000000 -0.400000 0.000000
+v 0.400000 -0.400000 0.000000
+v -0.000000 -0.400000 -0.400000
+v -0.400000 -0.400000 -0.400000
+v -0.000000 -0.400000 -0.000000
+v -0.400000 -0.400000 -0.000000
+vt 1.000000 1.000000
+vt 0.714286 1.000000
+vt 0.500000 0.023810
+vt 0.976191 0.023810
+vt 1.000000 0.714286
+vt 0.500000 0.976191
+vt 0.023810 0.976191
+vt 0.000000 0.000000
+vt 0.285714 0.000000
+vt 0.285714 0.285714
+vt 0.000000 0.285714
+vt 0.023810 0.500000
+vt 0.714286 0.714286
+vt 0.976191 0.500000
+vt 0.500000 0.500000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -1.000000 0.000000 0.000000
+s off
+f 21/1/1 12/1/1 11/2/1 22/2/1
+f 25/3/2 8/3/2 7/4/2 26/4/2
+f 23/5/3 10/5/3 12/1/3 21/1/3
+f 29/6/1 4/6/1 3/7/1 30/7/1
+f 17/8/4 18/9/4 20/10/4 19/11/4
+f 30/7/5 3/7/5 1/12/5 32/12/5
+f 19/11/5 14/11/5 16/8/5 17/8/5
+f 24/13/4 23/5/4 21/1/4 22/2/4
+f 22/2/5 11/2/5 9/13/5 24/13/5
+f 17/8/2 16/8/2 15/9/2 18/9/2
+f 26/4/3 7/4/3 5/14/3 28/14/3
+f 31/15/3 2/15/3 4/6/3 29/6/3
+f 18/9/3 15/9/3 13/10/3 20/10/3
+f 28/14/1 5/14/1 6/15/1 27/15/1
+f 32/12/2 1/12/2 2/15/2 31/15/2
+f 20/10/1 13/10/1 14/11/1 19/11/1
+f 32/12/4 31/15/4 29/6/4 30/7/4
+f 24/13/2 9/13/2 10/5/2 23/5/2
+f 27/15/5 6/15/5 8/3/5 25/3/5
+f 25/3/4 26/4/4 28/14/4 27/15/4
diff --git a/mods/ma_pops_furniture/models/FM_toilet_close.obj b/mods/ma_pops_furniture/models/FM_toilet_close.obj
new file mode 100644
index 00000000..80689835
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_toilet_close.obj
@@ -0,0 +1,231 @@
+# Blender v2.75 (sub 0) OBJ File: 'toilet.blend'
+# www.blender.org
+o Toilet_close_Cube.001
+v -0.216380 -0.364267 -0.195066
+v 0.230162 -0.364267 -0.195066
+v 0.230162 -0.364267 0.375364
+v -0.216380 -0.364267 0.375364
+v -0.216380 -0.503120 -0.195066
+v -0.216380 -0.503120 0.375364
+v 0.230162 -0.503120 0.375364
+v 0.230162 -0.503120 -0.195065
+v -0.026301 0.497050 0.418905
+v -0.099953 0.497050 0.418905
+v -0.099953 0.495923 0.354172
+v -0.026301 0.495923 0.354172
+v -0.099953 0.541235 0.418294
+v -0.099953 0.540108 0.353560
+v -0.026301 0.541235 0.418294
+v -0.026301 0.540108 0.353560
+v 0.332628 0.485120 0.489409
+v -0.318846 0.485120 0.489409
+v 0.332628 0.485120 0.286360
+v -0.318846 0.485120 0.286360
+v -0.318846 -0.032421 0.286360
+v 0.332628 -0.032421 0.286360
+v 0.332628 -0.367155 0.286360
+v -0.318846 -0.367155 0.286360
+v 0.332628 -0.032421 0.489409
+v -0.318846 -0.032421 0.489409
+v -0.318846 -0.367155 0.489409
+v 0.332628 -0.367155 0.489409
+v -0.318846 -0.032421 -0.334670
+v -0.318846 -0.367155 -0.334670
+v 0.332628 -0.032421 -0.334670
+v 0.332628 -0.367155 -0.334670
+v 0.074405 0.497050 0.418905
+v 0.000753 0.497050 0.418905
+v 0.000753 0.495923 0.354172
+v 0.074405 0.495923 0.354172
+v 0.000753 0.541235 0.418294
+v 0.000753 0.540108 0.353560
+v 0.074405 0.541235 0.418294
+v 0.074405 0.540108 0.353560
+v 0.298674 0.038404 0.235087
+v -0.290831 0.038404 0.235087
+v -0.290831 -0.026774 0.235208
+v 0.298674 -0.026774 0.235208
+v -0.290831 0.038576 -0.305720
+v -0.290831 -0.026603 -0.305600
+v 0.298674 0.038575 -0.305720
+v 0.298674 -0.026603 -0.305601
+v -0.230085 -0.040764 0.201747
+v 0.243868 -0.040764 0.201747
+v 0.243868 -0.040764 -0.250057
+v -0.230085 -0.040764 -0.250057
+v -0.230085 -0.318566 0.201747
+v 0.243867 -0.318566 0.201747
+v 0.163555 -0.203467 -0.206424
+v -0.149773 -0.203467 -0.206424
+vt 0.839078 0.324803
+vt -0.012876 0.324803
+vt -0.012876 0.991727
+vt 0.839077 0.991727
+vt -0.096533 0.324803
+vt -0.303914 0.324803
+vt -0.303914 0.991727
+vt -0.096533 0.991727
+vt 0.839078 -0.096533
+vt 0.839077 -0.303914
+vt -0.012876 -0.303914
+vt -0.012876 -0.096533
+vt -0.077905 0.608692
+vt -0.077905 0.498690
+vt 0.018775 0.498690
+vt 0.018775 0.608692
+vt -0.077905 1.189870
+vt -0.076993 1.255861
+vt 0.019689 1.254178
+vt 0.018775 1.188187
+vt -0.076993 0.498690
+vt -0.076993 0.608692
+vt 0.019689 0.608692
+vt 0.019689 0.498690
+vt 1.188187 0.608692
+vt 1.188187 0.498690
+vt 1.254178 0.498690
+vt 1.254178 0.608692
+vt 1.255861 0.608692
+vt 1.255861 0.498690
+vt 1.189870 0.498690
+vt 1.189870 0.608692
+vt 0.120055 1.144763
+vt 0.120055 0.171767
+vt -0.183205 0.171767
+vt -0.183205 1.144763
+vt -0.183205 0.399088
+vt 0.120055 0.399088
+vt 0.120055 -0.100846
+vt -0.183205 -0.100846
+vt 1.047581 0.399088
+vt 1.047581 -0.100846
+vt 0.246427 1.012196
+vt 0.246427 0.304334
+vt 0.399088 1.144763
+vt 0.399088 0.171767
+vt -0.100846 0.171767
+vt -0.100846 1.144763
+vt 1.047581 0.171767
+vt 1.047581 1.144763
+vt 0.120055 1.172052
+vt -0.183205 1.172052
+vt -0.077905 0.759099
+vt -0.077905 0.649098
+vt 0.018775 0.649098
+vt 0.018775 0.759099
+vt -0.076993 0.649098
+vt -0.076993 0.759099
+vt 0.019689 0.759099
+vt 0.019689 0.649098
+vt 1.188187 0.759099
+vt 1.188187 0.649098
+vt 1.254178 0.649098
+vt 1.254178 0.759099
+vt 1.255861 0.759099
+vt 1.255861 0.649098
+vt 1.189870 0.649098
+vt 1.189870 0.759099
+vt 1.172052 0.171767
+vt 1.172052 1.144763
+vt 0.921209 0.386628
+vt 0.246427 0.386628
+vt 0.246427 -0.028277
+vt 0.856042 0.143627
+vt 0.921209 0.304334
+vt 0.921209 1.012196
+vt 0.856042 0.892247
+vt 0.856042 0.424283
+vt 0.386628 1.012196
+vt 0.386628 0.304334
+vt 0.143627 0.424283
+vt 0.143627 0.892247
+vt -0.028277 1.012196
+vt -0.028277 0.304334
+vt 0.085590 0.552995
+vt 0.926569 0.552921
+vt 0.926318 0.442387
+vt 0.085337 0.442464
+vt 0.469592 0.001391
+vt 0.469592 1.001104
+vt 0.580109 1.001104
+vt 0.580109 0.001391
+vt 0.926615 0.799443
+vt 0.085637 0.800223
+vt 0.085291 0.689692
+vt 0.926271 0.688910
+vt 0.092622 1.001102
+vt 0.092622 0.001391
+vt 0.933506 0.001391
+vt 0.933506 1.001104
+vt 0.935429 1.001104
+vt 0.935429 0.001391
+vt 0.094547 0.001391
+vt 0.094547 1.001102
+vn 0.000000 1.000000 0.000000
+vn -0.000000 0.000000 1.000000
+vn -1.000000 0.000000 -0.000000
+vn 0.000000 -0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 -0.999800 0.017400
+vn 0.000000 0.999800 -0.017400
+vn 0.000000 -0.013800 -0.999900
+vn 0.000000 0.013800 0.999900
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 0.995200 -0.098100
+vn 0.975000 0.189300 0.116400
+vn 0.093600 0.995600 0.000000
+vn 0.000000 0.995200 0.098100
+vn -0.093600 0.995600 -0.000000
+vn -0.000000 0.962500 0.271400
+vn -0.975000 0.189300 0.116400
+vn -0.000000 0.259000 0.965900
+vn -0.000000 -0.001800 -1.000000
+vn -0.000000 -1.000000 -0.000300
+vn 0.000000 1.000000 0.000300
+g Toilet_close_Cube.001_White
+s off
+f 5/1/1 6/2/1 7/3/1 8/4/1
+f 1/5/2 5/6/2 8/7/2 2/8/2
+f 2/9/3 8/10/3 7/11/3 3/12/3
+f 3/8/4 7/7/4 6/6/4 4/5/4
+f 5/10/5 1/9/5 4/12/5 6/11/5
+f 9/13/6 10/14/6 11/15/6 12/16/6
+f 10/17/3 13/18/3 14/19/3 11/20/3
+f 13/21/7 15/22/7 16/23/7 14/24/7
+f 15/18/5 9/17/5 12/20/5 16/19/5
+f 12/25/8 11/26/8 14/27/8 16/28/8
+f 15/29/9 13/30/9 10/31/9 9/32/9
+f 19/33/1 20/34/1 18/35/1 17/36/1
+f 26/37/3 21/38/3 24/39/3 27/40/3
+f 31/41/5 22/38/5 23/39/5 32/42/5
+f 28/36/10 27/35/10 24/34/10 23/33/10
+f 21/34/11 22/33/11 50/43/11 49/44/11
+f 25/45/2 26/46/2 27/47/2 28/48/2
+f 21/38/3 29/41/3 30/42/3 24/39/3
+f 29/46/4 31/45/4 32/48/4 30/47/4
+f 22/38/5 25/37/5 28/40/5 23/39/5
+f 23/33/10 24/34/10 30/49/10 32/50/10
+f 25/37/5 22/38/5 19/51/5 17/52/5
+f 33/53/6 34/54/6 35/55/6 36/56/6
+f 34/17/3 37/18/3 38/19/3 35/20/3
+f 37/57/7 39/58/7 40/59/7 38/60/7
+f 39/18/5 33/17/5 36/20/5 40/19/5
+f 36/61/8 35/62/8 38/63/8 40/64/8
+f 39/65/9 37/66/9 34/67/9 33/68/9
+f 22/45/4 21/46/4 20/69/4 19/70/4
+f 21/38/3 26/37/3 18/52/3 20/51/3
+f 26/46/2 25/45/2 17/70/2 18/69/2
+f 52/71/12 49/72/12 53/73/12 56/74/12
+f 29/49/13 21/34/13 49/44/13 52/75/13
+f 31/50/14 29/49/14 52/75/14 51/76/14
+f 22/33/15 31/50/15 51/76/15 50/43/15
+f 55/77/16 56/78/16 53/44/16 54/43/16
+f 50/72/17 51/71/17 55/74/17 54/73/17
+f 51/79/18 52/80/18 56/81/18 55/82/18
+f 49/80/4 50/79/4 54/83/4 53/84/4
+g Toilet_close_Cube.001_Brown
+f 42/85/3 45/86/3 46/87/3 43/88/3
+f 45/89/19 47/90/19 48/91/19 46/92/19
+f 47/93/5 41/94/5 44/95/5 48/96/5
+f 44/97/20 43/98/20 46/99/20 48/100/20
+f 47/101/21 45/102/21 42/103/21 41/104/21
diff --git a/mods/ma_pops_furniture/models/FM_toilet_open.obj b/mods/ma_pops_furniture/models/FM_toilet_open.obj
new file mode 100644
index 00000000..c275f704
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_toilet_open.obj
@@ -0,0 +1,230 @@
+# Blender v2.75 (sub 0) OBJ File: 'toilet.blend'
+# www.blender.org
+o Toilet_open_Cube.002
+v -0.216380 -0.364267 -0.195066
+v 0.230162 -0.364267 -0.195066
+v 0.230162 -0.364267 0.375364
+v -0.216380 -0.364267 0.375364
+v -0.216380 -0.503120 -0.195066
+v -0.216380 -0.503120 0.375364
+v 0.230162 -0.503120 0.375364
+v 0.230162 -0.503120 -0.195065
+v -0.026301 0.497050 0.418905
+v -0.099953 0.497050 0.418905
+v -0.099953 0.495923 0.354172
+v -0.026301 0.495923 0.354172
+v -0.099953 0.541235 0.418294
+v -0.099953 0.540108 0.353560
+v -0.026301 0.541235 0.418294
+v -0.026301 0.540108 0.353560
+v 0.332628 0.485120 0.489409
+v -0.318846 0.485120 0.489409
+v 0.332628 0.485120 0.286360
+v -0.318846 0.485120 0.286360
+v -0.318846 -0.032421 0.286360
+v 0.332628 -0.032421 0.286360
+v 0.332628 -0.367155 0.286360
+v -0.318846 -0.367155 0.286360
+v 0.332628 -0.032421 0.489409
+v -0.318846 -0.032421 0.489409
+v -0.318846 -0.367155 0.489409
+v 0.332628 -0.367155 0.489409
+v -0.318846 -0.032421 -0.334670
+v -0.318846 -0.367155 -0.334670
+v 0.332628 -0.032421 -0.334670
+v 0.332628 -0.367155 -0.334670
+v 0.074405 0.497050 0.418905
+v 0.000753 0.497050 0.418905
+v 0.000753 0.495923 0.354172
+v 0.074405 0.495923 0.354172
+v 0.000753 0.541235 0.418294
+v 0.000753 0.540108 0.353560
+v 0.074405 0.541235 0.418294
+v 0.074405 0.540108 0.353560
+v 0.298674 -0.026545 0.261491
+v -0.290831 -0.026545 0.261491
+v -0.290831 -0.027680 0.196323
+v 0.298674 -0.027680 0.196323
+v -0.290831 0.469302 0.253946
+v -0.290831 0.468168 0.188777
+v 0.298674 0.469302 0.253946
+v 0.298675 0.468168 0.188777
+v -0.230085 -0.040764 0.201747
+v 0.243868 -0.040764 0.201747
+v 0.243868 -0.040764 -0.250057
+v -0.230085 -0.040764 -0.250057
+v -0.230085 -0.318566 0.201747
+v 0.243867 -0.318566 0.201747
+v 0.163555 -0.203467 -0.206424
+v -0.149773 -0.203467 -0.206424
+vt 0.839078 0.324803
+vt -0.012876 0.324803
+vt -0.012876 0.991727
+vt 0.839077 0.991727
+vt -0.096533 0.324803
+vt -0.303914 0.324803
+vt -0.303914 0.991727
+vt -0.096533 0.991727
+vt 0.839078 -0.096533
+vt 0.839077 -0.303914
+vt -0.012876 -0.303914
+vt -0.012876 -0.096533
+vt -0.077905 0.608692
+vt -0.077905 0.498690
+vt 0.018775 0.498690
+vt 0.018775 0.608692
+vt -0.077905 1.189870
+vt -0.076993 1.255861
+vt 0.019689 1.254178
+vt 0.018775 1.188187
+vt -0.076993 0.498690
+vt -0.076993 0.608692
+vt 0.019689 0.608692
+vt 0.019689 0.498690
+vt 1.188187 0.608692
+vt 1.188187 0.498690
+vt 1.254178 0.498690
+vt 1.254178 0.608692
+vt 1.255861 0.608692
+vt 1.255861 0.498690
+vt 1.189870 0.498690
+vt 1.189870 0.608692
+vt 0.120055 1.144763
+vt 0.120055 0.171767
+vt -0.183205 0.171767
+vt -0.183205 1.144763
+vt -0.183205 0.399088
+vt 0.120055 0.399088
+vt 0.120055 -0.100846
+vt -0.183205 -0.100846
+vt 1.047581 0.399088
+vt 1.047581 -0.100846
+vt 0.246427 1.012196
+vt 0.246427 0.304334
+vt 0.399088 1.144763
+vt 0.399088 0.171767
+vt -0.100846 0.171767
+vt -0.100846 1.144763
+vt 1.047581 0.171767
+vt 1.047581 1.144763
+vt 0.120055 1.172052
+vt -0.183205 1.172052
+vt -0.077905 0.759099
+vt -0.077905 0.649098
+vt 0.018775 0.649098
+vt 0.018775 0.759099
+vt -0.076993 0.649098
+vt -0.076993 0.759099
+vt 0.019689 0.759099
+vt 0.019689 0.649098
+vt 1.188187 0.759099
+vt 1.188187 0.649098
+vt 1.254178 0.649098
+vt 1.254178 0.759099
+vt 1.255861 0.759099
+vt 1.255861 0.649098
+vt 1.189870 0.649098
+vt 1.189870 0.759099
+vt 1.172052 0.171767
+vt 1.172052 1.144763
+vt 0.921209 0.386628
+vt 0.246427 0.386628
+vt 0.246427 -0.028277
+vt 0.856042 0.143627
+vt 0.921209 0.304334
+vt 0.921209 1.012196
+vt 0.856042 0.892247
+vt 0.856042 0.424283
+vt 0.386628 1.012196
+vt 0.386628 0.304334
+vt 0.143627 0.424283
+vt 0.143627 0.892247
+vt -0.028277 1.012196
+vt -0.028277 0.304334
+vt 0.085590 0.552995
+vt 0.926569 0.552921
+vt 0.926318 0.442387
+vt 0.085337 0.442464
+vt 0.469592 0.001391
+vt 0.469592 1.001104
+vt 0.580109 1.001104
+vt 0.580109 0.001391
+vt 0.926615 0.799443
+vt 0.085637 0.800223
+vt 0.085291 0.689692
+vt 0.926271 0.688910
+vt 0.092622 1.001102
+vt 0.092622 0.001391
+vt 0.933506 0.001391
+vt 0.933506 1.001104
+vt 0.935429 1.001104
+vt 0.935429 0.001391
+vt 0.094547 0.001391
+vt 0.094547 1.001102
+vn 0.000000 1.000000 0.000000
+vn -0.000000 0.000000 1.000000
+vn -1.000000 0.000000 -0.000000
+vn 0.000000 -0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 -0.999800 0.017400
+vn 0.000000 0.999800 -0.017400
+vn 0.000000 -0.013800 -0.999900
+vn 0.000000 0.013800 0.999900
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 0.995200 -0.098100
+vn 0.975000 0.189300 0.116400
+vn 0.093600 0.995600 0.000000
+vn 0.000000 0.995200 0.098100
+vn -0.093600 0.995600 -0.000000
+vn -0.000000 0.962500 0.271400
+vn -0.975000 0.189300 0.116400
+vn -0.000000 0.259000 0.965900
+vn 0.000000 -0.015200 -0.999900
+vn -0.000000 0.015200 0.999900
+g Toilet_open_Cube.002_White
+s off
+f 5/1/1 6/2/1 7/3/1 8/4/1
+f 1/5/2 5/6/2 8/7/2 2/8/2
+f 2/9/3 8/10/3 7/11/3 3/12/3
+f 3/8/4 7/7/4 6/6/4 4/5/4
+f 5/10/5 1/9/5 4/12/5 6/11/5
+f 9/13/6 10/14/6 11/15/6 12/16/6
+f 10/17/3 13/18/3 14/19/3 11/20/3
+f 13/21/7 15/22/7 16/23/7 14/24/7
+f 15/18/5 9/17/5 12/20/5 16/19/5
+f 12/25/8 11/26/8 14/27/8 16/28/8
+f 15/29/9 13/30/9 10/31/9 9/32/9
+f 19/33/1 20/34/1 18/35/1 17/36/1
+f 26/37/3 21/38/3 24/39/3 27/40/3
+f 31/41/5 22/38/5 23/39/5 32/42/5
+f 28/36/10 27/35/10 24/34/10 23/33/10
+f 21/34/11 22/33/11 50/43/11 49/44/11
+f 25/45/2 26/46/2 27/47/2 28/48/2
+f 21/38/3 29/41/3 30/42/3 24/39/3
+f 29/46/4 31/45/4 32/48/4 30/47/4
+f 22/38/5 25/37/5 28/40/5 23/39/5
+f 23/33/10 24/34/10 30/49/10 32/50/10
+f 25/37/5 22/38/5 19/51/5 17/52/5
+f 33/53/6 34/54/6 35/55/6 36/56/6
+f 34/17/3 37/18/3 38/19/3 35/20/3
+f 37/57/7 39/58/7 40/59/7 38/60/7
+f 39/18/5 33/17/5 36/20/5 40/19/5
+f 36/61/8 35/62/8 38/63/8 40/64/8
+f 39/65/9 37/66/9 34/67/9 33/68/9
+f 22/45/4 21/46/4 20/69/4 19/70/4
+f 21/38/3 26/37/3 18/52/3 20/51/3
+f 26/46/2 25/45/2 17/70/2 18/69/2
+f 52/71/12 49/72/12 53/73/12 56/74/12
+f 29/49/13 21/34/13 49/44/13 52/75/13
+f 31/50/14 29/49/14 52/75/14 51/76/14
+f 22/33/15 31/50/15 51/76/15 50/43/15
+f 55/77/16 56/78/16 53/44/16 54/43/16
+f 50/72/17 51/71/17 55/74/17 54/73/17
+f 51/79/18 52/80/18 56/81/18 55/82/18
+f 49/80/4 50/79/4 54/83/4 53/84/4
+g Toilet_open_Cube.002_Brown
+f 42/85/3 45/86/3 46/87/3 43/88/3
+f 45/89/7 47/90/7 48/91/7 46/92/7
+f 47/93/5 41/94/5 44/95/5 48/96/5
+f 44/97/19 43/98/19 46/99/19 48/100/19
+f 47/101/20 45/102/20 42/103/20 41/104/20
diff --git a/mods/ma_pops_furniture/models/FM_tv.obj b/mods/ma_pops_furniture/models/FM_tv.obj
new file mode 100644
index 00000000..6947528a
--- /dev/null
+++ b/mods/ma_pops_furniture/models/FM_tv.obj
@@ -0,0 +1,137 @@
+# Blender v2.78 (sub 0) OBJ File: ''
+# www.blender.org
+mtllib FM_tv.mtl
+o Cube_Cube.001_Body_Cube_Cube.001_Body_body
+v -0.450000 0.400000 0.200000
+v -0.450000 0.400000 -0.500000
+v -0.450000 -0.500000 -0.500000
+v -0.450000 -0.500000 0.200000
+v 0.450000 0.400000 -0.500000
+v 0.450000 -0.500000 -0.500000
+v 0.360000 -0.410000 -0.500000
+v 0.360000 0.310000 -0.500000
+v 0.450000 0.400000 0.200000
+v 0.450000 -0.500000 0.200000
+v -0.300000 0.220000 0.200000
+v 0.300000 0.220000 0.200000
+v -0.300000 0.220000 0.458106
+v 0.300000 0.220000 0.458106
+v 0.300000 -0.320000 0.200000
+v -0.300000 -0.320000 0.200000
+v -0.300000 -0.320000 0.458106
+v 0.300000 -0.320000 0.458106
+v -0.360000 -0.410000 -0.500000
+v -0.360000 0.310000 -0.500000
+v -0.360000 0.310000 -0.457433
+v -0.360000 -0.410000 -0.457433
+v 0.360000 -0.410000 -0.457433
+v 0.360000 0.310000 -0.457433
+vt 0.9999 0.0001
+vt 0.9999 0.7777
+vt 0.0001 0.7777
+vt 0.0001 0.0001
+vt 0.9999 0.0001
+vt 0.9999 0.9999
+vt 0.8999 0.8999
+vt 0.8999 0.1001
+vt 0.9999 0.7777
+vt 0.0001 0.7777
+vt 0.0001 0.0001
+vt 0.9999 0.9999
+vt 0.0001 0.9999
+vt 0.1667 0.7999
+vt 0.8333 0.7999
+vt 0.0001 0.7777
+vt 0.0001 0.0001
+vt 0.9999 0.0001
+vt 0.9999 0.7777
+vt 0.9999 0.0001
+vt 0.9999 0.7777
+vt 0.0001 0.7777
+vt 0.0001 0.0001
+vt 0.9999 0.4302
+vt 0.0001 0.4302
+vt 0.0001 0.0001
+vt 0.9999 0.0001
+vt 0.9999 0.0001
+vt 0.8333 0.2001
+vt 0.1667 0.2001
+vt 0.9999 0.8999
+vt 0.0001 0.8999
+vt 0.0001 0.0001
+vt 0.9999 0.0001
+vt 0.9999 0.4780
+vt 0.0001 0.4780
+vt 0.9999 0.0001
+vt 0.0001 0.0001
+vt 0.9999 0.0001
+vt 0.9999 0.4302
+vt 0.0001 0.4302
+vt 0.0001 0.0001
+vt 0.9999 0.0001
+vt 0.9999 0.4780
+vt 0.0001 0.4780
+vt 0.0000 0.0000
+vt 1.0000 0.0000
+vt 1.0000 0.0591
+vt 0.0000 0.0591
+vt 0.0001 0.9999
+vt 0.1001 0.8999
+vt 0.0001 0.0001
+vt 0.1001 0.1001
+vt 1.0000 0.0591
+vt 0.0000 0.0591
+vt 0.0000 0.0000
+vt 1.0000 0.0000
+vt 0.0000 0.0000
+vt 1.0000 0.0000
+vt 1.0000 0.0591
+vt 0.0000 0.0591
+vt 1.0000 0.0591
+vt 0.0000 0.0591
+vt 0.0000 0.0000
+vt 1.0000 0.0000
+vn -1.0000 0.0000 0.0000
+vn 0.0000 0.0000 -1.0000
+vn 1.0000 0.0000 0.0000
+vn 0.0000 -0.0000 1.0000
+vn 0.0000 -1.0000 -0.0000
+vn 0.0000 1.0000 0.0000
+g Cube_Cube.001_Body_Cube_Cube.001_Body_body_Cube_Cube.001_Body_Cube_Cube.001_Body_body_body
+usemtl body
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 5/5/2 6/6/2 7/7/2 8/8/2
+f 5/5/3 9/9/3 10/10/3 6/11/3
+f 9/12/4 1/13/4 11/14/4 12/15/4
+f 4/16/5 3/17/5 6/18/5 10/19/5
+f 9/20/6 5/21/6 2/22/6 1/23/6
+f 12/24/6 11/25/6 13/26/6 14/27/6
+f 10/28/4 9/12/4 12/15/4 15/29/4
+f 1/13/4 4/4/4 16/30/4 11/14/4
+f 4/4/4 10/28/4 15/29/4 16/30/4
+f 14/31/4 13/32/4 17/33/4 18/34/4
+f 11/35/1 16/36/1 17/33/1 13/37/1
+f 16/38/5 15/39/5 18/40/5 17/41/5
+f 15/42/3 12/43/3 14/44/3 18/45/3
+f 19/46/3 20/47/3 21/48/3 22/49/3
+f 6/6/2 3/50/2 19/51/2 7/7/2
+f 2/52/2 5/5/2 8/8/2 20/53/2
+f 3/50/2 2/52/2 20/53/2 19/51/2
+f 7/54/6 19/55/6 22/56/6 23/57/6
+f 20/58/5 8/59/5 24/60/5 21/61/5
+f 8/62/1 7/63/1 23/64/1 24/65/1
+o Cube_Cube.001_Screen_Cube_Cube.001_Screen_screen.001
+v -0.360000 0.310000 -0.457433
+v 0.360000 0.310000 -0.457433
+v 0.360000 -0.410000 -0.457433
+v -0.360000 -0.410000 -0.457433
+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
+g Cube_Cube.001_Screen_Cube_Cube.001_Screen_screen.001_Cube_Cube.001_Screen_Cube_Cube.001_Screen_screen.001_screen.001
+usemtl screen.001
+s 1
+f 25/66/7 26/67/7 27/68/7 28/69/7
diff --git a/mods/ma_pops_furniture/models/lever1.obj b/mods/ma_pops_furniture/models/lever1.obj
new file mode 100644
index 00000000..b88494fb
--- /dev/null
+++ b/mods/ma_pops_furniture/models/lever1.obj
@@ -0,0 +1,89 @@
+# Blender v2.79 (sub 0) OBJ File: ''
+# www.blender.org
+mtllib lever1.mtl
+o Cube
+v -0.171906 -0.523666 0.171906
+v -0.171906 -0.523666 -0.171906
+v 0.171906 -0.523666 -0.171906
+v 0.171906 -0.523666 0.171906
+v -0.171906 -0.386141 0.171906
+v -0.171906 -0.386141 -0.171906
+v 0.171906 -0.386141 -0.171906
+v 0.171906 -0.386141 0.171906
+v -0.068762 -0.400288 0.079551
+v -0.068762 -0.461698 -0.043501
+v 0.068762 -0.461698 -0.043501
+v 0.068762 -0.400288 0.079551
+v -0.068762 0.030396 -0.135383
+v -0.068762 -0.031014 -0.258436
+v 0.068762 -0.031014 -0.258436
+v 0.068762 0.030396 -0.135383
+vt 0.999929 0.500000
+vt 0.000071 0.500000
+vt 0.000071 0.000071
+vt 0.999929 0.000071
+vt 0.999929 0.500000
+vt 0.000071 0.500000
+vt 0.000071 0.000071
+vt 0.999929 0.000071
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.000000 0.187500
+vt 0.000071 0.000071
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.000000 0.187500
+vt 0.000000 0.187500
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.000000 0.187500
+vt 0.000071 0.000071
+vt 1.000000 1.000000
+vt -0.000000 1.000000
+vt 0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 0.500000
+vt 0.000000 1.000000
+vt -0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 1.000000
+vt 0.000035 0.999965
+vt -0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 1.000000
+vt -0.000000 0.500000
+vt 0.000000 1.000000
+vt 0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 1.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vn 0.0000 -1.0000 -0.0000
+vn 0.0000 1.0000 -0.0000
+vn -1.0000 0.0000 -0.0000
+vn 0.0000 -0.0000 -1.0000
+vn 1.0000 -0.0000 0.0000
+vn -0.0000 0.0000 1.0000
+vn 0.0000 0.8948 -0.4465
+vn 0.0000 -0.4465 -0.8948
+vn -0.0000 0.4465 0.8948
+vn 0.0000 -0.8948 0.4465
+usemtl Material
+s off
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 5/5/2 8/6/2 7/7/2 6/8/2
+f 1/9/3 5/10/3 6/11/3 2/12/3
+f 2/13/4 6/14/4 7/15/4 3/3/4
+f 3/16/5 7/7/5 8/17/5 4/18/5
+f 5/19/6 1/20/6 4/21/6 8/22/6
+f 13/23/7 16/24/7 15/25/7 14/26/7
+f 9/27/3 13/23/3 14/28/3 10/29/3
+f 10/30/8 14/31/8 15/32/8 11/33/8
+f 11/34/5 15/35/5 16/24/5 12/36/5
+f 13/37/9 9/38/9 12/39/9 16/40/9
+usemtl Material_NONE
+f 9/41/10 10/42/10 11/43/10 12/44/10
diff --git a/mods/ma_pops_furniture/models/lever2.obj b/mods/ma_pops_furniture/models/lever2.obj
new file mode 100644
index 00000000..f8734302
--- /dev/null
+++ b/mods/ma_pops_furniture/models/lever2.obj
@@ -0,0 +1,89 @@
+# Blender v2.79 (sub 0) OBJ File: ''
+# www.blender.org
+mtllib lever2.mtl
+o Cube
+v 0.171906 -0.523666 -0.171906
+v 0.171906 -0.523666 0.171906
+v -0.171906 -0.523666 0.171906
+v -0.171906 -0.523666 -0.171906
+v 0.171906 -0.386141 -0.171906
+v 0.171906 -0.386141 0.171906
+v -0.171906 -0.386141 0.171906
+v -0.171906 -0.386141 -0.171906
+v 0.068762 -0.400288 -0.079551
+v 0.068762 -0.461698 0.043501
+v -0.068762 -0.461698 0.043501
+v -0.068762 -0.400288 -0.079551
+v 0.068762 0.030396 0.135383
+v 0.068762 -0.031014 0.258436
+v -0.068762 -0.031014 0.258436
+v -0.068762 0.030396 0.135383
+vt 0.999929 0.500000
+vt 0.000071 0.500000
+vt 0.000071 0.000071
+vt 0.999929 0.000071
+vt 0.999929 0.500000
+vt 0.000071 0.500000
+vt 0.000071 0.000071
+vt 0.999929 0.000071
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.000000 0.187500
+vt 0.000071 0.000071
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.000000 0.187500
+vt 0.000000 0.187500
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.999929 0.000071
+vt 1.000000 0.187500
+vt 0.000000 0.187500
+vt 0.000071 0.000071
+vt 1.000000 1.000000
+vt -0.000000 1.000000
+vt 0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 0.500000
+vt 0.000000 1.000000
+vt -0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 1.000000
+vt 0.000035 0.999965
+vt -0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 1.000000
+vt -0.000000 0.500000
+vt 0.000000 1.000000
+vt 0.000000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 1.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vn 0.0000 -1.0000 0.0000
+vn 0.0000 1.0000 0.0000
+vn 1.0000 0.0000 0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn 0.0000 0.0000 -1.0000
+vn -0.0000 0.8948 0.4465
+vn -0.0000 -0.4465 0.8948
+vn 0.0000 0.4465 -0.8948
+vn 0.0000 -0.8948 -0.4465
+usemtl Material
+s off
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 5/5/2 8/6/2 7/7/2 6/8/2
+f 1/9/3 5/10/3 6/11/3 2/12/3
+f 2/13/4 6/14/4 7/15/4 3/3/4
+f 3/16/5 7/7/5 8/17/5 4/18/5
+f 5/19/6 1/20/6 4/21/6 8/22/6
+f 13/23/7 16/24/7 15/25/7 14/26/7
+f 9/27/3 13/23/3 14/28/3 10/29/3
+f 10/30/8 14/31/8 15/32/8 11/33/8
+f 11/34/5 15/35/5 16/24/5 12/36/5
+f 13/37/9 9/38/9 12/39/9 16/40/9
+usemtl Material_NONE
+f 9/41/10 10/42/10 11/43/10 12/44/10
diff --git a/mods/ma_pops_furniture/models/mp_ceiling_fan.fbx b/mods/ma_pops_furniture/models/mp_ceiling_fan.fbx
new file mode 100644
index 00000000..e033d96a
Binary files /dev/null and b/mods/ma_pops_furniture/models/mp_ceiling_fan.fbx differ
diff --git a/mods/ma_pops_furniture/models/mp_ceiling_fan_animated.3ds b/mods/ma_pops_furniture/models/mp_ceiling_fan_animated.3ds
new file mode 100644
index 00000000..40828792
Binary files /dev/null and b/mods/ma_pops_furniture/models/mp_ceiling_fan_animated.3ds differ
diff --git a/mods/ma_pops_furniture/models/mp_ceiling_fan_animated.3ds.b3d b/mods/ma_pops_furniture/models/mp_ceiling_fan_animated.3ds.b3d
new file mode 100644
index 00000000..23e32a54
Binary files /dev/null and b/mods/ma_pops_furniture/models/mp_ceiling_fan_animated.3ds.b3d differ
diff --git a/mods/ma_pops_furniture/models/stairs.mtl b/mods/ma_pops_furniture/models/stairs.mtl
new file mode 100644
index 00000000..379df14c
--- /dev/null
+++ b/mods/ma_pops_furniture/models/stairs.mtl
@@ -0,0 +1,12 @@
+# Blender MTL File: 'None'
+# Material Count: 1
+
+newmtl Material
+Ns 96.078431
+Ka 1.000000 1.000000 1.000000
+Kd 0.640000 0.640000 0.640000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.000000
+d 1.000000
+illum 2
diff --git a/mods/ma_pops_furniture/models/stairs.obj b/mods/ma_pops_furniture/models/stairs.obj
new file mode 100644
index 00000000..316e498e
--- /dev/null
+++ b/mods/ma_pops_furniture/models/stairs.obj
@@ -0,0 +1,86 @@
+# Blender v2.79 (sub 0) OBJ File: ''
+# www.blender.org
+mtllib stairs.mtl
+o Cube.003_Cube.004
+v -0.145056 -0.155475 -0.315349
+v -0.003360 -0.155475 -0.315288
+v -0.003419 -0.155475 -0.180255
+v -0.145114 -0.155475 -0.180316
+v -0.145056 -0.297056 -0.315349
+v -0.003360 -0.297056 -0.315288
+v -0.003419 -0.297056 -0.180255
+v -0.145114 -0.297056 -0.180316
+v -0.144081 -0.482542 -0.560711
+v -0.004195 -0.482544 -0.560650
+v -0.004623 0.545783 0.468518
+v -0.144509 0.545785 0.468457
+v -0.144121 -0.572912 -0.470414
+v -0.004235 -0.572914 -0.470353
+v -0.004664 0.455413 0.558814
+v -0.144549 0.455415 0.558754
+v -0.152012 0.474149 0.261187
+v 0.003098 0.474149 0.261254
+v 0.003040 0.474149 0.396288
+v -0.152071 0.474149 0.396220
+v -0.152012 0.332569 0.261187
+v 0.003098 0.332569 0.261254
+v 0.003039 0.332569 0.396288
+v -0.152071 0.332569 0.396221
+v -0.574097 -0.097822 -0.570914
+v 0.425903 -0.097822 -0.570481
+v 0.425694 -0.097822 -0.087989
+v -0.574306 -0.097822 -0.088422
+v -0.574097 -0.153023 -0.570914
+v 0.425903 -0.153023 -0.570481
+v 0.425694 -0.153023 -0.087989
+v -0.574306 -0.153023 -0.088422
+v -0.574319 0.518663 -0.057371
+v 0.425680 0.518663 -0.056939
+v 0.425472 0.518663 0.425554
+v -0.574528 0.518663 0.425121
+v -0.574319 0.463462 -0.057371
+v 0.425681 0.463462 -0.056938
+v 0.425472 0.463462 0.425554
+v -0.574528 0.463462 0.425121
+vn 0.0000 1.0000 0.0000
+vn 0.0000 -1.0000 0.0000
+vn 0.0004 0.0000 -1.0000
+vn 1.0000 0.0000 0.0004
+vn -0.0004 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0004
+vn 0.0003 0.7074 -0.7068
+vn -0.0003 -0.7074 0.7068
+vn 0.0003 -0.7068 -0.7074
+vn -0.0003 0.7068 0.7074
+usemtl Material
+s off
+f 1//1 4//1 3//1 2//1
+f 5//2 6//2 7//2 8//2
+f 1//3 2//3 6//3 5//3
+f 2//4 3//4 7//4 6//4
+f 3//5 4//5 8//5 7//5
+f 5//6 8//6 4//6 1//6
+f 9//7 12//7 11//7 10//7
+f 13//8 14//8 15//8 16//8
+f 9//9 10//9 14//9 13//9
+f 10//4 11//4 15//4 14//4
+f 11//10 12//10 16//10 15//10
+f 13//6 16//6 12//6 9//6
+f 17//1 20//1 19//1 18//1
+f 21//2 22//2 23//2 24//2
+f 17//3 18//3 22//3 21//3
+f 18//4 19//4 23//4 22//4
+f 19//5 20//5 24//5 23//5
+f 21//6 24//6 20//6 17//6
+f 25//1 28//1 27//1 26//1
+f 29//2 30//2 31//2 32//2
+f 25//3 26//3 30//3 29//3
+f 26//4 27//4 31//4 30//4
+f 27//5 28//5 32//5 31//5
+f 29//6 32//6 28//6 25//6
+f 33//1 36//1 35//1 34//1
+f 37//2 38//2 39//2 40//2
+f 33//3 34//3 38//3 37//3
+f 34//4 35//4 39//4 38//4
+f 35//5 36//5 40//5 39//5
+f 37//6 40//6 36//6 33//6
diff --git a/mods/ma_pops_furniture/outside.lua b/mods/ma_pops_furniture/outside.lua
new file mode 100644
index 00000000..398c5e0b
--- /dev/null
+++ b/mods/ma_pops_furniture/outside.lua
@@ -0,0 +1,339 @@
+function ma_pops_furniture.register_hedge(name, def)
+
+ -- register nodes
+ if minetest.get_modpath("default") then
+ def.sounds = def.sounds or default.node_sound_leaves_defaults()
+ end
+
+ minetest.register_node(name, {
+ description = def.description or "Hedge",
+ drawtype = "nodebox",
+ paramtype = "light",
+ tiles = {def.texture},
+ groups = def.groups or
+ {snappy = 3, flammable = 2, leaves = 1, hedge = 1},
+ waving = 1,
+ node_box = {
+ type = "connected",
+ fixed = {{-5/16, -0.5, -5/16, 5/16, 5/16, 5/16}},
+ connect_left = {{-0.5, -0.5, -5/16, -5/16, 5/16, 5/16}},
+ connect_right = {{5/16, -0.5, -5/16, 0.5, 5/16, 5/16}},
+ connect_front = {{-5/16, -0.5, -0.5, 5/16, 5/16, -5/16}},
+ connect_back = {{-5/16, -0.5, 5/16, 5/16, 5/16, 0.5}},
+ },
+ connects_to = {"group:fence", "group:wood", "group:tree", "group:hedge"},
+ light_source = def.light_source or 0,
+ sounds = def.sounds,
+ after_place_node = function(pos, placer, itemstack, pointed_thing)
+ local pos_under = {x = pos.x, y = pos.y - 1, z = pos.z}
+ local pos_above = {x = pos.x, y = pos.y + 1, z = pos.z}
+ local node_under = string.gsub(minetest.get_node(pos_under).name, "_full$", "")
+ local node_above = string.gsub(minetest.get_node(pos_above).name, "_full$", "")
+
+ if minetest.get_item_group(node_under, "hedge") == 1 then
+ minetest.set_node(pos_under, {name = node_under .. "_full"})
+ end
+ if minetest.get_item_group(node_above, "hedge") == 1 then
+ minetest.set_node(pos, {name = name .. "_full"})
+ end
+ end,
+ after_dig_node = function(pos, oldnode, oldmetadata, digger)
+ local pos_under = {x = pos.x, y = pos.y - 1, z = pos.z}
+ local node_under = string.gsub(minetest.get_node(pos_under).name, "_full$", "")
+ if minetest.get_item_group(node_under, "hedge") == 1 and
+ digger and digger:is_player() then
+ minetest.set_node(pos_under, {name = node_under})
+ end
+ end,
+ })
+
+ minetest.register_node(name .. "_full", {
+ description = def.description or "Hedge",
+ drawtype = "nodebox",
+ paramtype = "light",
+ tiles = {def.texture},
+ groups = def.groups or
+ {snappy = 3, flammable = 2, leaves = 1, hedge = 1,
+ not_in_creative_inventory = 1},
+ waving = 1,
+ node_box = {
+ type = "connected",
+ fixed = {{-5/16, -0.5, -5/16, 5/16, 0.5, 5/16}},
+ connect_left = {{-0.5, -0.5, -5/16, -5/16, 0.5, 5/16}},
+ connect_right = {{5/16, -0.5, -5/16, 0.5, 0.5, 5/16}},
+ connect_front = {{-5/16, -0.5, -0.5, 5/16, 0.5, -5/16}},
+ connect_back = {{-5/16, -0.5, 5/16, 5/16, 0.5, 0.5}},
+ },
+ connects_to = {"group:fence", "group:wood", "group:tree", "group:hedge"},
+ light_source = def.light_source or 0,
+ sounds = def.sounds,
+ drop = name,
+ after_dig_node = function(pos, oldnode, oldmetadata, digger)
+ local pos_under = {x = pos.x, y = pos.y - 1, z = pos.z}
+ local node_under = string.gsub(minetest.get_node(pos_under).name, "_full$", "")
+ if minetest.get_item_group(node_under, "hedge") == 1 and
+ digger and digger:is_player() then
+ minetest.set_node(pos_under, {name = node_under})
+ end
+ end,
+ })
+
+ -- register crafting recipe
+ minetest.register_craft({
+ output = name .. " 4",
+ recipe = {
+ {def.material, def.material, def.material},
+ {def.material, def.material, def.material},
+ }
+ })
+end
+
+
+-- register hedges if default mod found
+if minetest.get_modpath("default") then
+
+ ma_pops_furniture.register_hedge("ma_pops_furniture:apple_hedge", {
+ description = "Apple Hedge",
+ texture = "default_leaves.png",
+ material = "default:leaves",
+ })
+
+ ma_pops_furniture.register_hedge("ma_pops_furniture:jungle_hedge", {
+ description = "Jungle Hedge",
+ texture = "default_jungleleaves.png",
+ material = "default:jungleleaves",
+ })
+
+ ma_pops_furniture.register_hedge("ma_pops_furniture:pine_hedge", {
+ description = "Pine Hedge",
+ texture = "default_pine_needles.png",
+ material = "default:pine_needles",
+ })
+
+ ma_pops_furniture.register_hedge("ma_pops_furniture:acacia_hedge", {
+ description = "Acacia Hedge",
+ texture = "default_acacia_leaves.png",
+ material = "default:acacia_leaves",
+ })
+
+ ma_pops_furniture.register_hedge("ma_pops_furniture:aspen_hedge", {
+ description = "Aspen Hedge",
+ texture = "default_aspen_leaves.png",
+ material = "default:aspen_leaves",
+ })
+
+end
+
+
+-- alternative recipes using bush leaves
+ minetest.register_craft({
+ output = "hedges:apple_hedge 4",
+ recipe = {
+ {"default:bush_leaves", "default:bush_leaves", "default:bush_leaves"},
+ {"default:bush_leaves", "default:bush_leaves", "default:bush_leaves"},
+ }
+ })
+
+ minetest.register_craft({
+ output = "hedges:acacia_hedge 4",
+ recipe = {
+ {"default:acacia_bush_leaves", "default:acacia_bush_leaves", "default:acacia_bush_leaves"},
+ {"default:acacia_bush_leaves", "default:acacia_bush_leaves", "default:acacia_bush_leaves"},
+ }
+ })
+
+minetest.register_node('ma_pops_furniture:birdbath', {
+ description = 'Birdbath',
+ drawtype = 'mesh',
+ mesh = 'FM_birdbath.obj',
+ tiles = {{name='default_stone.png'},{name='default_water_source_animated.png', animation={type='vertical_frames', aspect_w=16, aspect_h=16, length=2.0}}},
+ groups = {cracky=2, oddly_breakable_by_hand=5, furniture=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.STONE_SOUNDS,
+})
+
+minetest.register_node('ma_pops_furniture:doorbell', {
+ description = 'Doorbell',
+ drawtype = 'nodebox',
+ tiles = {
+ "mp_db_top.png",
+ "mp_db_top.png",
+ "mp_db_right.png",
+ "mp_db_left.png",
+ "default_wood.png",
+ "mp_db_front.png"
+ },
+ groups = {cracky=2, oddly_breakable_by_hand=5, furniture=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.STONE_SOUNDS,
+ on_rightclick = function(pos, node, clicker, itemstack)
+ node.name = "ma_pops_furniture:doorbell_ring"
+ minetest.swap_node(pos, node)
+ -- one second ring.
+ minetest.get_node_timer(pos):start(1.0)
+ end,
+ sounds = moditems.STONE_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.125, -0.375, 0.4375, 0.125, -0.125, 0.5},
+ {-0.0625, -0.3125, 0.375, 0.0625, -0.1875, 0.4375},
+ },
+ }
+})
+
+minetest.register_node('ma_pops_furniture:doorbell_ring', {
+ description = 'Doorbell (ring)',
+ drawtype = 'nodebox',
+ tiles = {
+ "mp_db_top.png",
+ "mp_db_top.png",
+ "mp_db_right.png",
+ "mp_db_left.png",
+ "default_wood.png",
+ "mp_db_front.png"
+ },
+ groups = {cracky=2, oddly_breakable_by_hand=5, furniture=1, not_in_creative_inventory=1},
+ drop = 'ma_pops_furniture:doorbell',
+ on_timer = function(pos,elapsed)
+ local node = minetest.get_node(pos)
+ node.name = "ma_pops_furniture:doorbell"
+ minetest.swap_node(pos, node)
+ end,
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.STONE_SOUNDS,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.125, -0.375, 0.4375, 0.125, -0.125, 0.5},
+ {-0.0625, -0.3125, 0.375, 0.0625, -0.1875, 0.4375},
+ },
+ }
+})
+
+
+minetest.register_node('ma_pops_furniture:stone_path_1', {
+ description = 'Stone Path',
+ drawtype = 'mesh',
+ mesh = 'FM_stone_path_1.obj',
+ tiles = {'default_stone.png'},
+ groups = {cracky=2, oddly_breakable_by_hand=5, furniture=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.STONE_SOUNDS,
+ selection_box = {
+ type = 'fixed',
+ fixed = {-.5, -.5, -.5, .5, -.4, .5},
+ },
+ collision_box = {
+ type = 'fixed',
+ fixed = {-.5, -.5, -.5, .5, -.4, .5},
+ },
+ on_place = function(itemstack, placer, pointed_thing)
+ local stack = ItemStack("ma_pops_furniture:stone_path_" .. math.random(1,4))
+ local ret = minetest.item_place(stack, placer, pointed_thing)
+ return ItemStack("ma_pops_furniture:stone_path_1 " ..
+ itemstack:get_count() - (1 - ret:get_count()))
+ end,
+})
+
+for i = 2, 4 do
+minetest.register_node('ma_pops_furniture:stone_path_'..i, {
+ description = 'Stone Path',
+ drawtype = 'mesh',
+ mesh = 'FM_stone_path_'..i..'.obj',
+ tiles = {'default_stone.png'},
+ groups = {cracky=2, oddly_breakable_by_hand=5, furniture=1, not_in_creative_inventory=1},
+ paramtype = 'light',
+ paramtype2 = 'facedir',
+ sounds = moditems.STONE_SOUNDS,
+ drop = 'ma_pops_furniture:stone_path_1',
+ selection_box = {
+ type = 'fixed',
+ fixed = {-.5, -.5, -.5, .5, -.4, .5},
+ },
+ collision_box = {
+ type = 'fixed',
+ fixed = {-.5, -.5, -.5, .5, -.4, .5},
+ },
+})
+end
+
+minetest.register_node("ma_pops_furniture:outdoor_lamp", {
+ description = "Outdoor Lamp",
+ tiles = {
+ "default_stone.png",
+ "default_stone.png^mp_light_off.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png"
+},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sounds = moditems.STONE_SOUNDS,
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:outdoor_lamp_on"
+ minetest.set_node(pos, node)
+ end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.125, 0.25, -0.125, 0.125, 0.5, 0.125},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:outdoor_lamp_on", {
+ description = "Outdoor Lamp On",
+ tiles = {
+ "default_stone.png",
+ "default_stone.png^mp_light_on.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png",
+ "default_stone.png"
+},
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ light_source = 14,
+ drop = 'ma_pops_furniture:outdoor_lamp',
+ sounds = moditems.STONE_SOUNDS,
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ node.name = "ma_pops_furniture:outdoor_lamp"
+minetest.set_node(pos, node)
+end,
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, not_in_creative_inventory = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.125, 0.25, -0.125, 0.125, 0.5, 0.125},
+ },
+ }
+})
+
+minetest.register_node("ma_pops_furniture:trampoline", {
+ description = "Trampoline",
+ tiles = {"mp_trampoline_top.png", "default_coal_block.png", "mp_trampoline_side.png"},
+ drawtype="nodebox",
+ paramtype2="facedir",
+ paramtype="light",
+ groups = {cracky=3, oddly_breakable_by_hand=1, fall_damage_add_percent=-80, bouncy=90},
+ sounds = {wood = {name="xdecor_bouncy", gain=0.8}},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.3125, -0.5, 0.5, 0, 0.5}, -- NodeBox1
+ {0.1875, -0.5, 0.1875, 0.5, -0.3125, 0.5}, -- NodeBox2
+ {-0.5, -0.5, 0.1875, -0.1875, -0.3125, 0.5}, -- NodeBox3
+ {-0.5, -0.5, -0.5, -0.1875, -0.3125, -0.1875}, -- NodeBox4
+ {0.1875, -0.5, -0.5, 0.5, -0.3125, -0.1875}, -- NodeBox5
+ },
+ }
+})
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/oven.lua b/mods/ma_pops_furniture/oven.lua
new file mode 100644
index 00000000..f8d96b52
--- /dev/null
+++ b/mods/ma_pops_furniture/oven.lua
@@ -0,0 +1,163 @@
+local oven_fs = "size[8,7]"
+ .."image[3.5,1.5;1,1;default_furnace_fire_bg.png]"
+ .."list[current_player;main;0,3;8,4;]"
+ .."list[context;input;2,1.5;1,1;]"
+ .."list[context;output;5,1.5;1,1;]"
+ .."label[3,0.5;Oven]"
+ .."label[1.5,1;Uncooked Food]"
+ .."label[4.5,1;Cooked Food]"
+ -- possibly add "fire" image?
+
+local function get_active_oven_fs(item_percent)
+ return "size[8,7]"
+ .."image[3.5,1.5;1,1;default_furnace_fire_bg.png^[lowpart:"
+ ..(item_percent)..":default_furnace_fire_fg.png]"
+ .."list[current_player;main;0,3;8,4;]"
+ .."list[context;input;2,1.5;1,1;]"
+ .."list[context;output;5,1.5;1,1;]"
+ .."label[3,0.5;Oven]"
+ .."label[1.5,1;Uncooked Food]"
+ .."label[4.5,1;Cooked Food]"
+ -- possibly add "fire" image?
+end
+
+--x,y;w,h
+
+-- Adding recipe API so we don't end up hardcoding items
+ma_pops_furniture.oven = {}
+local oven = ma_pops_furniture.oven
+oven.recipes = {}
+function oven.register_recipe(input, output) oven.recipes[input] = output end
+
+local function update_formspec(progress, goal, meta)
+ local formspec
+
+ if progress > 0 and progress <= goal then
+ local item_percent = math.floor(progress / goal * 100)
+ formspec = get_active_oven_fs(item_percent)
+ else
+ formspec = oven_fs
+ end
+
+ meta:set_string("formspec", formspec)
+end
+
+local function recalculate(pos)
+ local meta, timer = minetest.get_meta(pos), minetest.get_node_timer(pos)
+ local inv = meta:get_inventory()
+ local stack = inv:get_stack("input", 1)
+
+ local k = oven.recipes[stack:get_name()]
+ if not k then return end
+
+ timer:stop()
+ update_formspec(0, 3, meta)
+ timer:start(1)
+end
+
+local function do_cook_single(pos)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ local food_uncooked = inv:get_stack("input", 1)
+ food_uncooked:set_count(1)
+
+ --If the uncooked food wasn't removed mid-cooking, then cook it.
+ if not oven.recipes[food_uncooked:get_name()] then
+ minetest.chat_send_all("Oven cooked nothing because there was nothing to cook.")
+ minetest.get_node_timer(pos):stop()
+ update_formspec(0, 3, meta)
+ else
+ inv:remove_item("input", food_uncooked) -- Clear the slot
+ local food_cooked = oven.recipes[food_uncooked:get_name()] -- Get the cooked food
+ inv:add_item("output", food_cooked) -- Put the cooked food in the slot
+ end
+end
+
+minetest.register_node("ma_pops_furniture:oven", {
+ description = "Oven",
+ tiles = {
+ "mp_oven_top.png",
+ "mp_oven_bottom.png",
+ "mp_oven_right.png",
+ "mp_oven_left.png",
+ "mp_oven_back.png",
+ "mp_oven_front.png"
+ },
+ paramtype2 = "facedir",
+ groups = {cracky = 2, tubedevice = 1, tubedevice_receiver = 1},
+ legacy_facedir_simple = true,
+ is_ground_content = false,
+ sounds = moditems.STONE_SOUNDS,
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.3125, -0.5, 0.5, 0.5, 0.5},
+ {-0.5, -0.5, -0.375, 0.5, 0.3125, 0.5},
+ {-0.4375, -0.4375, -0.4375, 0.4375, 0.25, -0.375},
+ {-0.375, 0.125, -0.5, 0.375, 0.1875, -0.375},
+ },
+ },
+ can_dig = function(pos, player)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ return inv:is_empty("input") and inv:is_empty("output")
+ end,
+
+ on_timer = function(pos, elapsed)
+ local meta = minetest.get_meta(pos)
+ local stack = meta:get_inventory():get_stack("input", 1)
+ local cooking_time = meta:get_int("cooking_time") or 0
+ cooking_time = cooking_time + 1
+
+ if cooking_time % 3 == 0 then
+ do_cook_single(pos)
+ end
+
+ update_formspec(cooking_time % 3, 3, meta)
+ meta:set_int("cooking_time", cooking_time)
+
+ --Keep cooking until there is nothing left to cook.
+ if not stack:is_empty() then
+ return true
+ else
+ meta:set_int("cooking_time", 0)
+ update_formspec(0, 3, meta)
+ return false
+ end
+ end,
+
+ on_metadata_inventory_put = recalculate,
+ on_metadata_inventory_take = recalculate,
+
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", oven_fs)
+ local inv = meta:get_inventory()
+ inv:set_size("input", 1)
+ inv:set_size("output", 1)
+ end,
+
+ on_blast = function(pos)
+ local drops = {}
+ default.get_inventory_drops(pos, "input", drops)
+ default.get_inventory_drops(pos, "output", drops)
+ table.insert(drops, "ma_pops_furniture:oven")
+ minetest.remove_node(pos)
+ return drops
+ end,
+
+ allow_metadata_inventory_put = function(pos, list, index, stack, player)
+ return oven.recipes[stack:get_name()] and stack:get_count() or 0
+ end,
+})
+
+-- Recipe Registration
+oven.register_recipe("default:ice", "default:water_source")
+-- No milk bucket as this doesn't support substitutes for now
+oven.register_recipe("mobs_mc:chicken_raw", "test:chicken_cooked")
+--[[ We don't need to check mod existance when registering recipe
+Recipe won't even be executed if there is no raw chicken in input ]]--
+oven.register_recipe("mobs_mc:beef_raw", "test:beef_cooked")
+oven.register_recipe("farming:coffee_cup", "farming:coffee_cup_hot") -- What a crutch there was...
+-- Add needed recipes as you go, note that other mods can add more recipes too
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/sofa.lua b/mods/ma_pops_furniture/sofa.lua
new file mode 100644
index 00000000..4bf1deb6
--- /dev/null
+++ b/mods/ma_pops_furniture/sofa.lua
@@ -0,0 +1,242 @@
+ma_pops_furniture.default_hues = {
+ "white",
+ "grey",
+ "dark_grey",
+ "black",
+ "violet",
+ "blue",
+ "cyan",
+ "dark_green",
+ "green",
+ "yellow",
+ "orange",
+ "red",
+ "magenta"
+}
+
+local sofa_table = { --name, color, colorize(hex or color name:intensity(1-255))
+{'Black', 'black', 'black:225'},
+{'Blue', 'blue', 'blue:225'},
+{'Brown', 'brown', 'brown:225'},
+{'Cyan', 'cyan', 'cyan:200'},
+{'Dark Green', 'dark_green', 'green:225'},
+{'Dark Grey', 'dark_grey', 'black:200'},
+{'Green', 'green', '#32cd32:150'},
+{'Grey', 'grey', 'black:100'},
+{'Magenta', 'magenta', 'magenta:200'},
+{'Orange', 'orange', 'orange:225'},
+{'Pink', 'pink', 'pink:225'},
+{'Red', 'red', 'red:225'},
+{'Violet', 'violet', 'violet:225'},
+{'White', 'white', 'white:1'},
+{'Yellow', 'yellow', 'yellow:225'},
+}
+
+local function sofa_punch(sofa_type, pos, node, clicker)
+ local item = clicker:get_wielded_item():get_name()
+
+ -- Extract mod name and color
+ split = string.split(item, ":")
+ mod, color = split[1], split[2]
+
+ -- If dye, recolor sofa
+ if mod == 'dye' then
+ node.name = sofa_type..color
+ minetest.set_node(pos, node)
+ end
+
+ ma_pops_furniture.sit(pos, node, clicker)
+end
+
+for i in ipairs (sofa_table) do
+ local name = sofa_table[i][1]
+ local color = sofa_table[i][2]
+ local hex = sofa_table[i][3]
+
+minetest.register_node('ma_pops_furniture:sofa_'..color, {
+ description = name..' Sofa',
+ drawtype = 'mesh',
+ mesh = 'FM_sofa.obj',
+ tiles = {'wool_'..color..'.png'},
+ groups = {cracky=3, oddly_breakable_by_hand=2, flammable=1, furniture=1, fall_damage_add_percent=-80, bouncy=80},
+ --inventory_image = 'mp_sofa.png^[colorize:'..hex,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sounds = {wood = {name="furn_bouncy", gain=0.8}},
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ selection_box = {
+ type = 'fixed',
+ fixed = {
+
+ {-.5, -.5, -.5, .5, 0, .5}, --Right, Bottom, Back, Left, Top, Front
+ {-.5, 0, .5, .5, .5, .2},
+ {-.65, -.15, -.45, -.45, .3, .25}, --left
+ {.65, -.15, -.45, .45, .3, .25}, --right
+ },
+ },
+ collision_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5}, --base
+ {-.5, 0, .5, .5, .5, .2}, --back
+ {-.65, -.15, -.45, -.45, .3, .25}, --left
+ {.65, -.15, -.45, .45, .3, .25}, --right
+ },
+ },
+ on_punch = function(pos, node, clicker)
+ sofa_punch("ma_pops_furniture:sofa_", pos, node, clicker)
+ end
+})
+
+minetest.register_node('ma_pops_furniture:sofa_l_'..color, {
+ description = name..' Sofa',
+ drawtype = 'mesh',
+ mesh = 'FM_sofa_l.obj',
+ tiles = {'wool_'..color..'.png'},
+ groups = {cracky=3, oddly_breakable_by_hand=2, flammable=1, not_in_creative_inventory=1, fall_damage_add_percent=-80, bouncy=80},
+ drop = 'ma_pops_furniture:sofa_'..color,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ sounds = {
+ wood = {name="furn_bouncy", gain=0.8}
+ },
+ selection_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5},
+ {-.5, 0, .5, .5, .5, .2},
+ {.65, -.15, -.45, .45, .3, .25},
+ }
+ },
+ collision_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5},
+ {-.5, 0, .5, .5, .5, .2},
+ {.65, -.15, -.45, .45, .3, .25},
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ sofa_punch("ma_pops_furniture:sofa_l_", pos, node, clicker)
+ end
+})
+
+minetest.register_node('ma_pops_furniture:sofa_m_'..color, {
+ description = name..' Sofa',
+ drawtype = 'mesh',
+ mesh = 'FM_sofa_m.obj',
+ tiles = {'wool_'..color..'.png'},
+ groups = {cracky=3, oddly_breakable_by_hand=2, flammable=1, not_in_creative_inventory=1, fall_damage_add_percent=-80, bouncy=80},
+ drop = 'ma_pops_furniture:sofa_'..color,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ sounds = {wood = {name="furn_bouncy", gain=0.8}},
+ selection_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5},
+ {-.5, 0, .5, .5, .5, .2},
+ }
+ },
+ collision_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5},
+ {-.5, 0, .5, .5, .5, .2},
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ sofa_punch("ma_pops_furniture:sofa_m_", pos, node, clicker)
+ end
+})
+
+minetest.register_node('ma_pops_furniture:sofa_r_'..color, {
+ description = name..' Sofa',
+ drawtype = 'mesh',
+ mesh = 'FM_sofa_r.obj',
+ tiles = {'wool_'..color..'.png'},
+ groups = {cracky=3, oddly_breakable_by_hand=2, flammable=1, not_in_creative_inventory=1, fall_damage_add_percent=-80, bouncy=80},
+ drop = 'ma_pops_furniture:sofa_'..color,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sounds = {wood = {name="furn_bouncy", gain=0.8}},
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ selection_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5},
+ {-.5, 0, .5, .5, .5, .2},
+ {-.65, -.15, -.45, -.45, .3, .25},
+ }
+ },
+ collision_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5},
+ {-.5, 0, .5, .5, .5, .2},
+ {-.65, -.15, -.45, -.45, .3, .25},
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ sofa_punch("ma_pops_furniture:sofa_r_", pos, node, clicker)
+ end
+})
+
+minetest.register_node('ma_pops_furniture:sofa_c_'..color, {
+ description = name..' Sofa',
+ drawtype = 'mesh',
+ mesh = 'FM_sofa_c.obj',
+ tiles = {'wool_'..color..'.png'},
+ groups = {cracky=3, oddly_breakable_by_hand=2, flammable=1, not_in_creative_inventory=1, furniture=1, fall_damage_add_percent=-80, bouncy=80},
+ drop = 'ma_pops_furniture:sofa_'..color,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sounds = {wood = {name="furn_bouncy", gain=0.8}},
+ can_dig = ma_pops_furniture.sit_dig,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ pos.y = pos.y + 0 -- Sitting position
+ ma_pops_furniture.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ selection_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5}, --bottom
+ {-.5, 0, .5, .5, .5, .2}, --back
+ {.2, 0, -.5, .5, .5, .2}, --side
+ }
+ },
+ collision_box = {
+ type = 'fixed',
+ fixed = {
+ {-.5, -.5, -.5, .5, 0, .5},
+ {-.5, 0, .5, .5, .5, .2},
+ }
+ },
+ on_punch = function(pos, node, clicker)
+ sofa_punch("ma_pops_furniture:sofa_c_", pos, node, clicker)
+ end
+})
+end
diff --git a/mods/ma_pops_furniture/sounds/275072__kwahmah-02__doorbell-a.ogg b/mods/ma_pops_furniture/sounds/275072__kwahmah-02__doorbell-a.ogg
new file mode 100644
index 00000000..47d98bc1
Binary files /dev/null and b/mods/ma_pops_furniture/sounds/275072__kwahmah-02__doorbell-a.ogg differ
diff --git a/mods/ma_pops_furniture/sounds/mp_blast.ogg b/mods/ma_pops_furniture/sounds/mp_blast.ogg
new file mode 100644
index 00000000..3ae962c3
Binary files /dev/null and b/mods/ma_pops_furniture/sounds/mp_blast.ogg differ
diff --git a/mods/ma_pops_furniture/sounds/mp_glass.ogg b/mods/ma_pops_furniture/sounds/mp_glass.ogg
new file mode 100644
index 00000000..ea5b0913
Binary files /dev/null and b/mods/ma_pops_furniture/sounds/mp_glass.ogg differ
diff --git a/mods/ma_pops_furniture/sounds/mp_radio_static.ogg b/mods/ma_pops_furniture/sounds/mp_radio_static.ogg
new file mode 100644
index 00000000..23253809
Binary files /dev/null and b/mods/ma_pops_furniture/sounds/mp_radio_static.ogg differ
diff --git a/mods/ma_pops_furniture/sounds/mp_rainbow.ogg b/mods/ma_pops_furniture/sounds/mp_rainbow.ogg
new file mode 100644
index 00000000..60ce2494
Binary files /dev/null and b/mods/ma_pops_furniture/sounds/mp_rainbow.ogg differ
diff --git a/mods/ma_pops_furniture/sounds/mp_smoke_detector.ogg b/mods/ma_pops_furniture/sounds/mp_smoke_detector.ogg
new file mode 100644
index 00000000..0fb73e6f
Binary files /dev/null and b/mods/ma_pops_furniture/sounds/mp_smoke_detector.ogg differ
diff --git a/mods/ma_pops_furniture/sounds/mp_static.ogg b/mods/ma_pops_furniture/sounds/mp_static.ogg
new file mode 100644
index 00000000..85ee9533
Binary files /dev/null and b/mods/ma_pops_furniture/sounds/mp_static.ogg differ
diff --git a/mods/ma_pops_furniture/stereo.lua b/mods/ma_pops_furniture/stereo.lua
new file mode 100644
index 00000000..2db7bbd9
--- /dev/null
+++ b/mods/ma_pops_furniture/stereo.lua
@@ -0,0 +1,38 @@
+local songs = { "static"
+}
+
+minetest.register_node("ma_pops_furniture:stereo", {
+ description = "Stereo",
+ tiles = {
+ "mp_radio_top.png",
+ "mp_radio_bottom.png",
+ "mp_radio_right.png",
+ "mp_radio_left.png",
+ "mp_radio_back.png",
+ "mp_radio_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, furniture = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, 0.1875, 0.5, -0.125, 0.5}, -- NodeBox1
+ {-0.25, -0.5, 0.125, 0.25, -0.0625, 0.5}, -- NodeBox2
+ }
+ },
+ on_rightclick = function(pos, node, clicker, itemstack)
+ local meta = minetest.env:get_meta(pos)
+ if string.len(meta:get_string("hwnd")) > 0 then
+ minetest.sound_stop(meta:get_string("hwnd"))
+ meta:set_string("hwnd",nil)
+ else
+ meta:set_string("hwnd",minetest.sound_play("radio_" .. songs[math.random(1,#songs)], {gain = 0.5, max_hear_distance = 25}))
+ end
+ end,
+ on_destruct = function(pos)
+ local meta = minetest.env:get_meta(pos)
+ if string.len(meta:get_string("hwnd")) > 0 then minetest.sound_stop(meta:get_string("hwnd")) end
+ end,
+})
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/textures/mp_TV.png b/mods/ma_pops_furniture/textures/mp_TV.png
new file mode 100644
index 00000000..f648d02e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_TV.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ac_b.png b/mods/ma_pops_furniture/textures/mp_ac_b.png
new file mode 100644
index 00000000..e2e1b333
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ac_b.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ac_f.png b/mods/ma_pops_furniture/textures/mp_ac_f.png
new file mode 100644
index 00000000..d13adb2e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ac_f.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ac_s.png b/mods/ma_pops_furniture/textures/mp_ac_s.png
new file mode 100644
index 00000000..1919ce38
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ac_s.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ac_top.png b/mods/ma_pops_furniture/textures/mp_ac_top.png
new file mode 100644
index 00000000..babe656c
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ac_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_b.png b/mods/ma_pops_furniture/textures/mp_b.png
new file mode 100644
index 00000000..ef031a8f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_b.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_barrel.png b/mods/ma_pops_furniture/textures/mp_barrel.png
new file mode 100644
index 00000000..9e83d1ec
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_barrel.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_barrel_top.png b/mods/ma_pops_furniture/textures/mp_barrel_top.png
new file mode 100644
index 00000000..b40e6fb2
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_barrel_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_bathroom_tile.png b/mods/ma_pops_furniture/textures/mp_bathroom_tile.png
new file mode 100644
index 00000000..f2ebb6bc
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_bathroom_tile.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_blade.png b/mods/ma_pops_furniture/textures/mp_blade.png
new file mode 100644
index 00000000..3075d333
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_blade.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_blinds.png b/mods/ma_pops_furniture/textures/mp_blinds.png
new file mode 100644
index 00000000..e70f95c5
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_blinds.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_boygame.png b/mods/ma_pops_furniture/textures/mp_boygame.png
new file mode 100644
index 00000000..8eb3cfdb
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_boygame.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_boygame_back.png b/mods/ma_pops_furniture/textures/mp_boygame_back.png
new file mode 100644
index 00000000..eaed4747
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_boygame_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_boygame_front.png b/mods/ma_pops_furniture/textures/mp_boygame_front.png
new file mode 100644
index 00000000..4798d5ee
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_boygame_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_boygame_left.png b/mods/ma_pops_furniture/textures/mp_boygame_left.png
new file mode 100644
index 00000000..01030fd7
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_boygame_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_boygame_right.png b/mods/ma_pops_furniture/textures/mp_boygame_right.png
new file mode 100644
index 00000000..cdb4b4e6
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_boygame_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_boygame_top.png b/mods/ma_pops_furniture/textures/mp_boygame_top.png
new file mode 100644
index 00000000..83544998
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_boygame_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_breadmaker_back.png b/mods/ma_pops_furniture/textures/mp_breadmaker_back.png
new file mode 100644
index 00000000..b42a6456
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_breadmaker_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_breadmaker_side.png b/mods/ma_pops_furniture/textures/mp_breadmaker_side.png
new file mode 100644
index 00000000..e090babc
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_breadmaker_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_breadmaker_top.png b/mods/ma_pops_furniture/textures/mp_breadmaker_top.png
new file mode 100644
index 00000000..fe3ed766
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_breadmaker_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_breadmaker_top2.png b/mods/ma_pops_furniture/textures/mp_breadmaker_top2.png
new file mode 100644
index 00000000..90f64710
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_breadmaker_top2.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_breadslice.png b/mods/ma_pops_furniture/textures/mp_breadslice.png
new file mode 100644
index 00000000..2d04ab03
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_breadslice.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_camp_back.png b/mods/ma_pops_furniture/textures/mp_camp_back.png
new file mode 100644
index 00000000..c4fd041a
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_camp_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_camp_bottom.png b/mods/ma_pops_furniture/textures/mp_camp_bottom.png
new file mode 100644
index 00000000..583ed9f2
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_camp_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_camp_front.png b/mods/ma_pops_furniture/textures/mp_camp_front.png
new file mode 100644
index 00000000..ffe77e24
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_camp_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_camp_left.png b/mods/ma_pops_furniture/textures/mp_camp_left.png
new file mode 100644
index 00000000..c4fd041a
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_camp_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_camp_right.png b/mods/ma_pops_furniture/textures/mp_camp_right.png
new file mode 100644
index 00000000..c4fd041a
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_camp_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_camp_top.png b/mods/ma_pops_furniture/textures/mp_camp_top.png
new file mode 100644
index 00000000..1d32af19
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_camp_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cartridge.png b/mods/ma_pops_furniture/textures/mp_cartridge.png
new file mode 100644
index 00000000..294800dd
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cartridge.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cartridge_back.png b/mods/ma_pops_furniture/textures/mp_cartridge_back.png
new file mode 100644
index 00000000..69e929f0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cartridge_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cartridge_bottom.png b/mods/ma_pops_furniture/textures/mp_cartridge_bottom.png
new file mode 100644
index 00000000..8ce77714
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cartridge_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cartridge_front.png b/mods/ma_pops_furniture/textures/mp_cartridge_front.png
new file mode 100644
index 00000000..0e69bb9d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cartridge_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cb.png b/mods/ma_pops_furniture/textures/mp_cb.png
new file mode 100644
index 00000000..adc15a98
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cb.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ceiling_light_bottom.png b/mods/ma_pops_furniture/textures/mp_ceiling_light_bottom.png
new file mode 100644
index 00000000..b037fb00
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ceiling_light_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ceiling_light_side.png b/mods/ma_pops_furniture/textures/mp_ceiling_light_side.png
new file mode 100644
index 00000000..492a802d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ceiling_light_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cf.png b/mods/ma_pops_furniture/textures/mp_cf.png
new file mode 100644
index 00000000..62240454
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cf.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_chair_acacia_wood.png b/mods/ma_pops_furniture/textures/mp_chair_acacia_wood.png
new file mode 100644
index 00000000..3987e3f1
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_chair_acacia_wood.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_chair_aspen_wood.png b/mods/ma_pops_furniture/textures/mp_chair_aspen_wood.png
new file mode 100644
index 00000000..9bb71069
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_chair_aspen_wood.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_chair_junglewood.png b/mods/ma_pops_furniture/textures/mp_chair_junglewood.png
new file mode 100644
index 00000000..9ee3ae80
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_chair_junglewood.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_chair_pine_wood.png b/mods/ma_pops_furniture/textures/mp_chair_pine_wood.png
new file mode 100644
index 00000000..779c49bd
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_chair_pine_wood.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_chair_stone.png b/mods/ma_pops_furniture/textures/mp_chair_stone.png
new file mode 100644
index 00000000..1c085ec7
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_chair_stone.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_chair_wood.png b/mods/ma_pops_furniture/textures/mp_chair_wood.png
new file mode 100644
index 00000000..c8371a74
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_chair_wood.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_channel_blast.png b/mods/ma_pops_furniture/textures/mp_channel_blast.png
new file mode 100644
index 00000000..0361c106
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_channel_blast.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_channel_cube.png b/mods/ma_pops_furniture/textures/mp_channel_cube.png
new file mode 100644
index 00000000..eaf02a4b
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_channel_cube.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_channel_rainbow.png b/mods/ma_pops_furniture/textures/mp_channel_rainbow.png
new file mode 100644
index 00000000..2a5d59b1
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_channel_rainbow.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_channel_screen.png b/mods/ma_pops_furniture/textures/mp_channel_screen.png
new file mode 100644
index 00000000..904ba285
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_channel_screen.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_channel_static.png b/mods/ma_pops_furniture/textures/mp_channel_static.png
new file mode 100644
index 00000000..fc2bd6b0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_channel_static.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cof_back.png b/mods/ma_pops_furniture/textures/mp_cof_back.png
new file mode 100644
index 00000000..82b10902
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cof_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cof_bottom.png b/mods/ma_pops_furniture/textures/mp_cof_bottom.png
new file mode 100644
index 00000000..d7d6694d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cof_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cof_front.png b/mods/ma_pops_furniture/textures/mp_cof_front.png
new file mode 100644
index 00000000..c61d8a65
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cof_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cof_left.png b/mods/ma_pops_furniture/textures/mp_cof_left.png
new file mode 100644
index 00000000..f393481e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cof_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cof_right.png b/mods/ma_pops_furniture/textures/mp_cof_right.png
new file mode 100644
index 00000000..6fb4f61f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cof_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cof_top.png b/mods/ma_pops_furniture/textures/mp_cof_top.png
new file mode 100644
index 00000000..c975c999
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cof_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_corn_r_back.png b/mods/ma_pops_furniture/textures/mp_corn_r_back.png
new file mode 100644
index 00000000..e43af637
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_corn_r_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_corn_r_bottom.png b/mods/ma_pops_furniture/textures/mp_corn_r_bottom.png
new file mode 100644
index 00000000..57c13597
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_corn_r_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_corn_r_front.png b/mods/ma_pops_furniture/textures/mp_corn_r_front.png
new file mode 100644
index 00000000..7ddcf287
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_corn_r_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_corn_r_left.png b/mods/ma_pops_furniture/textures/mp_corn_r_left.png
new file mode 100644
index 00000000..7ddcf287
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_corn_r_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_corn_r_right.png b/mods/ma_pops_furniture/textures/mp_corn_r_right.png
new file mode 100644
index 00000000..7ddcf287
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_corn_r_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_corn_r_top.png b/mods/ma_pops_furniture/textures/mp_corn_r_top.png
new file mode 100644
index 00000000..f385e5e8
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_corn_r_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_curtainb.png b/mods/ma_pops_furniture/textures/mp_curtainb.png
new file mode 100644
index 00000000..aa96e095
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_curtainb.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_curtains.png b/mods/ma_pops_furniture/textures/mp_curtains.png
new file mode 100644
index 00000000..ed1991da
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_curtains.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_cutting_board.png b/mods/ma_pops_furniture/textures/mp_cutting_board.png
new file mode 100644
index 00000000..fb9946e4
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_cutting_board.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_db_bottom.png b/mods/ma_pops_furniture/textures/mp_db_bottom.png
new file mode 100644
index 00000000..d14e2a03
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_db_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_db_front.png b/mods/ma_pops_furniture/textures/mp_db_front.png
new file mode 100644
index 00000000..d52b19e5
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_db_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_db_left.png b/mods/ma_pops_furniture/textures/mp_db_left.png
new file mode 100644
index 00000000..f607f689
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_db_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_db_right.png b/mods/ma_pops_furniture/textures/mp_db_right.png
new file mode 100644
index 00000000..b1c2aac2
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_db_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_db_top.png b/mods/ma_pops_furniture/textures/mp_db_top.png
new file mode 100644
index 00000000..183e0b6e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_db_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dfridge_back.png b/mods/ma_pops_furniture/textures/mp_dfridge_back.png
new file mode 100644
index 00000000..71c41f00
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dfridge_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dfridge_bottom.png b/mods/ma_pops_furniture/textures/mp_dfridge_bottom.png
new file mode 100644
index 00000000..6737b515
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dfridge_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dfridge_front.png b/mods/ma_pops_furniture/textures/mp_dfridge_front.png
new file mode 100644
index 00000000..bc93d871
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dfridge_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dfridge_left.png b/mods/ma_pops_furniture/textures/mp_dfridge_left.png
new file mode 100644
index 00000000..e5b46f31
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dfridge_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dfridge_right.png b/mods/ma_pops_furniture/textures/mp_dfridge_right.png
new file mode 100644
index 00000000..ba46483a
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dfridge_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dfridge_top.png b/mods/ma_pops_furniture/textures/mp_dfridge_top.png
new file mode 100644
index 00000000..751b5c68
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dfridge_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dw_back.png b/mods/ma_pops_furniture/textures/mp_dw_back.png
new file mode 100644
index 00000000..3f9b1741
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dw_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dw_bottom.png b/mods/ma_pops_furniture/textures/mp_dw_bottom.png
new file mode 100644
index 00000000..cd294782
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dw_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dw_front.png b/mods/ma_pops_furniture/textures/mp_dw_front.png
new file mode 100644
index 00000000..f16cfc10
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dw_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dw_left.png b/mods/ma_pops_furniture/textures/mp_dw_left.png
new file mode 100644
index 00000000..70d9bb19
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dw_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dw_right.png b/mods/ma_pops_furniture/textures/mp_dw_right.png
new file mode 100644
index 00000000..c1373518
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dw_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_dw_top.png b/mods/ma_pops_furniture/textures/mp_dw_top.png
new file mode 100644
index 00000000..6737b515
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_dw_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_enc_back.png b/mods/ma_pops_furniture/textures/mp_enc_back.png
new file mode 100644
index 00000000..ca782aac
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_enc_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_enc_bottom.png b/mods/ma_pops_furniture/textures/mp_enc_bottom.png
new file mode 100644
index 00000000..56e65433
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_enc_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_enc_front.png b/mods/ma_pops_furniture/textures/mp_enc_front.png
new file mode 100644
index 00000000..1e2c28be
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_enc_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_enc_front2.png b/mods/ma_pops_furniture/textures/mp_enc_front2.png
new file mode 100644
index 00000000..94e23fa1
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_enc_front2.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_enc_left.png b/mods/ma_pops_furniture/textures/mp_enc_left.png
new file mode 100644
index 00000000..bbd69a29
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_enc_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_enc_right.png b/mods/ma_pops_furniture/textures/mp_enc_right.png
new file mode 100644
index 00000000..99edc7fa
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_enc_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_enc_top.png b/mods/ma_pops_furniture/textures/mp_enc_top.png
new file mode 100644
index 00000000..f385e5e8
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_enc_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_f.png b/mods/ma_pops_furniture/textures/mp_f.png
new file mode 100644
index 00000000..6c496013
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_f.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_fan_off.png b/mods/ma_pops_furniture/textures/mp_fan_off.png
new file mode 100644
index 00000000..d5d4a776
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_fan_off.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_fan_on.png b/mods/ma_pops_furniture/textures/mp_fan_on.png
new file mode 100644
index 00000000..d9d49881
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_fan_on.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_fireplace.png b/mods/ma_pops_furniture/textures/mp_fireplace.png
new file mode 100644
index 00000000..54c30cb3
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_fireplace.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_flat_tv.png b/mods/ma_pops_furniture/textures/mp_flat_tv.png
new file mode 100644
index 00000000..8cf8b2d6
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_flat_tv.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_fridge_back.png b/mods/ma_pops_furniture/textures/mp_fridge_back.png
new file mode 100644
index 00000000..7e62e21b
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_fridge_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_grif_sides.png b/mods/ma_pops_furniture/textures/mp_grif_sides.png
new file mode 100644
index 00000000..dc9fbff7
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_grif_sides.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_grif_top.png b/mods/ma_pops_furniture/textures/mp_grif_top.png
new file mode 100644
index 00000000..1097a346
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_grif_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_grills.png b/mods/ma_pops_furniture/textures/mp_grills.png
new file mode 100644
index 00000000..f75c7ef0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_grills.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_grillt.png b/mods/ma_pops_furniture/textures/mp_grillt.png
new file mode 100644
index 00000000..38ecaae0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_grillt.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_grillton.png b/mods/ma_pops_furniture/textures/mp_grillton.png
new file mode 100644
index 00000000..b6b71a8f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_grillton.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_hammer.png b/mods/ma_pops_furniture/textures/mp_hammer.png
new file mode 100644
index 00000000..2af679a0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_hammer.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_hw_back.png b/mods/ma_pops_furniture/textures/mp_hw_back.png
new file mode 100644
index 00000000..019a6690
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_hw_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_hw_bottom.png b/mods/ma_pops_furniture/textures/mp_hw_bottom.png
new file mode 100644
index 00000000..56a5accb
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_hw_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_hw_front.png b/mods/ma_pops_furniture/textures/mp_hw_front.png
new file mode 100644
index 00000000..019a6690
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_hw_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_hw_left.png b/mods/ma_pops_furniture/textures/mp_hw_left.png
new file mode 100644
index 00000000..8db11633
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_hw_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_hw_right.png b/mods/ma_pops_furniture/textures/mp_hw_right.png
new file mode 100644
index 00000000..9dd3a36c
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_hw_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_hw_top.png b/mods/ma_pops_furniture/textures/mp_hw_top.png
new file mode 100644
index 00000000..d05e4f46
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_hw_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_itembirdbath.png b/mods/ma_pops_furniture/textures/mp_itembirdbath.png
new file mode 100644
index 00000000..93af8da5
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_itembirdbath.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_kitchen_floor_tile.png b/mods/ma_pops_furniture/textures/mp_kitchen_floor_tile.png
new file mode 100644
index 00000000..ae101dc0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_kitchen_floor_tile.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_kitchen_tile.png b/mods/ma_pops_furniture/textures/mp_kitchen_tile.png
new file mode 100644
index 00000000..505a0fc1
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_kitchen_tile.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_knife.png b/mods/ma_pops_furniture/textures/mp_knife.png
new file mode 100644
index 00000000..8a495ae8
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_knife.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_knob_back.png b/mods/ma_pops_furniture/textures/mp_knob_back.png
new file mode 100644
index 00000000..0a07897c
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_knob_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_knob_bottom.png b/mods/ma_pops_furniture/textures/mp_knob_bottom.png
new file mode 100644
index 00000000..c0cc0a9e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_knob_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_knob_front.png b/mods/ma_pops_furniture/textures/mp_knob_front.png
new file mode 100644
index 00000000..0a07897c
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_knob_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_knob_left.png b/mods/ma_pops_furniture/textures/mp_knob_left.png
new file mode 100644
index 00000000..7cf78e6f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_knob_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_knob_right.png b/mods/ma_pops_furniture/textures/mp_knob_right.png
new file mode 100644
index 00000000..d97eab2e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_knob_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_knob_top.png b/mods/ma_pops_furniture/textures/mp_knob_top.png
new file mode 100644
index 00000000..a5d67fb3
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_knob_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_lb.png b/mods/ma_pops_furniture/textures/mp_lb.png
new file mode 100644
index 00000000..86587204
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_lb.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_lb_middle.png b/mods/ma_pops_furniture/textures/mp_lb_middle.png
new file mode 100644
index 00000000..7e1018e3
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_lb_middle.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_light_off.png b/mods/ma_pops_furniture/textures/mp_light_off.png
new file mode 100644
index 00000000..f4038c21
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_light_off.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_light_on.png b/mods/ma_pops_furniture/textures/mp_light_on.png
new file mode 100644
index 00000000..c65be586
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_light_on.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_light_wall.png b/mods/ma_pops_furniture/textures/mp_light_wall.png
new file mode 100644
index 00000000..ecaddd29
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_light_wall.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_lp_base.png b/mods/ma_pops_furniture/textures/mp_lp_base.png
new file mode 100644
index 00000000..6e40463b
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_lp_base.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_lp_top.png b/mods/ma_pops_furniture/textures/mp_lp_top.png
new file mode 100644
index 00000000..db61dd66
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_lp_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ls.png b/mods/ma_pops_furniture/textures/mp_ls.png
new file mode 100644
index 00000000..25428410
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ls.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ls_top.png b/mods/ma_pops_furniture/textures/mp_ls_top.png
new file mode 100644
index 00000000..1d99c291
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ls_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_lt.png b/mods/ma_pops_furniture/textures/mp_lt.png
new file mode 100644
index 00000000..c69c3ab5
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_lt.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mask.png b/mods/ma_pops_furniture/textures/mp_mask.png
new file mode 100644
index 00000000..ec472869
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mask.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_med_back.png b/mods/ma_pops_furniture/textures/mp_med_back.png
new file mode 100644
index 00000000..688d070d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_med_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_med_front.png b/mods/ma_pops_furniture/textures/mp_med_front.png
new file mode 100644
index 00000000..e677aa0b
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_med_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_microwave_GUI.png b/mods/ma_pops_furniture/textures/mp_microwave_GUI.png
new file mode 100644
index 00000000..7d1a7ca4
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_microwave_GUI.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_microwave_start.png b/mods/ma_pops_furniture/textures/mp_microwave_start.png
new file mode 100644
index 00000000..9a94e404
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_microwave_start.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_bottom.png b/mods/ma_pops_furniture/textures/mp_mirror_bottom.png
new file mode 100644
index 00000000..3cf396f8
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_front.png b/mods/ma_pops_furniture/textures/mp_mirror_front.png
new file mode 100644
index 00000000..42f46eca
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_left.png b/mods/ma_pops_furniture/textures/mp_mirror_left.png
new file mode 100644
index 00000000..26315d56
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_open_bottom.png b/mods/ma_pops_furniture/textures/mp_mirror_open_bottom.png
new file mode 100644
index 00000000..4c3f158a
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_open_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_open_front.png b/mods/ma_pops_furniture/textures/mp_mirror_open_front.png
new file mode 100644
index 00000000..b5287551
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_open_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_open_left.png b/mods/ma_pops_furniture/textures/mp_mirror_open_left.png
new file mode 100644
index 00000000..a12a7e5a
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_open_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_open_right.png b/mods/ma_pops_furniture/textures/mp_mirror_open_right.png
new file mode 100644
index 00000000..7e8faa9f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_open_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_open_top.png b/mods/ma_pops_furniture/textures/mp_mirror_open_top.png
new file mode 100644
index 00000000..ecf8123d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_open_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_right.png b/mods/ma_pops_furniture/textures/mp_mirror_right.png
new file mode 100644
index 00000000..f69431c0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mirror_top.png b/mods/ma_pops_furniture/textures/mp_mirror_top.png
new file mode 100644
index 00000000..3e72cb43
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mirror_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_back.png b/mods/ma_pops_furniture/textures/mp_mw_back.png
new file mode 100644
index 00000000..02087b19
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_bar.png b/mods/ma_pops_furniture/textures/mp_mw_bar.png
new file mode 100644
index 00000000..ba516e7e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_bar.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_bar_on.png b/mods/ma_pops_furniture/textures/mp_mw_bar_on.png
new file mode 100644
index 00000000..65e3ffd2
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_bar_on.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_bottom.png b/mods/ma_pops_furniture/textures/mp_mw_bottom.png
new file mode 100644
index 00000000..9f488292
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_front.png b/mods/ma_pops_furniture/textures/mp_mw_front.png
new file mode 100644
index 00000000..189d72eb
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_left.png b/mods/ma_pops_furniture/textures/mp_mw_left.png
new file mode 100644
index 00000000..e6928075
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_right.png b/mods/ma_pops_furniture/textures/mp_mw_right.png
new file mode 100644
index 00000000..40083f23
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_mw_top.png b/mods/ma_pops_furniture/textures/mp_mw_top.png
new file mode 100644
index 00000000..1a563857
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_mw_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_oven_GUI.png b/mods/ma_pops_furniture/textures/mp_oven_GUI.png
new file mode 100644
index 00000000..12e64cb9
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_oven_GUI.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_oven_back.png b/mods/ma_pops_furniture/textures/mp_oven_back.png
new file mode 100644
index 00000000..6737b515
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_oven_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_oven_bottom.png b/mods/ma_pops_furniture/textures/mp_oven_bottom.png
new file mode 100644
index 00000000..6737b515
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_oven_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_oven_front.png b/mods/ma_pops_furniture/textures/mp_oven_front.png
new file mode 100644
index 00000000..9f60361e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_oven_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_oven_left.png b/mods/ma_pops_furniture/textures/mp_oven_left.png
new file mode 100644
index 00000000..600fc6cf
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_oven_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_oven_right.png b/mods/ma_pops_furniture/textures/mp_oven_right.png
new file mode 100644
index 00000000..c4892953
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_oven_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_oven_top.png b/mods/ma_pops_furniture/textures/mp_oven_top.png
new file mode 100644
index 00000000..4e5c5d89
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_oven_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_r_cb.png b/mods/ma_pops_furniture/textures/mp_r_cb.png
new file mode 100644
index 00000000..b9e8a791
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_r_cb.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_r_cf.png b/mods/ma_pops_furniture/textures/mp_r_cf.png
new file mode 100644
index 00000000..4608de76
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_r_cf.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_radio_back.png b/mods/ma_pops_furniture/textures/mp_radio_back.png
new file mode 100644
index 00000000..1b66a17d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_radio_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_radio_bottom.png b/mods/ma_pops_furniture/textures/mp_radio_bottom.png
new file mode 100644
index 00000000..4009a538
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_radio_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_radio_front.png b/mods/ma_pops_furniture/textures/mp_radio_front.png
new file mode 100644
index 00000000..c0061b66
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_radio_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_radio_left.png b/mods/ma_pops_furniture/textures/mp_radio_left.png
new file mode 100644
index 00000000..b6b12a30
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_radio_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_radio_right.png b/mods/ma_pops_furniture/textures/mp_radio_right.png
new file mode 100644
index 00000000..aa5e244f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_radio_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_radio_top.png b/mods/ma_pops_furniture/textures/mp_radio_top.png
new file mode 100644
index 00000000..eff52477
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_radio_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_s.png b/mods/ma_pops_furniture/textures/mp_s.png
new file mode 100644
index 00000000..7ba186a7
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_s.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_shears.png b/mods/ma_pops_furniture/textures/mp_shears.png
new file mode 100644
index 00000000..c567a2fa
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_shears.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_shk_back.png b/mods/ma_pops_furniture/textures/mp_shk_back.png
new file mode 100644
index 00000000..7c6d54e3
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_shk_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_shk_bottom.png b/mods/ma_pops_furniture/textures/mp_shk_bottom.png
new file mode 100644
index 00000000..2c6a3b30
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_shk_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_shk_front.png b/mods/ma_pops_furniture/textures/mp_shk_front.png
new file mode 100644
index 00000000..a693d14d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_shk_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_shk_left.png b/mods/ma_pops_furniture/textures/mp_shk_left.png
new file mode 100644
index 00000000..b9202fe4
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_shk_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_shk_right.png b/mods/ma_pops_furniture/textures/mp_shk_right.png
new file mode 100644
index 00000000..c91e8174
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_shk_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_shk_top.png b/mods/ma_pops_furniture/textures/mp_shk_top.png
new file mode 100644
index 00000000..9b8183d1
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_shk_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_showbas_sides.png b/mods/ma_pops_furniture/textures/mp_showbas_sides.png
new file mode 100644
index 00000000..1d6bb5ab
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_showbas_sides.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_showbas_top.png b/mods/ma_pops_furniture/textures/mp_showbas_top.png
new file mode 100644
index 00000000..8acf7e8c
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_showbas_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_si.png b/mods/ma_pops_furniture/textures/mp_si.png
new file mode 100644
index 00000000..40b4118e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_si.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_sink_top.png b/mods/ma_pops_furniture/textures/mp_sink_top.png
new file mode 100644
index 00000000..17394db1
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_sink_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_sofa.png b/mods/ma_pops_furniture/textures/mp_sofa.png
new file mode 100644
index 00000000..ca1fd6f0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_sofa.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_stairs_back.png b/mods/ma_pops_furniture/textures/mp_stairs_back.png
new file mode 100644
index 00000000..d75be957
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_stairs_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_stairs_front.png b/mods/ma_pops_furniture/textures/mp_stairs_front.png
new file mode 100644
index 00000000..17b9c0eb
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_stairs_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_stairs_side.png b/mods/ma_pops_furniture/textures/mp_stairs_side.png
new file mode 100644
index 00000000..afeaa3f7
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_stairs_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_stool_top.png b/mods/ma_pops_furniture/textures/mp_stool_top.png
new file mode 100644
index 00000000..db5e8e76
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_stool_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_stool_top1.png b/mods/ma_pops_furniture/textures/mp_stool_top1.png
new file mode 100644
index 00000000..4d7cb848
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_stool_top1.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_t.png b/mods/ma_pops_furniture/textures/mp_t.png
new file mode 100644
index 00000000..b8d8811b
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_t.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_table_stone.png b/mods/ma_pops_furniture/textures/mp_table_stone.png
new file mode 100644
index 00000000..07087ebe
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_table_stone.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_table_wood.png b/mods/ma_pops_furniture/textures/mp_table_wood.png
new file mode 100644
index 00000000..e390d270
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_table_wood.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_table_wood_acacia.png b/mods/ma_pops_furniture/textures/mp_table_wood_acacia.png
new file mode 100644
index 00000000..06921fda
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_table_wood_acacia.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_table_wood_aspen.png b/mods/ma_pops_furniture/textures/mp_table_wood_aspen.png
new file mode 100644
index 00000000..e282d598
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_table_wood_aspen.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_table_wood_jungle.png b/mods/ma_pops_furniture/textures/mp_table_wood_jungle.png
new file mode 100644
index 00000000..270c7f70
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_table_wood_jungle.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_table_wood_pine.png b/mods/ma_pops_furniture/textures/mp_table_wood_pine.png
new file mode 100644
index 00000000..bae817cd
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_table_wood_pine.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_back.png b/mods/ma_pops_furniture/textures/mp_toas_back.png
new file mode 100644
index 00000000..908bf93e
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_back_bread.png b/mods/ma_pops_furniture/textures/mp_toas_back_bread.png
new file mode 100644
index 00000000..b0e637a7
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_back_bread.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_back_side.png b/mods/ma_pops_furniture/textures/mp_toas_back_side.png
new file mode 100644
index 00000000..358d0f54
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_back_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_back_toast.png b/mods/ma_pops_furniture/textures/mp_toas_back_toast.png
new file mode 100644
index 00000000..8f4cb1ad
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_back_toast.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_bottom.png b/mods/ma_pops_furniture/textures/mp_toas_bottom.png
new file mode 100644
index 00000000..efc46b05
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_front.png b/mods/ma_pops_furniture/textures/mp_toas_front.png
new file mode 100644
index 00000000..47a5dc31
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_front_bread.png b/mods/ma_pops_furniture/textures/mp_toas_front_bread.png
new file mode 100644
index 00000000..88e29268
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_front_bread.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_front_side.png b/mods/ma_pops_furniture/textures/mp_toas_front_side.png
new file mode 100644
index 00000000..b6677b87
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_front_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_front_toast.png b/mods/ma_pops_furniture/textures/mp_toas_front_toast.png
new file mode 100644
index 00000000..bec71537
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_front_toast.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_left.png b/mods/ma_pops_furniture/textures/mp_toas_left.png
new file mode 100644
index 00000000..562c5a3d
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_left_bread.png b/mods/ma_pops_furniture/textures/mp_toas_left_bread.png
new file mode 100644
index 00000000..eb9b86d0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_left_bread.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_left_toast.png b/mods/ma_pops_furniture/textures/mp_toas_left_toast.png
new file mode 100644
index 00000000..dc15ef87
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_left_toast.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_left_toast_side.png b/mods/ma_pops_furniture/textures/mp_toas_left_toast_side.png
new file mode 100644
index 00000000..215eb57f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_left_toast_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_right.png b/mods/ma_pops_furniture/textures/mp_toas_right.png
new file mode 100644
index 00000000..0aa9bbc6
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_right_bread.png b/mods/ma_pops_furniture/textures/mp_toas_right_bread.png
new file mode 100644
index 00000000..c3e559f0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_right_bread.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_right_toast.png b/mods/ma_pops_furniture/textures/mp_toas_right_toast.png
new file mode 100644
index 00000000..dabbabe1
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_right_toast.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_top.png b/mods/ma_pops_furniture/textures/mp_toas_top.png
new file mode 100644
index 00000000..70238503
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_top_bread.png b/mods/ma_pops_furniture/textures/mp_toas_top_bread.png
new file mode 100644
index 00000000..ab43a773
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_top_bread.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_top_bread_on.png b/mods/ma_pops_furniture/textures/mp_toas_top_bread_on.png
new file mode 100644
index 00000000..c254ef82
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_top_bread_on.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toas_top_toast.png b/mods/ma_pops_furniture/textures/mp_toas_top_toast.png
new file mode 100644
index 00000000..c58d2770
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toas_top_toast.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_toast.png b/mods/ma_pops_furniture/textures/mp_toast.png
new file mode 100644
index 00000000..acbe34a7
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_toast.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_top.png b/mods/ma_pops_furniture/textures/mp_top.png
new file mode 100644
index 00000000..14af8583
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_tp_back.png b/mods/ma_pops_furniture/textures/mp_tp_back.png
new file mode 100644
index 00000000..ac014693
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_tp_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_tp_bottom.png b/mods/ma_pops_furniture/textures/mp_tp_bottom.png
new file mode 100644
index 00000000..ae3332ec
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_tp_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_tp_front.png b/mods/ma_pops_furniture/textures/mp_tp_front.png
new file mode 100644
index 00000000..77d00e50
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_tp_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_tp_left.png b/mods/ma_pops_furniture/textures/mp_tp_left.png
new file mode 100644
index 00000000..07452769
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_tp_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_tp_right.png b/mods/ma_pops_furniture/textures/mp_tp_right.png
new file mode 100644
index 00000000..f92629cc
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_tp_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_tp_top.png b/mods/ma_pops_furniture/textures/mp_tp_top.png
new file mode 100644
index 00000000..4dac23f4
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_tp_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_trampoline_side.png b/mods/ma_pops_furniture/textures/mp_trampoline_side.png
new file mode 100644
index 00000000..dfeef414
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_trampoline_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_trampoline_top.png b/mods/ma_pops_furniture/textures/mp_trampoline_top.png
new file mode 100644
index 00000000..716813be
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_trampoline_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ufridge_front.png b/mods/ma_pops_furniture/textures/mp_ufridge_front.png
new file mode 100644
index 00000000..c8e961d3
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ufridge_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ufridge_left.png b/mods/ma_pops_furniture/textures/mp_ufridge_left.png
new file mode 100644
index 00000000..6a1aed77
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ufridge_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ufridge_right.png b/mods/ma_pops_furniture/textures/mp_ufridge_right.png
new file mode 100644
index 00000000..4fe2e5d8
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ufridge_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_ufridge_top.png b/mods/ma_pops_furniture/textures/mp_ufridge_top.png
new file mode 100644
index 00000000..d11c69cc
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_ufridge_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_up_bottom.png b/mods/ma_pops_furniture/textures/mp_up_bottom.png
new file mode 100644
index 00000000..5ab650ec
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_up_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_up_front.png b/mods/ma_pops_furniture/textures/mp_up_front.png
new file mode 100644
index 00000000..ebc38e19
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_up_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_up_left.png b/mods/ma_pops_furniture/textures/mp_up_left.png
new file mode 100644
index 00000000..0c4c5221
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_up_left.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_up_right.png b/mods/ma_pops_furniture/textures/mp_up_right.png
new file mode 100644
index 00000000..9c38bce6
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_up_right.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_up_top.png b/mods/ma_pops_furniture/textures/mp_up_top.png
new file mode 100644
index 00000000..9239e701
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_up_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_vcr_off.png b/mods/ma_pops_furniture/textures/mp_vcr_off.png
new file mode 100644
index 00000000..3b1158b2
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_vcr_off.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_vcr_on.png b/mods/ma_pops_furniture/textures/mp_vcr_on.png
new file mode 100644
index 00000000..afeb0f7f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_vcr_on.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_venext_back.png b/mods/ma_pops_furniture/textures/mp_venext_back.png
new file mode 100644
index 00000000..609496e0
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_venext_back.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_venext_bottom.png b/mods/ma_pops_furniture/textures/mp_venext_bottom.png
new file mode 100644
index 00000000..5d31117f
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_venext_bottom.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_venext_front.png b/mods/ma_pops_furniture/textures/mp_venext_front.png
new file mode 100644
index 00000000..aea95098
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_venext_front.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_venext_side.png b/mods/ma_pops_furniture/textures/mp_venext_side.png
new file mode 100644
index 00000000..2a1411ec
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_venext_side.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_venext_side2.png b/mods/ma_pops_furniture/textures/mp_venext_side2.png
new file mode 100644
index 00000000..00a1bdef
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_venext_side2.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_venext_top.png b/mods/ma_pops_furniture/textures/mp_venext_top.png
new file mode 100644
index 00000000..44011fdb
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_venext_top.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_venext_top1.png b/mods/ma_pops_furniture/textures/mp_venext_top1.png
new file mode 100644
index 00000000..f900a195
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_venext_top1.png differ
diff --git a/mods/ma_pops_furniture/textures/mp_wool_coloured_rainbow.png b/mods/ma_pops_furniture/textures/mp_wool_coloured_rainbow.png
new file mode 100644
index 00000000..22e80b09
Binary files /dev/null and b/mods/ma_pops_furniture/textures/mp_wool_coloured_rainbow.png differ
diff --git a/mods/ma_pops_furniture/toaster.lua b/mods/ma_pops_furniture/toaster.lua
new file mode 100644
index 00000000..33b3b582
--- /dev/null
+++ b/mods/ma_pops_furniture/toaster.lua
@@ -0,0 +1,183 @@
+local S = ma_pops_furniture.intllib
+
+--Toaster and Toast--
+minetest.register_node("ma_pops_furniture:toaster", {
+ description = S("Toaster"),
+ tiles = {
+ "mp_toas_top.png",
+ "mp_toas_bottom.png",
+ "mp_toas_right.png",
+ "mp_toas_left.png",
+ "mp_toas_back.png",
+ "mp_toas_front.png"
+ },
+ walkable = false,
+ groups = { snappy=3 },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, 0, 0.375, -0.0625, 0.3125},
+ {-0.4375, -0.1875, 0.0625, -0.375, -0.125, 0.25},
+ },
+ },
+})
+
+local function breadslice_on_use(itemstack, user, pointed_thing)
+ local node, pos
+ if pointed_thing.under then
+ pos = pointed_thing.under
+ node = minetest.get_node(pos)
+ end
+
+ local pname = user:get_player_name()
+
+ if node and pos and (node.name == "ma_pops_furniture:toaster") then
+ if minetest.is_protected(pos, pname) then
+ minetest.record_protection_violation(pos, pname)
+ else
+ if itemstack:get_count() >= 2 then
+ itemstack:take_item(2)
+ minetest.set_node(pos, {name = "ma_pops_furniture:toaster_with_breadslice", param2 = node.param2})
+ return itemstack
+ end
+ end
+ else
+ return minetest.do_item_eat(2, nil, itemstack, user, pointed_thing)
+ end
+end
+
+if minetest.registered_items["farming:bread_slice"] then
+ minetest.override_item("farming:bread_slice", {on_use = breadslice_on_use })
+ minetest.register_alias("ma_pops_furniture:breadslice", "farming:bread_slice")
+else
+ minetest.register_craftitem("ma_pops_furniture:breadslice", {
+ description = S("Slice of Bread"),
+ inventory_image = "mp_breadslice.png",
+ groups = {flammable = 2},
+ on_use = breadslice_on_use,
+ })
+end
+
+if minetest.registered_items["farming:toast"] then
+ minetest.register_alias("ma_pops_furniture:toast", "farming:toast")
+else
+ minetest.register_craftitem("ma_pops_furniture:toast", {
+ description = S("Toast"),
+ inventory_image = "mp_toast.png",
+ on_use = minetest.item_eat(3),
+ groups = {flammable = 2},
+ })
+end
+
+minetest.register_node("ma_pops_furniture:toaster_with_breadslice", {
+ description = S("Toaster with Breadslice"),
+ tiles = {
+ "mp_toas_top_bread.png",
+ "mp_toas_bottom.png",
+ "mp_toas_right_bread.png",
+ "mp_toas_left_bread.png",
+ "mp_toas_back_bread.png",
+ "mp_toas_front_bread.png"
+ },
+ walkable = false,
+ groups = {not_in_creative_inventory=1},
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ diggable = false,
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, 0, 0.375, -0.0625, 0.3125}, -- NodeBox1
+ {-0.25, -0.0625, 0.0625, 0.25, 0.0625, 0.125}, -- NodeBox2
+ {-0.25, -0.0625, 0.1875, 0.25, 0.0625, 0.25}, -- NodeBox3
+ {-0.4375, -0.1875, 0.0625, -0.375, -0.125, 0.25}, -- NodeBox4
+ },
+ },
+ on_punch = function(pos, node, clicker, itemstack, pointed_thing)
+ local fdir = node.param2
+ minetest.set_node(pos, { name = "ma_pops_furniture:toaster_toasting_breadslice", param2 = fdir })
+ minetest.after(6, minetest.set_node, pos, { name = "ma_pops_furniture:toaster_with_toast", param2 = fdir })
+ minetest.sound_play("toaster", {
+ pos = pos,
+ gain = 1.0,
+ max_hear_distance = 5
+ })
+ return itemstack
+ end
+})
+
+minetest.register_node("ma_pops_furniture:toaster_toasting_breadslice", {
+ description = S("Toaster Toasting Slice of Bread"),
+ tiles = {
+ "mp_toas_top_bread_on.png",
+ "mp_toas_bottom.png",
+ "mp_toas_right_bread.png",
+ "mp_toas_left_toast_side.png",
+ "mp_toas_back_side.png",
+ "mp_toas_front_side.png"
+ },
+ walkable = false,
+ groups = {not_in_creative_inventory = 1 },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ diggable = false,
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, 0, 0.375, -0.0625, 0.3125}, -- NodeBox1
+ {-0.4375, -0.375, 0.0625, -0.375, -0.3125, 0.25}, -- NodeBox4
+ },
+ },
+})
+
+minetest.register_node("ma_pops_furniture:toaster_with_toast", {
+ description = S("Toaster with Toast"),
+ tiles = {
+ "mp_toas_top_toast.png",
+ "mp_toas_bottom.png",
+ "mp_toas_right_toast.png",
+ "mp_toas_left_toast.png",
+ "mp_toas_back_toast.png",
+ "mp_toas_front_toast.png"
+ },
+ walkable = false,
+ groups = { snappy=3, not_in_creative_inventory=1 },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.375, -0.5, 0, 0.375, -0.0625, 0.3125}, -- NodeBox1
+ {-0.25, -0.0625, 0.0625, 0.25, 0.0625, 0.125}, -- NodeBox2
+ {-0.25, -0.0625, 0.1875, 0.25, 0.0625, 0.25}, -- NodeBox3
+ {-0.4375, -0.1875, 0.0625, -0.375, -0.125, 0.25}, -- NodeBox4
+ },
+ },
+ on_punch = function (pos, node, player, pointed_thing)
+ local inv = player:get_inventory()
+ local left = inv:add_item("main", "ma_pops_furniture:toast 2")
+ if left:is_empty() then
+ minetest.set_node(pos, {name = "ma_pops_furniture:toaster", param2 = node.param2})
+ end
+ end
+})
+
+--Slice of Bread (only if not farming one used)
+if not minetest.registered_items["farming:bread_slice"] then
+ minetest.register_craft({
+ output = 'ma_pops_furniture:breadslice 2',
+ type = "shapeless",
+ recipe = {"farming:bread", "ma_pops_furniture:knife"},
+ replacements = {{"ma_pops_furniture:knife", "ma_pops_furniture:knife"}},
+ })
+end
\ No newline at end of file
diff --git a/mods/ma_pops_furniture/tools.lua b/mods/ma_pops_furniture/tools.lua
new file mode 100644
index 00000000..10cb20e1
--- /dev/null
+++ b/mods/ma_pops_furniture/tools.lua
@@ -0,0 +1,129 @@
+local USES = 200 --how many times you can use the tool before it breaks.
+
+minetest.register_tool('ma_pops_furniture:hammer', {
+ description = 'Hammer',
+ inventory_image = 'mp_hammer.png',
+ on_use = function(itemstack, user, pointed_thing)
+ if pointed_thing.type ~= 'node' then
+ return
+ end
+ local pos = pointed_thing.under
+ local node = minetest.get_node(pos)
+ local sofa_table = {
+ {'black'},
+ {'blue'},
+ {'brown'},
+ {'cyan'},
+ {'dark_green'},
+ {'dark_grey'},
+ {'green'},
+ {'grey'},
+ {'magenta'},
+ {'orange'},
+ {'pink'},
+ {'red'},
+ {'violet'},
+ {'white'},
+ {'yellow'},
+ }
+
+ for i in ipairs (sofa_table) do
+ local color = sofa_table[i][1]
+
+ if node.name == 'ma_pops_furniture:sofa_'..color then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:sofa_r_'..color, param2=node.param2})
+ end
+ if node.name == 'ma_pops_furniture:sofa_r_'..color then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:sofa_m_'..color, param2=node.param2})
+ end
+ if node.name == 'ma_pops_furniture:sofa_m_'..color then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:sofa_l_'..color, param2=node.param2})
+ end
+ if node.name == 'ma_pops_furniture:sofa_l_'..color then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:sofa_c_'..color, param2=node.param2})
+ end
+ if node.name == 'ma_pops_furniture:sofa_c_'..color then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:sofa_'..color, param2=node.param2})
+ end
+ end
+ if not minetest.setting_getbool("creative_mode") then
+ itemstack:add_wear(65535 / (USES - 1))
+ end
+ return itemstack
+ end,
+
+ on_place = function(itemstack, user, pointed_thing)
+ if pointed_thing.type ~= 'node' then
+ return
+ end
+ local pos = pointed_thing.under
+ local node = minetest.get_node(pos).name
+ local para = minetest.get_node(pos).param2
+ local newpara = para + 1
+ if newpara > 3 then
+ newpara = 0
+ end
+ minetest.set_node(pos,{name = ""..node, param2 = newpara})
+
+ if not minetest.setting_getbool("creative_mode") then
+ itemstack:add_wear(65535 / (USES - 1))
+ end
+ return itemstack
+ end,
+})
+
+minetest.register_tool('ma_pops_furniture:shears', {
+ description = 'Shears',
+ inventory_image = 'mp_shears.png',
+ on_use = function(itemstack, user, pointed_thing)
+ if pointed_thing.type ~= 'node' then
+ return
+ end
+ local pos = pointed_thing.under
+ local node = minetest.get_node(pos)
+ local hedge_table = { --material
+ {'leaves'},
+ {'pine_needles'},
+ {'jungleleaves'},
+ {'aspen_leaves'},
+ {'acacia_leaves'}
+ }
+
+ for i in ipairs (hedge_table) do
+ local mat = hedge_table[i][1]
+
+ if node.name == 'ma_pops_furniture:hedge_'..mat then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:hedge_t_'..mat, param2=node.param2})
+ end
+ if node.name == 'ma_pops_furniture:hedge_t_'..mat then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:hedge_c_'..mat, param2=node.param2})
+ end
+ if node.name == 'ma_pops_furniture:hedge_c_'..mat then
+ minetest.set_node(pos,{name = 'ma_pops_furniture:hedge_'..mat, param2=node.param2})
+ end
+ end
+ if not minetest.setting_getbool("creative_mode") then
+ itemstack:add_wear(65535 / (USES - 1))
+ end
+ return itemstack
+ end,
+
+ on_place = function(itemstack, user, pointed_thing)
+ if pointed_thing.type ~= 'node' then
+ return
+ end
+ local pos = pointed_thing.under
+ local node = minetest.get_node(pos).name
+ local para = minetest.get_node(pos).param2
+ local newpara = para + 1
+ if newpara > 3 then
+ newpara = 0
+ end
+ minetest.set_node(pos,{name = ""..node, param2 = newpara})
+
+ if not minetest.setting_getbool("creative_mode") then
+ itemstack:add_wear(65535 / (USES - 1))
+ end
+ return itemstack
+ end,
+})
diff --git a/mods/ma_pops_furniture/toys.lua b/mods/ma_pops_furniture/toys.lua
new file mode 100644
index 00000000..b0536765
--- /dev/null
+++ b/mods/ma_pops_furniture/toys.lua
@@ -0,0 +1,26 @@
+-- GENERATED CODE
+-- Node Box Editor, version 0.9.0
+-- Namespace: test
+
+minetest.register_node("ma_pops_furniture:boy_game", {
+ description = "BoyGame",
+ tiles = {
+ "default_silver_sandstone.png^mp_boygame.png",
+ "default_silver_sandstone.png^mp_boygame_back.png^[transformR180]",
+ "default_silver_sandstone.png^mp_boygame_right.png",
+ "default_silver_sandstone.png^mp_boygame_left.png",
+ "default_silver_sandstone.png^mp_boygame_top.png",
+ "default_silver_sandstone.png^mp_boygame_front.png"
+ },
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ groups = {choppy = 1, oddly_breakable_by_hand = 1},
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.3125, -0.5, -0.4375, 0.3125, -0.3125, 0.4375},
+ }
+ }
+})
+
diff --git a/mods/ma_pops_furniture/tv.lua b/mods/ma_pops_furniture/tv.lua
new file mode 100644
index 00000000..7dcd18dc
--- /dev/null
+++ b/mods/ma_pops_furniture/tv.lua
@@ -0,0 +1,210 @@
+local function handle_rightclick(pos, node, sound_name, new_node_name)
+ local meta = minetest.get_meta(pos)
+ node.name = new_node_name
+ minetest.set_node(pos, node)
+
+ local hwnd = meta:get_string("hwnd")
+ if hwnd and string.len(hwnd) > 0 then
+ minetest.sound_stop(hwnd)
+ end
+
+ meta:set_string("hwnd", minetest.sound_play(sound_name, {
+ gain = 0.5,
+ pos = pos,
+ max_hear_distance = 25,
+ loop = true
+ }))
+end
+
+local function handle_destruct(pos)
+ local meta = minetest.get_meta(pos)
+ local hwnd = meta:get_string("hwnd")
+ if hwnd and string.len(hwnd) > 0 then
+ minetest.sound_stop(hwnd)
+ end
+end
+
+local function register_tv_node(name, desc, mesh, tiles, sound, new_node_name, light, groups, additional_props)
+ local node_def = {
+ description = desc,
+ drawtype = "mesh",
+ mesh = mesh,
+ drop = "ma_pops_furniture:tv_off",
+ tiles = tiles,
+ groups = groups,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ light_source = light,
+ sounds = moditems.WOOD_SOUNDS,
+ selection_box = {
+ type = "fixed",
+ fixed = {-.45, -.5, -.5, .45, .4, .45}
+ },
+ collision_box = {
+ type = "fixed",
+ fixed = {-.45, -.5, -.5, .45, .4, .45}
+ },
+ on_rightclick = function(pos, node, clicker, itemstack)
+ handle_rightclick(pos, node, sound, new_node_name)
+ end,
+ on_destruct = handle_destruct
+ }
+
+ -- Apply additional properties if provided
+ if additional_props then
+ for k, v in pairs(additional_props) do
+ node_def[k] = v
+ end
+ end
+
+ minetest.register_node(name, node_def)
+end
+
+local common_groups = {cracky=2, oddly_breakable_by_hand=3, furniture=1}
+local not_in_creative = {cracky=2, oddly_breakable_by_hand=3, not_in_creative_inventory=1, furniture=1}
+
+register_tv_node(
+ 'ma_pops_furniture:tv_rainbow',
+ 'TV',
+ 'FM_tv.obj',
+ {
+ {name='default_tree.png'},
+ {name='mp_channel_rainbow.png', animation={type='vertical_frames', aspect_w=64, aspect_h=48, length=3}}
+ },
+ 'mp_blast',
+ 'ma_pops_furniture:tv_blast',
+ 14,
+ not_in_creative
+)
+
+register_tv_node(
+ 'ma_pops_furniture:tv_blast',
+ 'TV',
+ 'FM_tv.obj',
+ {
+ {name='default_tree.png'},
+ {name='mp_channel_blast.png', animation={type='vertical_frames', aspect_w=64, aspect_h=64, length=22}}
+ },
+ 'mp_static',
+ 'ma_pops_furniture:tv_static',
+ 14,
+ not_in_creative
+)
+
+register_tv_node(
+ 'ma_pops_furniture:tv_static',
+ 'TV',
+ 'FM_tv.obj',
+ {
+ {name='default_tree.png'},
+ {name='mp_channel_static.png', animation={type='vertical_frames', aspect_w=40, aspect_h=30, length=1}}
+ },
+ 'mp_cube',
+ 'ma_pops_furniture:tv_cube',
+ 14,
+ not_in_creative
+)
+
+register_tv_node(
+ 'ma_pops_furniture:tv_cube',
+ 'TV',
+ 'FM_tv.obj',
+ {
+ {name='default_tree.png'},
+ {name='mp_channel_cube.png', animation={type='vertical_frames', aspect_w=40, aspect_h=40, length=2}}
+ },
+ 'mp_off',
+ 'ma_pops_furniture:tv_off',
+ 14,
+ not_in_creative
+)
+
+register_tv_node(
+ 'ma_pops_furniture:tv_off',
+ 'TV',
+ 'FM_tv.obj',
+ {
+ {name='default_tree.png'},
+ {name='wool_black.png^default_glass_detail.png^[colorize:black:225'}
+ },
+ 'mp_rainbow',
+ 'ma_pops_furniture:tv_rainbow',
+ 1,
+ common_groups
+)
+
+register_tv_node(
+ 'ma_pops_furniture:lcd_tv_off',
+ 'TV',
+ 'FM_lcd_tv.obj',
+ {
+ {name='mp_flat_tv.png'},
+ {name='wool_black.png^default_glass_detail.png^[colorize:black:225'}
+ },
+ 'mp_rainbow',
+ 'ma_pops_furniture:lcd_tv_rainbow',
+ 1,
+ common_groups,
+ {
+ sounds = moditems.STONE_SOUNDS,
+ selection_box = {
+ type = "fixed",
+ fixed = {-0.98, -.5, -.10, 0.98, .8, .10}
+ },
+ collision_box = {
+ type = "fixed",
+ fixed = {-0.98, -.5, -.10, 0.98, .8, .10}
+ }
+ }
+)
+
+register_tv_node(
+ 'ma_pops_furniture:lcd_tv_rainbow',
+ 'TV',
+ 'FM_lcd_tv.obj',
+ {
+ {name='mp_flat_tv.png'},
+ {name='mp_channel_screen.png', animation={type='vertical_frames', aspect_w=40, aspect_h=40, length=2}}
+ },
+ 'mp_blast',
+ 'ma_pops_furniture:lcd_tv_blast',
+ 1,
+ not_in_creative,
+ {
+ sounds = moditems.STONE_SOUNDS,
+ drop = "ma_pops_furniture:lcd_tv_off",
+ selection_box = {
+ type = "fixed",
+ fixed = {-0.98, -.5, -.10, 0.98, .8, .10}
+ },
+ collision_box = {
+ type = "fixed",
+ fixed = {-0.98, -.5, -.10, 0.98, .8, .10}
+ }
+ }
+)
+
+register_tv_node(
+ 'ma_pops_furniture:lcd_tv_blast',
+ 'HD TV',
+ 'FM_lcd_tv.obj',
+ {
+ {name='mp_flat_tv.png'},
+ {name='mp_channel_blast.png', animation={type='vertical_frames', aspect_w=64, aspect_h=64, length=22}}
+ },
+ 'mp_off',
+ 'ma_pops_furniture:lcd_tv_off',
+ 14,
+ not_in_creative,
+ {
+ sounds = moditems.STONE_SOUNDS,
+ selection_box = {
+ type = "fixed",
+ fixed = {-0.98, -.5, -.10, 0.98, .8, .10}
+ },
+ collision_box = {
+ type = "fixed",
+ fixed = {-0.98, -.5, -.10, 0.98, .8, .10}
+ }
+ }
+)
diff --git a/mods/minetest_game/flowers/init.lua b/mods/minetest_game/flowers/init.lua
index e13ee32f..6bb979dc 100644
--- a/mods/minetest_game/flowers/init.lua
+++ b/mods/minetest_game/flowers/init.lua
@@ -194,7 +194,7 @@ minetest.register_node("flowers:mushroom_red", {
sunlight_propagates = true,
walkable = false,
buildable_to = true,
- groups = {mushroom = 1, snappy = 3, attached_node = 1, flammable = 1},
+ groups = {mushroom = 1, snappy = 3, attached_node = 1, flammable = 1, eatable = -5},
sounds = default.node_sound_leaves_defaults(),
on_use = minetest.item_eat(-5),
selection_box = {
@@ -213,7 +213,7 @@ minetest.register_node("flowers:mushroom_brown", {
sunlight_propagates = true,
walkable = false,
buildable_to = true,
- groups = {mushroom = 1, food_mushroom = 1, snappy = 3, attached_node = 1, flammable = 1},
+ groups = {mushroom = 1, food_mushroom = 1, snappy = 3, attached_node = 1, flammable = 1, eatable = 1},
sounds = default.node_sound_leaves_defaults(),
on_use = minetest.item_eat(1),
selection_box = {
diff --git a/mods/moreblocks/.cdb.json b/mods/moreblocks/.cdb.json
new file mode 100644
index 00000000..2d676819
--- /dev/null
+++ b/mods/moreblocks/.cdb.json
@@ -0,0 +1,11 @@
+{
+ "type": "MOD",
+ "name": "moreblocks",
+ "title": "moreblocks and stairsplus",
+ "short_description": "adds various blocks to the game",
+ "repo": "https://github.com/fluxionary/minetest-moreblocks.git",
+ "website": "https://github.com/fluxionary/minetest-moreblocks",
+ "issue_tracker": "https://github.com/fluxionary/minetest-moreblocks/issues",
+ "license": "LGPL-3.0-or-later",
+ "media_license": "CC-BY-SA-4.0"
+}
diff --git a/mods/moreblocks/.check_date.sh b/mods/moreblocks/.check_date.sh
new file mode 100644
index 00000000..b2ae3259
--- /dev/null
+++ b/mods/moreblocks/.check_date.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+grep $(date -u -I) modpack.conf &&
+ grep $(date -u -I) invsaw/mod.conf &&
+ grep $(date -u -I) moreblocks/mod.conf &&
+ grep $(date -u -I) moreblocks_legacy_recipes/mod.conf &&
+ grep $(date -u -I) stairs/mod.conf &&
+ grep $(date -u -I) stairsplus/mod.conf &&
+ grep $(date -u -I) stairsplus_legacy/mod.conf
+exit $?
diff --git a/mods/moreblocks/.editorconfig b/mods/moreblocks/.editorconfig
new file mode 100644
index 00000000..33568faf
--- /dev/null
+++ b/mods/moreblocks/.editorconfig
@@ -0,0 +1,14 @@
+# See https://editorconfig.org/
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{lua,luacheckrc}]
+indent_style = tab
diff --git a/mods/moreblocks/.github/workflows/pre-commit.yml b/mods/moreblocks/.github/workflows/pre-commit.yml
new file mode 100644
index 00000000..d1faea17
--- /dev/null
+++ b/mods/moreblocks/.github/workflows/pre-commit.yml
@@ -0,0 +1,34 @@
+name: pre-commit
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: update
+ run: sudo apt-get update -y
+
+ - uses: actions/checkout@master
+ - uses: actions/setup-python@master
+
+ - name: install luarocks
+ run: sudo apt-get install -y luarocks
+
+ - name: add luarocks path
+ run: echo "$HOME/.luarocks/bin" >> $GITHUB_PATH
+
+ - name: luacheck install
+ run: luarocks install --local luacheck
+
+ - name: install cargo
+ run: sudo apt-get install -y cargo
+
+ - name: install stylua
+ run: cargo install stylua
+
+ - name: Install pre-commit
+ run: pip3 install pre-commit
+
+ - name: Run pre-commit
+ run: pre-commit run --all-files
diff --git a/mods/moreblocks/.gitignore b/mods/moreblocks/.gitignore
new file mode 100644
index 00000000..6796d82e
--- /dev/null
+++ b/mods/moreblocks/.gitignore
@@ -0,0 +1,7 @@
+/stairsplus/scripts/dump.json
+/stairsplus/scripts/pg_connection
+/stairsplus/scripts/schems/
+/stairsplus/scripts/tmp
+/stairsplus/scripts/venv/
+/stairsplus/scripts/crap/
+/stairsplus/scripts/output
diff --git a/mods/moreblocks/.pre-commit-config.yaml b/mods/moreblocks/.pre-commit-config.yaml
new file mode 100644
index 00000000..093f4450
--- /dev/null
+++ b/mods/moreblocks/.pre-commit-config.yaml
@@ -0,0 +1,69 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.3.0
+ hooks:
+ - id: fix-byte-order-marker
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - id: mixed-line-ending
+ args: [ --fix=lf ]
+
+ - repo: local
+ hooks:
+ - id: detect_debug
+ name: detect debug
+ language: pygrep
+ entry: DEBUG
+ pass_filenames: true
+ exclude: .pre-commit-config.yaml
+ fail_fast: true
+ - id: date_version
+ name: date version
+ language: script
+ entry: .check_date.sh
+ files: modpack.conf, mod.conf
+ always_run: true
+ fail_fast: true
+ - id: stylua
+ name: stylua
+ language: system
+ entry: stylua
+ pass_filenames: true
+ types: [ file, lua ]
+ fail_fast: true
+ - id: luacheck_invsaw
+ name: luacheck_invsaw
+ language: system
+ entry: luacheck
+ pass_filenames: false
+ args: [--config,./invsaw/.luacheckrc,-q,./invsaw]
+ - id: luacheck_moreblocks
+ name: luacheck_moreblocks
+ language: system
+ entry: luacheck
+ pass_filenames: false
+ args: [--config,./moreblocks/.luacheckrc,-q,./moreblocks]
+ - id: luacheck_moreblocks_legacy_recipes
+ name: luacheck_moreblocks_legacy_recipes
+ language: system
+ entry: luacheck
+ pass_filenames: false
+ args: [--config,./moreblocks_legacy_recipes/.luacheckrc,-q,./moreblocks_legacy_recipes]
+ - id: luacheck_stairs
+ name: luacheck_stairs
+ language: system
+ entry: luacheck
+ pass_filenames: false
+ args: [--config,./stairs/.luacheckrc,-q,./stairs]
+ - id: luacheck_stairsplus
+ name: luacheck_stairsplus
+ language: system
+ entry: luacheck
+ pass_filenames: false
+ args: [--config,./stairsplus/.luacheckrc,-q,./stairsplus]
+ - id: luacheck_stairsplus_legacy
+ name: luacheck_stairsplus_legacy
+ language: system
+ entry: luacheck
+ pass_filenames: false
+ args: [--config,./stairsplus_legacy/.luacheckrc,-q,./stairsplus_legacy]
diff --git a/mods/moreblocks/LICENSE.txt b/mods/moreblocks/LICENSE.txt
new file mode 100644
index 00000000..b6ff27cc
--- /dev/null
+++ b/mods/moreblocks/LICENSE.txt
@@ -0,0 +1,168 @@
+this license is for the code.
+any non-code media included in this repository is covered by the contents of MEDIA_LICENSE.txt.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/mods/moreblocks/MEDIA_LICENSE.txt b/mods/moreblocks/MEDIA_LICENSE.txt
new file mode 100644
index 00000000..7d4f96c5
--- /dev/null
+++ b/mods/moreblocks/MEDIA_LICENSE.txt
@@ -0,0 +1,427 @@
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/mods/moreblocks/README.md b/mods/moreblocks/README.md
new file mode 100644
index 00000000..7f02e5bf
--- /dev/null
+++ b/mods/moreblocks/README.md
@@ -0,0 +1,165 @@
+# moreblocks and stairsplus
+
+more blocks for [minetest](https://www.minetest.net/), a free and open source infinite
+world block sandbox game.
+
+
+
+stairsplus adds a large variety of new shapes for registered nodes:
+
+
+
+
+
+this is a fork of the version maintained by the minetest-mods group at https://github.com/minetest-mods/moreblocks.
+it provides numerous new features and bugfixes and is designed to reduce the number of registered nodes, to avoid
+the node ID limit.
+
+# mods in the pack
+
+## moreblocks
+
+defines a bunch of new kinds of nodes. provides an API for creating variants of some nodes.
+
+## moreblocks_legacy_recipes
+
+provided to keep compatibility w/ recipe changes from older versions of moreblocks. disabled by default.
+
+## stairsplus
+
+allows the creation of 49 new shapes for registered nodes. provides an API for registering new shapes.
+
+## stairsplus_legacy
+
+stairsplus registrations for various mods which were formerly done automatically as part of moreblocks.
+
+## stairs
+
+overrides the stairs mod from minetest_game to use stairsplus behind the scenes, to avoid duplication of nodes.
+
+## invsaw
+
+adds a button in unified_inventory that allows you to use the circular saw interface if you are
+playing creatively, or have a circular saw item in your inventory and have the right priv
+(`interact`, by default).
+
+invsaw was taken from [cheapie's invsaw mod](https://forum.minetest.net/viewtopic.php?t=14736), which
+itself borrowed heavily from an older version of this mod. Flux decided to just add it here because it
+needed to be fully rewritten to be compatible w/ their modifications to the stairsplus API.
+
+# documentation
+
+## for players
+
+use of a decent inventory manager (e.g.
+[unified_inventory](https://content.minetest.net/packages/RealBadAngel/unified_inventory/) or
+[i3](https://content.minetest.net/packages/jp/i3/)) will help you figure out how to craft various nodes.
+
+## for admins
+
+### minetest version compatibility
+
+more blocks is only tested against up-to-date minetest. Issues arising in older versions will generally not be fixed.
+
+### legacy mode
+
+the 2023-02-01 release of moreblocks introduces a "legacy" mode, which is on by default, and is meant to
+allow new servers to not commit to creating as many nodes as older versions, while not breaking anything
+on existing servers. See `settingtypes.txt` for available settings.
+
+by defaul the 2023-02-01 release disables certain recipe overrides that were part of moreblocks 2.*. to re-enable
+them, set `moreblocks_legacy_recipes.enabled = true`.
+
+### settings
+
+see `settingtypes.txt` for available settings.
+
+### dependencies
+
+moreblocks and stairsplus do not have hard dependencies on other mods. invsaw depends on `unified_inventory`
+and stairsplus.
+
+### compatability
+
+moreblocks currently supports resources from a number of mods and minetest_game. without these installed,
+some things may not be craftable, may have low-quality textures, or may not have "node sounds" registered.
+if available, resources will be used from `bucket`, `default`, `rhotator`, `screwdriver`, and `vessels`.
+
+if the `stairsplus_legacy` mod is enabled, stairsplus nodes will automatically be registered for the following
+mods, if they are available: `basic_materials`, `default`, `farming`, `gloopblocks`, `prefab`, `technic`,
+and `wool`.
+
+### stairsplus whitelist mode
+
+stairsplus can add a *lot* of nodes to the world - it comes with 49 shape variants! this can lead to big problems,
+because minetest can only handle 32767 different kinds of nodes at the same time. that means, you can at maximum
+create all possible variants for 655 distinct nodes. even fewer variations are possible if you want to use other
+mods at the same time.
+
+in order to reduce the number of registered nodes, stairsplus can be run in *whitelist mode*, which means that only
+nodes listed in a certain file (`$WORLDPATH/stairsplus.whitelist`) will actually get registered (see note 1 below).
+to enable whitelist mode, set `stairsplus.whitelist_mode = true` in minetest.conf.
+
+additionally stairsplus comes w/ tools to automatically create a whitelist for existing worlds, so that server
+operators can reap the benefits of whitelist mode without ending up w/ tons of "unknown" nodes. first, before enabling
+whitelist mode, run the `/dump_stairsplus_registered_nodes` command to generate `$WORLDPATH/stairsplus_dump.json`,
+which will be used in the next step. generating this file should be quick and painless.
+
+the next step is *not* necessarily quick and painless. you will need to run a provided python script, located at
+`moreblocks/stairsplus/scripts/create_whitelist.py`. this script can only process sqlite and postgres map backends.
+additionally, this script only works w/ map serialization version 29, introduced with minetest 5.7.0. if you are
+not running 5.7.0, you will have to upgrade and migrate your database (`--recompress`). additionally, while the script
+is fairly efficient and makes use of multiple threads, it is still slow, and you will have to shut your server down
+for the duration of the execution of this script, or run it against a backup database.
+
+the script requires several non-standard python modules be installed, listed in
+`moreblocks/stairsplus/scripts/requirements.txt`.
+
+to run the script against a sqlite database, execute
+```bash
+python create_whitelist.py -s $WORLDPATH/map.sqlite $WORLDPATH/stairsplus_dump.json
+```
+
+for postgres, fill in the correct connection string for your database:
+```bash
+python create_whitelist.py -p "host=127.0.0.1 user=minetest password=pass dbname=minetest" $WORLDPATH/stairsplus_dump.json
+```
+
+both of these commands will generate `$WORLDPATH/stairsplus.whitelist` if successful. while running, an estimate
+of how much more time is needed for the script to complete will be provided.
+
+notes:
+1. micro_\*_8 variants are always registered, as they are fundamental to the functionality of the mod.
+
+### migrating schemas
+
+stairsplus also comes with a script which can migrate blocks in minetest schemas and worldedit save files to their new
+names in stairsplus. without this, trying to load schemas saved while using old stairsplus may cause a server crash.
+
+first, create the `stairsplus_dump.json` file mentioned in whitelist mode. next, run
+
+```bash
+python translate_schems.py $WORLDPATH/stairsplus_dump.json $WORLDPATH/schems
+```
+
+## for mod makers
+
+See moreblocks/API.md and stairsplus/API.md.
+
+# license
+
+## moreblocks, stairsplus, stairsplus_legacy, moreblocks_legacy_recipes
+
+* © 2011-2022 Hugo Locurcio and contributors under the zlib license
+* © 2023- fluxionary under the LGPL v3+
+
+- unless otherwise specified, textures are licensed under
+ [CC BY-SA 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/).
+- `moreblocks_copperpatina.png` was created by pithydon, and is licensed under
+ [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
+- `stairsplus_saw_button.png` CC BY-SA 3.0 Unported
+
+## invsaw
+
+* © 2022 cheapie and contributors under the zlib license
+* © 2023- fluxionary under the LGPL v3+
diff --git a/mods/moreblocks/invsaw/.luacheckrc b/mods/moreblocks/invsaw/.luacheckrc
new file mode 100644
index 00000000..d2977b74
--- /dev/null
+++ b/mods/moreblocks/invsaw/.luacheckrc
@@ -0,0 +1,657 @@
+std = "lua51+luajit+minetest+invsaw"
+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.invsaw = {
+ globals = {
+ "invsaw",
+ },
+ read_globals = {
+ "fmod",
+ "stairsplus",
+ "unified_inventory",
+ },
+}
diff --git a/mods/moreblocks/invsaw/api.lua b/mods/moreblocks/invsaw/api.lua
new file mode 100644
index 00000000..f0071876
--- /dev/null
+++ b/mods/moreblocks/invsaw/api.lua
@@ -0,0 +1,32 @@
+local server_is_creative = minetest.settings:get_bool("creative_mode", false)
+
+function invsaw.has_saw_in_inventory(player)
+ local inv = player:get_inventory()
+ return inv:contains_item("main", invsaw.settings.saw_item)
+end
+
+function invsaw.can_use_saw(player)
+ return (
+ server_is_creative
+ or minetest.check_player_privs(player, invsaw.settings.creative_priv)
+ or minetest.check_player_privs(player, invsaw.settings.priv)
+ )
+end
+
+function invsaw.allow_use_saw(player)
+ return (
+ server_is_creative
+ or minetest.check_player_privs(player, invsaw.settings.creative_priv)
+ or (minetest.check_player_privs(player, invsaw.settings.priv) and invsaw.has_saw_in_inventory(player))
+ )
+end
+
+function invsaw.check_use_status(player)
+ if invsaw.can_use_saw(player) then
+ invsaw.initialize_inventory(player)
+ else
+ invsaw.drop_inventory(player)
+ end
+end
+
+minetest.register_on_joinplayer(invsaw.check_use_status)
diff --git a/mods/moreblocks/invsaw/formspec.lua b/mods/moreblocks/invsaw/formspec.lua
new file mode 100644
index 00000000..ba97684e
--- /dev/null
+++ b/mods/moreblocks/invsaw/formspec.lua
@@ -0,0 +1,29 @@
+local station = stairsplus.api.station
+local circular_saw = stairsplus.api.circular_saw
+
+function invsaw.show_formspec(player)
+ local name = player:get_player_name()
+ local meta = player:get_meta()
+ local inv = player:get_inventory()
+
+ minetest.show_formspec(name, "invsaw", circular_saw.build_formspec(meta, inv))
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local meta = player:get_meta()
+ local inv = player:get_inventory()
+
+ if fields.saw then
+ if invsaw.allow_use_saw(player) then
+ invsaw.show_formspec(player)
+ end
+
+ return true
+ elseif station.on_receive_fields(meta, inv, formname, fields, player) then
+ if invsaw.allow_use_saw(player) then
+ invsaw.show_formspec(player)
+ end
+
+ return true
+ end
+end)
diff --git a/mods/moreblocks/invsaw/init.lua b/mods/moreblocks/invsaw/init.lua
new file mode 100644
index 00000000..c4163ff6
--- /dev/null
+++ b/mods/moreblocks/invsaw/init.lua
@@ -0,0 +1,9 @@
+invsaw = fmod.create()
+
+invsaw.users = {}
+
+invsaw.dofile("privs")
+invsaw.dofile("api")
+invsaw.dofile("inventory")
+invsaw.dofile("formspec")
+invsaw.dofile("unified_inventory")
diff --git a/mods/moreblocks/invsaw/inventory.lua b/mods/moreblocks/invsaw/inventory.lua
new file mode 100644
index 00000000..058ed612
--- /dev/null
+++ b/mods/moreblocks/invsaw/inventory.lua
@@ -0,0 +1,78 @@
+local station = stairsplus.api.station
+
+function invsaw.initialize_inventory(player)
+ local meta = player:get_meta()
+ local inv = player:get_inventory()
+
+ station.initialize_metadata(meta, inv, { "legacy" })
+ station.initialize_inventory(inv, { "legacy" })
+end
+
+function invsaw.drop_inventory(player)
+ local pos = player:get_pos()
+ local inv = player:get_inventory()
+ for _, listname in ipairs({ "stairsplus:input", "stairsplus:micro", "stairsplus:recycle" }) do
+ for i = 1, inv:get_size(listname) do
+ local item = inv:get_stack(listname, i)
+ if not item:is_empty() then
+ minetest.add_item(pos, item)
+ end
+ end
+ inv:set_size(listname, 0)
+ end
+ inv:set_size("stairsplus:output", 0)
+end
+
+local function is_stairsplus_inventory(listname)
+ return (
+ listname == "stairsplus:input"
+ or listname == "stairsplus:micro"
+ or listname == "stairsplus:recycle"
+ or listname == "stairsplus:output"
+ )
+end
+
+minetest.register_allow_player_inventory_action(function(player, action, inv, info)
+ local meta = player:get_meta()
+ if action == "move" then
+ local from_is_stairsplus = is_stairsplus_inventory(info.from_list)
+ local to_is_stairsplus = is_stairsplus_inventory(info.to_list)
+ if from_is_stairsplus and to_is_stairsplus then
+ return station.allow_inventory_move(
+ meta,
+ inv,
+ info.from_list,
+ info.from_index,
+ info.to_list,
+ info.to_index,
+ info.count,
+ player
+ )
+ elseif to_is_stairsplus then
+ local stack = inv:get_stack(info.from_list, info.from_index)
+ return station.allow_inventory_put(meta, inv, info.to_list, info.to_index, stack, player)
+ end
+ elseif action == "put" and is_stairsplus_inventory(info.listname) then
+ return station.allow_inventory_put(meta, inv, info.listname, info.index, info.stack, player)
+ end
+end)
+
+minetest.register_on_player_inventory_action(function(player, action, inv, info)
+ local meta = player:get_meta()
+ if action == "move" and is_stairsplus_inventory(info.from_list) and not is_stairsplus_inventory(info.to_list) then
+ local stack = inv:get_stack(info.to_list, info.to_index)
+ stack:set_count(info.count)
+ station.on_inventory_take(meta, inv, info.from_list, info.from_index, stack, player)
+ elseif
+ action == "move"
+ and not is_stairsplus_inventory(info.from_list)
+ and is_stairsplus_inventory(info.to_list)
+ then
+ local stack = inv:get_stack(info.from_list, info.from_index)
+ station.on_inventory_put(meta, inv, info.to_list, info.to_index, stack, player)
+ elseif action == "put" and is_stairsplus_inventory(info.listname) then
+ station.on_inventory_put(meta, inv, info.listname, info.index, info.stack, player)
+ elseif action == "take" and is_stairsplus_inventory(info.listname) then
+ station.on_inventory_take(meta, inv, info.listname, info.index, info.stack, player)
+ end
+end)
diff --git a/mods/moreblocks/invsaw/locale/default.txt b/mods/moreblocks/invsaw/locale/default.txt
new file mode 100644
index 00000000..3fae4429
--- /dev/null
+++ b/mods/moreblocks/invsaw/locale/default.txt
@@ -0,0 +1,3 @@
+# textdomain: invsaw
+
+Circular Saw=
diff --git a/mods/moreblocks/invsaw/locale/invsaw.ru.tr b/mods/moreblocks/invsaw/locale/invsaw.ru.tr
new file mode 100644
index 00000000..29e3aace
--- /dev/null
+++ b/mods/moreblocks/invsaw/locale/invsaw.ru.tr
@@ -0,0 +1,3 @@
+# textdomain: invsaw
+
+Circular Saw=Циркулярная пила
diff --git a/mods/moreblocks/invsaw/mod.conf b/mods/moreblocks/invsaw/mod.conf
new file mode 100644
index 00000000..d29912ad
--- /dev/null
+++ b/mods/moreblocks/invsaw/mod.conf
@@ -0,0 +1,9 @@
+name = invsaw
+title = inventory saw
+description = "circular saw in the unified inventory"
+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, stairsplus, unified_inventory
diff --git a/mods/moreblocks/invsaw/privs.lua b/mods/moreblocks/invsaw/privs.lua
new file mode 100644
index 00000000..c4181f5c
--- /dev/null
+++ b/mods/moreblocks/invsaw/privs.lua
@@ -0,0 +1,48 @@
+-- luacheck: globals minetest
+
+local creative_priv = invsaw.settings.creative_priv
+local priv = invsaw.settings.priv
+
+local function on_priv_change(name)
+ local player = minetest.get_player_by_name(name)
+ if player then
+ invsaw.check_use_status(player)
+ end
+end
+
+local function override_on_priv_change(old)
+ return function(name, cause)
+ on_priv_change(name)
+ if old then
+ old(name, cause)
+ end
+ end
+end
+
+if minetest.registered_privileges[priv] then
+ local def = minetest.registered_privileges[priv]
+ def.on_grant = override_on_priv_change(def.on_grant)
+ def.on_revoke = override_on_priv_change(def.on_revoke)
+else
+ minetest.register_privilege(priv, {
+ description = "Allow use of the circular saw in inventory",
+ give_to_singleplayer = true,
+ give_to_admin = false,
+ on_grant = on_priv_change,
+ on_revoke = on_priv_change,
+ })
+end
+
+if minetest.registered_privileges[creative_priv] then
+ local def = minetest.registered_privileges[creative_priv]
+ def.on_grant = override_on_priv_change(def.on_grant)
+ def.on_revoke = override_on_priv_change(def.on_revoke)
+else
+ minetest.register_privilege(creative_priv, {
+ description = "Allow use of the inventory saw creatively",
+ give_to_singleplayer = true,
+ give_to_admin = false,
+ on_grant = on_priv_change,
+ on_revoke = on_priv_change,
+ })
+end
diff --git a/mods/moreblocks/invsaw/settingtypes.txt b/mods/moreblocks/invsaw/settingtypes.txt
new file mode 100644
index 00000000..0d57dab5
--- /dev/null
+++ b/mods/moreblocks/invsaw/settingtypes.txt
@@ -0,0 +1,8 @@
+# Will be created if it doesn't already exist
+invsaw.priv (Priv to use the inventory saw) string interact
+
+# Will be created if it doesn't already exist
+invsaw.creative_priv (Priv to use the inventory saw w/out a saw item) string creative
+
+# The item that a normal player has to have to use the saw in inventory
+invsaw.saw_item (Saw item) string stairsplus:circular_saw
diff --git a/mods/moreblocks/invsaw/unified_inventory.lua b/mods/moreblocks/invsaw/unified_inventory.lua
new file mode 100644
index 00000000..b9b22a96
--- /dev/null
+++ b/mods/moreblocks/invsaw/unified_inventory.lua
@@ -0,0 +1,10 @@
+local ui = unified_inventory
+
+ui.register_button("saw", {
+ type = "image",
+ image = "stairsplus_saw_button.png",
+ tooltip = invsaw.S("Circular Saw"),
+ condition = function(player)
+ return invsaw.allow_use_saw(player)
+ end,
+})
diff --git a/mods/moreblocks/modpack.conf b/mods/moreblocks/modpack.conf
new file mode 100644
index 00000000..6e29181d
--- /dev/null
+++ b/mods/moreblocks/modpack.conf
@@ -0,0 +1,11 @@
+name = moreblocks
+title = moreblocks and stairsplus
+description = adds various blocks to the game
+website = https://content.minetest.net/packages/rheo/moreblocks/.
+author = rheo
+license = LGPL-3.0-or-later
+media_license = CC-BY-SA-4.0
+version = 2024-12-23
+min_minetest_version = 5.7.0
+supported_games = *
+release = 29489
diff --git a/mods/moreblocks/moreblocks/.luacheckrc b/mods/moreblocks/moreblocks/.luacheckrc
new file mode 100644
index 00000000..527bcbb1
--- /dev/null
+++ b/mods/moreblocks/moreblocks/.luacheckrc
@@ -0,0 +1,659 @@
+std = "lua51+luajit+minetest+moreblocks"
+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.moreblocks = {
+ globals = {
+ "moreblocks",
+ },
+ read_globals = {
+ "default",
+ "fmod",
+ "futil",
+ "stairs",
+ "stairsplus",
+ },
+}
diff --git a/mods/moreblocks/moreblocks/API.md b/mods/moreblocks/moreblocks/API.md
new file mode 100644
index 00000000..a5bd8559
--- /dev/null
+++ b/mods/moreblocks/moreblocks/API.md
@@ -0,0 +1,9 @@
+
+* `moreblocks.api.register_all_faces(itemstring, base, [redef])`
+ Register an "All Faces" variant of a tree.
+
+* `moreblocks.api.register_no_faces(itemstring, base, [redef])`
+ Register a "No Faces" variant of a tree.
+
+* `moreblocks.api.register_trap(itemstring, base, [redef])`
+ Register a "Trap" variant of a node. A trap variant is a non-solid node that looks like the original.
diff --git a/mods/moreblocks/moreblocks/aliases.lua b/mods/moreblocks/moreblocks/aliases.lua
new file mode 100644
index 00000000..d75e3c7c
--- /dev/null
+++ b/mods/moreblocks/moreblocks/aliases.lua
@@ -0,0 +1,135 @@
+local cm = moreblocks.resources.craft_materials
+
+-- More Blocks aliases:
+minetest.register_alias("sweeper", "moreblocks:sweeper")
+
+if cm.stick then
+ minetest.register_alias("jungle_stick", cm.stick)
+ minetest.register_alias("moreblocks:jungle_stick", cm.stick)
+end
+
+-- Old block/item replacement:
+if cm.mossycobble then
+ minetest.register_alias("moreblocks:oerkkiblock", cm.mossycobble)
+end
+
+if cm.screwdriver then
+ minetest.register_alias("moreblocks:screwdriver", cm.screwdriver)
+end
+
+-- Node and item renaming:
+if cm.stone_brick then
+ minetest.register_alias("moreblocks:stone_bricks", cm.stone_brick)
+ minetest.register_alias("moreblocks:stonebrick", cm.stone_brick)
+end
+
+if cm.jungle_wood then
+ minetest.register_alias("moreblocks:junglewood", cm.jungle_wood)
+ minetest.register_alias("moreblocks:jungle_wood", cm.jungle_wood)
+end
+
+if cm.fence_jungle_wood then
+ minetest.register_alias("moreblocks:fence_junglewood", cm.fence_jungle_wood)
+ minetest.register_alias("moreblocks:fence_jungle_wood", cm.fence_jungle_wood)
+end
+
+minetest.register_alias("moreblocks:horizontaltree", "moreblocks:horizontal_tree")
+minetest.register_alias("moreblocks:horizontaljungletree", "moreblocks:horizontal_jungle_tree")
+minetest.register_alias("moreblocks:stonesquare", "moreblocks:stone_tile")
+minetest.register_alias("moreblocks:circlestonebrick", "moreblocks:circle_stone_bricks")
+minetest.register_alias("moreblocks:ironstonebrick", "moreblocks:iron_stone_bricks")
+minetest.register_alias("moreblocks:coalstone", "moreblocks:coal_stone")
+minetest.register_alias("moreblocks:ironstone", "moreblocks:iron_stone")
+minetest.register_alias("moreblocks:woodtile", "moreblocks:wood_tile")
+minetest.register_alias("moreblocks:woodtile_full", "moreblocks:wood_tile_full")
+minetest.register_alias("moreblocks:woodtile_centered", "moreblocks:wood_tile_centered")
+minetest.register_alias("moreblocks:woodtile_up", "moreblocks:wood_tile_offset")
+minetest.register_alias("moreblocks:wood_tile_up", "moreblocks:wood_tile_offset")
+minetest.register_alias("moreblocks:woodtile_down", "moreblocks:wood_tile_down")
+minetest.register_alias("moreblocks:woodtile_left", "moreblocks:wood_tile_left")
+minetest.register_alias("moreblocks:woodtile_right", "moreblocks:wood_tile_right")
+minetest.register_alias("moreblocks:coalglass", "moreblocks:coal_glass")
+minetest.register_alias("moreblocks:ironglass", "moreblocks:iron_glass")
+minetest.register_alias("moreblocks:glowglass", "moreblocks:glow_glass")
+minetest.register_alias("moreblocks:superglowglass", "moreblocks:super_glow_glass")
+minetest.register_alias("moreblocks:trapglass", "moreblocks:trap_glass")
+minetest.register_alias("moreblocks:trapstone", "moreblocks:trap_stone")
+minetest.register_alias("moreblocks:cactuschecker", "moreblocks:cactus_checker")
+minetest.register_alias("moreblocks:coalchecker", "moreblocks:coal_checker")
+minetest.register_alias("moreblocks:ironchecker", "moreblocks:iron_checker")
+minetest.register_alias("moreblocks:cactusbrick", "moreblocks:cactus_brick")
+minetest.register_alias("moreblocks:cleanglass", "moreblocks:clean_glass")
+minetest.register_alias("moreblocks:emptybookshelf", "moreblocks:empty_bookshelf")
+minetest.register_alias("moreblocks:junglestick", "moreblocks:jungle_stick")
+minetest.register_alias("moreblocks:splitstonesquare", "moreblocks:split_stone_tile")
+minetest.register_alias("moreblocks:allfacestree", "moreblocks:all_faces_tree")
+minetest.register_alias("moreblocks:empty_bookshelf", "moreblocks:empty_shelf")
+minetest.register_alias("moreblocks:split_stone_tile_alt", "moreblocks:checker_stone_tile")
+
+if moreblocks.has.stairsplus and cm.jungle_wood then
+ stairsplus.api.register_alias_all("moreblocks:jungle_wood", cm.jungle_wood)
+end
+
+if moreblocks.has.stairsplus then
+ stairsplus.api.register_alias_all("moreblocks:split_stone_tile_alt", "moreblocks:checker_stone_tile")
+end
+
+minetest.register_lbm({
+ name = "moreblocks:reduce_wood_tile_redundancy",
+ nodenames = {
+ "moreblocks:wood_tile_left",
+ "moreblocks:wood_tile_down",
+ "moreblocks:wood_tile_right",
+ "moreblocks:wood_tile_flipped",
+ },
+ action = function(pos, node)
+ if node.name == "moreblocks:wood_tile_left" then
+ minetest.set_node(pos, { name = "moreblocks:wood_tile_offset", param2 = 1 })
+ elseif node.name == "moreblocks:wood_tile_down" then
+ minetest.set_node(pos, { name = "moreblocks:wood_tile_offset", param2 = 2 })
+ elseif node.name == "moreblocks:wood_tile_right" then
+ minetest.set_node(pos, { name = "moreblocks:wood_tile_offset", param2 = 3 })
+ else
+ -- wood_tile_flipped
+ minetest.set_node(pos, { name = "moreblocks:wood_tile", param2 = 1 })
+ end
+ moreblocks.log("action", "LBM replaced %s at %s", node.name, minetest.pos_to_string(pos))
+ end,
+})
+
+local horizontal_tree_convert_facedir = { 7, 12, 9, 18 }
+if cm.tree then
+ minetest.register_lbm({
+ name = "moreblocks:reduce_horizontal_tree_redundancy",
+ nodenames = {
+ "moreblocks:horizontal_tree",
+ },
+ action = function(pos, node)
+ node.name = cm.tree
+ node.param2 = node.param2 < 3 and node.param2 or 0
+ minetest.set_node(pos, {
+ name = node.name,
+ param2 = horizontal_tree_convert_facedir[node.param2 + 1],
+ })
+ moreblocks.log("action", "LBM replaced %s at %s", node.name, minetest.pos_to_string(pos))
+ end,
+ })
+end
+
+if cm.jungle_tree then
+ minetest.register_lbm({
+ name = "moreblocks:reduce_horizontal_jungle_tree_redundancy",
+ nodenames = {
+ "moreblocks:horizontal_jungle_tree",
+ },
+ action = function(pos, node)
+ node.name = cm.jungle_tree
+ node.param2 = node.param2 < 3 and node.param2 or 0
+ minetest.set_node(pos, {
+ name = node.name,
+ param2 = horizontal_tree_convert_facedir[node.param2 + 1],
+ })
+ moreblocks.log("action", "LBM replaced %s at %s", node.name, minetest.pos_to_string(pos))
+ end,
+ })
+end
diff --git a/mods/moreblocks/moreblocks/api/all_faces.lua b/mods/moreblocks/moreblocks/api/all_faces.lua
new file mode 100644
index 00000000..225168ec
--- /dev/null
+++ b/mods/moreblocks/moreblocks/api/all_faces.lua
@@ -0,0 +1,28 @@
+local S = moreblocks.S
+
+function moreblocks.api.register_all_faces(itemstring, base, redef)
+ local def = table.copy(minetest.registered_nodes[base])
+
+ def.tiles = { def.tiles[1] }
+ def.description = S("All-faces @1", def.description)
+
+ if def.short_description then
+ def.short_description = S("All-faces @1", def.short_description)
+ end
+
+ redef = redef or {}
+ for k, v in pairs(redef) do
+ def[k] = v
+ end
+
+ minetest.register_node(itemstring, def)
+
+ minetest.register_craft({
+ output = itemstring .. " 8",
+ recipe = {
+ { base, base, base },
+ { base, "", base },
+ { base, base, base },
+ },
+ })
+end
diff --git a/mods/moreblocks/moreblocks/api/init.lua b/mods/moreblocks/moreblocks/api/init.lua
new file mode 100644
index 00000000..6e4c3354
--- /dev/null
+++ b/mods/moreblocks/moreblocks/api/init.lua
@@ -0,0 +1,5 @@
+moreblocks.api = {}
+
+moreblocks.dofile("api", "all_faces")
+moreblocks.dofile("api", "no_faces")
+moreblocks.dofile("api", "trap")
diff --git a/mods/moreblocks/moreblocks/api/no_faces.lua b/mods/moreblocks/moreblocks/api/no_faces.lua
new file mode 100644
index 00000000..f2950a89
--- /dev/null
+++ b/mods/moreblocks/moreblocks/api/no_faces.lua
@@ -0,0 +1,28 @@
+local S = moreblocks.S
+
+function moreblocks.api.register_no_faces(itemstring, base, redef)
+ local def = table.copy(minetest.registered_nodes[base])
+
+ def.tiles = { def.tiles[3] }
+ def.description = S("No-faces @1", def.description)
+
+ if def.short_description then
+ def.short_description = S("No-faces @1", def.short_description)
+ end
+
+ redef = redef or {}
+ for k, v in pairs(redef) do
+ def[k] = v
+ end
+
+ minetest.register_node(itemstring, def)
+
+ minetest.register_craft({
+ output = itemstring .. " 9",
+ recipe = {
+ { base, base, base },
+ { base, base, base },
+ { base, base, base },
+ },
+ })
+end
diff --git a/mods/moreblocks/moreblocks/api/trap.lua b/mods/moreblocks/moreblocks/api/trap.lua
new file mode 100644
index 00000000..96e96b91
--- /dev/null
+++ b/mods/moreblocks/moreblocks/api/trap.lua
@@ -0,0 +1,43 @@
+local S = moreblocks.S
+local cm = moreblocks.resources.craft_materials
+
+local outline_trap_nodes = moreblocks.settings.outline_trap_nodes
+
+function moreblocks.api.register_trap(itemstring, base, redef)
+ local def = table.copy(minetest.registered_nodes[base])
+
+ def.description = S("Trap @1", def.description)
+
+ if def.short_description then
+ def.short_description = S("Trap @1", def.short_description)
+ end
+
+ if outline_trap_nodes then
+ for i, tile in ipairs(def.tiles) do
+ def.tiles[i] = tile .. "^moreblocks_trap_box.png"
+ end
+ end
+
+ if def.drawtype ~= "glasslike_framed_optional" then
+ def.drawtype = "glasslike_framed"
+ end
+
+ def.walkable = false
+ def.paramtype = "light"
+ def.is_ground_content = false
+
+ redef = redef or {}
+ for k, v in pairs(redef) do
+ def[k] = v
+ end
+
+ minetest.register_node(itemstring, def)
+
+ if cm.trap_material then
+ minetest.register_craft({
+ output = itemstring,
+ type = "shapeless",
+ recipe = { cm.trap_material, base },
+ })
+ end
+end
diff --git a/mods/moreblocks/moreblocks/crafting.lua b/mods/moreblocks/moreblocks/crafting.lua
new file mode 100644
index 00000000..d0a43992
--- /dev/null
+++ b/mods/moreblocks/moreblocks/crafting.lua
@@ -0,0 +1,576 @@
+local cm = moreblocks.resources.craft_materials
+
+if cm.stick and cm.dry_shrub then
+ minetest.register_craft({
+ output = cm.stick,
+ recipe = { { cm.dry_shrub } },
+ })
+end
+
+if cm.stick then
+ minetest.register_craft({
+ output = cm.stick,
+ recipe = { { "group:sapling" } },
+ })
+end
+
+if cm.stick and cm.wood then
+ minetest.register_craft({
+ output = cm.wood,
+ recipe = {
+ { cm.stick, cm.stick },
+ { cm.stick, cm.stick },
+ },
+ })
+end
+
+if cm.dirt_with_grass and cm.jungle_grass and cm.dirt then
+ minetest.register_craft({
+ output = cm.dirt_with_grass,
+ type = "shapeless",
+ recipe = { cm.jungle_grass, cm.dirt },
+ })
+end
+
+if cm.mossy_cobble and cm.jungle_grass and cm.cobble then
+ minetest.register_craft({
+ output = cm.mossy_cobble,
+ type = "shapeless",
+ recipe = { cm.jungle_grass, cm.cobble },
+ })
+end
+
+minetest.register_craft({
+ output = "moreblocks:wood_tile 9",
+ recipe = {
+ { "group:wood", "group:wood", "group:wood" },
+ { "group:wood", "group:wood", "group:wood" },
+ { "group:wood", "group:wood", "group:wood" },
+ },
+})
+
+-- This must be registered after `moreblocks:wood_tile` to avoid recipe conflicts,
+-- since `moreblocks:wood_tile` is part of `group:wood`
+minetest.register_craft({
+ output = "moreblocks:wood_tile_center 9",
+ recipe = {
+ { "group:wood", "group:wood", "group:wood" },
+ { "group:wood", "moreblocks:wood_tile", "group:wood" },
+ { "group:wood", "group:wood", "group:wood" },
+ },
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = "moreblocks:wood_tile",
+ recipe = { "moreblocks:wood_tile_flipped" },
+})
+
+minetest.register_craft({
+ output = "moreblocks:wood_tile_full 4",
+ recipe = {
+ { "moreblocks:wood_tile", "moreblocks:wood_tile" },
+ { "moreblocks:wood_tile", "moreblocks:wood_tile" },
+ },
+})
+
+if cm.stick then
+ minetest.register_craft({
+ output = "moreblocks:wood_tile_offset",
+ recipe = {
+ { cm.stick },
+ { "moreblocks:wood_tile_center" },
+ },
+ })
+end
+
+minetest.register_craft({
+ type = "shapeless",
+ output = "moreblocks:wood_tile_offset",
+ recipe = { "moreblocks:wood_tile_down" },
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = "moreblocks:wood_tile_offset",
+ recipe = { "moreblocks:wood_tile_left" },
+})
+
+minetest.register_craft({
+ type = "shapeless",
+ output = "moreblocks:wood_tile_offset",
+ recipe = { "moreblocks:wood_tile_right" },
+})
+
+if cm.stone and cm.coal_lump then
+ minetest.register_craft({
+ output = "moreblocks:circle_stone_bricks 5",
+ recipe = {
+ { "", cm.stone, "" },
+ { cm.stone, cm.coal_lump, cm.stone },
+ { "", cm.stone, "" },
+ },
+ })
+end
+
+if cm.jungle_grass and cm.stick then
+ minetest.register_craft({
+ output = "moreblocks:sweeper 4",
+ recipe = {
+ { cm.jungle_grass },
+ { cm.stick },
+ },
+ })
+end
+
+if cm.cobble and cm.stone then
+ minetest.register_craft({
+ output = "moreblocks:stone_tile 9",
+ recipe = {
+ { cm.cobble, cm.cobble, cm.cobble },
+ { cm.cobble, cm.stone, cm.cobble },
+ { cm.cobble, cm.cobble, cm.cobble },
+ },
+ })
+end
+
+minetest.register_craft({
+ output = "moreblocks:split_stone_tile",
+ recipe = {
+ { "moreblocks:stone_tile" },
+ },
+})
+
+minetest.register_craft({
+ output = "moreblocks:checker_stone_tile",
+ recipe = {
+ { "moreblocks:split_stone_tile" },
+ },
+})
+
+-- When approaching the below craft, loop back to cobblestone, which can then be used to craft stone tiles again
+if cm.cobble then
+ minetest.register_craft({
+ output = cm.cobble,
+ recipe = {
+ { "moreblocks:checker_stone_tile" },
+ },
+ })
+end
+
+if cm.stone and cm.brick then
+ minetest.register_craft({
+ output = "moreblocks:grey_bricks 2",
+ type = "shapeless",
+ recipe = { cm.stone, cm.brick },
+ })
+end
+
+if cm.stone_brick and cm.brick then
+ minetest.register_craft({
+ output = "moreblocks:grey_bricks 2",
+ type = "shapeless",
+ recipe = { cm.stone_brick, cm.brick },
+ })
+end
+
+if cm.bookshelf and cm.book then
+ minetest.register_craft({
+ output = "moreblocks:empty_shelf",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", cm.bookshelf },
+ replacements = { { cm.bookshelf, cm.book .. " 3" } },
+ -- When obtaining an empty shelf, return the books used in it as well
+ })
+end
+
+if cm.vessels_shelf and cm.glass_bottle then
+ minetest.register_craft({
+ output = "moreblocks:empty_shelf",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", cm.vessels_shelf },
+ replacements = { { cm.vessels_shelf, cm.glass_bottle .. " 3" } },
+ })
+end
+
+if cm.book and cm.bookshelf then
+ minetest.register_craft({
+ type = "shapeless",
+ output = cm.bookshelf,
+ recipe = { "moreblocks:empty_shelf", cm.book, cm.book, cm.book },
+ })
+end
+
+minetest.register_craft({
+ output = "moreblocks:empty_shelf",
+ recipe = {
+ { "group:wood", "group:wood", "group:wood" },
+ { "", "", "" },
+ { "group:wood", "group:wood", "group:wood" },
+ },
+})
+
+minetest.register_craft({
+ output = "moreblocks:coal_stone_bricks 4",
+ recipe = {
+ { "moreblocks:coal_stone", "moreblocks:coal_stone" },
+ { "moreblocks:coal_stone", "moreblocks:coal_stone" },
+ },
+})
+
+minetest.register_craft({
+ output = "moreblocks:iron_stone_bricks 4",
+ recipe = {
+ { "moreblocks:iron_stone", "moreblocks:iron_stone" },
+ { "moreblocks:iron_stone", "moreblocks:iron_stone" },
+ },
+})
+
+minetest.register_craft({
+ output = "moreblocks:plankstone 4",
+ recipe = {
+ { "group:stone", "group:wood" },
+ { "group:wood", "group:stone" },
+ },
+})
+
+minetest.register_craft({
+ output = "moreblocks:plankstone 4",
+ recipe = {
+ { "group:wood", "group:stone" },
+ { "group:stone", "group:wood" },
+ },
+})
+
+if cm.coal_lump and cm.stone then
+ minetest.register_craft({
+ output = "moreblocks:coal_checker 4",
+ recipe = {
+ { cm.stone, cm.coal_lump },
+ { cm.coal_lump, cm.stone },
+ },
+ })
+
+ minetest.register_craft({
+ output = "moreblocks:coal_checker 4",
+ recipe = {
+ { cm.coal_lump, cm.stone },
+ { cm.stone, cm.coal_lump },
+ },
+ })
+end
+
+if cm.steel_ingot and cm.stone then
+ minetest.register_craft({
+ output = "moreblocks:iron_checker 4",
+ recipe = {
+ { cm.steel_ingot, cm.stone },
+ { cm.stone, cm.steel_ingot },
+ },
+ })
+
+ minetest.register_craft({
+ output = "moreblocks:iron_checker 4",
+ recipe = {
+ { cm.stone, cm.steel_ingot },
+ { cm.steel_ingot, cm.stone },
+ },
+ })
+end
+
+if cm.chest and cm.chest_locked then
+ if cm.steel_ingot then
+ minetest.register_craft({
+ output = cm.chest_locked,
+ type = "shapeless",
+ recipe = { cm.steel_ingot, cm.chest },
+ })
+ end
+
+ if cm.copper_ingot then
+ minetest.register_craft({
+ output = cm.chest_locked,
+ type = "shapeless",
+ recipe = { cm.copper_ingot, cm.chest },
+ })
+ end
+
+ if cm.bronze_ingot then
+ minetest.register_craft({
+ output = cm.chest_locked,
+ type = "shapeless",
+ recipe = { cm.bronze_ingot, cm.chest },
+ })
+ end
+
+ if cm.gold_ingot then
+ minetest.register_craft({
+ output = cm.chest_locked,
+ type = "shapeless",
+ recipe = { cm.gold_ingot, cm.chest },
+ })
+ end
+end
+
+if cm.glass and cm.steel_ingot then
+ minetest.register_craft({
+ output = "moreblocks:iron_glass",
+ type = "shapeless",
+ recipe = { cm.steel_ingot, cm.glass },
+ })
+
+ minetest.register_craft({
+ output = cm.glass,
+ type = "shapeless",
+ recipe = { cm.steel_ingot, "moreblocks:coal_glass" },
+ })
+end
+
+if cm.glass and cm.coal_lump then
+ minetest.register_craft({
+ output = cm.glass,
+ type = "shapeless",
+ recipe = { cm.coal_lump, "moreblocks:iron_glass" },
+ })
+
+ minetest.register_craft({
+ output = "moreblocks:coal_glass",
+ type = "shapeless",
+ recipe = { cm.coal_lump, cm.glass },
+ })
+end
+
+if cm.glass then
+ minetest.register_craft({
+ output = "moreblocks:clean_glass",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", cm.glass },
+ })
+end
+
+minetest.register_craft({
+ output = "moreblocks:trap_clean_glass",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", "moreblocks:trap_glass" },
+})
+
+if cm.glass and cm.torch then
+ minetest.register_craft({
+ output = "moreblocks:glow_glass",
+ type = "shapeless",
+ recipe = { cm.torch, cm.glass },
+ })
+end
+
+if cm.torch then
+ minetest.register_craft({
+ output = "moreblocks:clean_glow_glass",
+ type = "shapeless",
+ recipe = { cm.torch, "moreblocks:clean_glass" },
+ })
+end
+
+minetest.register_craft({
+ output = "moreblocks:clean_glow_glass",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", "moreblocks:glow_glass" },
+})
+
+minetest.register_craft({
+ output = "moreblocks:trap_clean_glow_glass",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", "moreblocks:trap_glow_glass" },
+})
+
+if cm.torch and cm.glass then
+ minetest.register_craft({
+ output = "moreblocks:super_glow_glass",
+ type = "shapeless",
+ recipe = { cm.torch, cm.torch, cm.glass },
+ })
+end
+
+if cm.torch then
+ minetest.register_craft({
+ output = "moreblocks:super_glow_glass",
+ type = "shapeless",
+ recipe = { cm.torch, "moreblocks:glow_glass" },
+ })
+
+ minetest.register_craft({
+ output = "moreblocks:clean_super_glow_glass",
+ type = "shapeless",
+ recipe = { cm.torch, cm.torch, "moreblocks:clean_glass" },
+ })
+
+ minetest.register_craft({
+ output = "moreblocks:clean_super_glow_glass",
+ type = "shapeless",
+ recipe = { cm.torch, "moreblocks:clean_glow_glass" },
+ })
+end
+
+minetest.register_craft({
+ output = "moreblocks:clean_super_glow_glass",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", "moreblocks:super_glow_glass" },
+})
+
+minetest.register_craft({
+ output = "moreblocks:trap_clean_super_glow_glass",
+ type = "shapeless",
+ recipe = { "moreblocks:sweeper", "moreblocks:trap_super_glow_glass" },
+})
+
+if cm.coal_lump and cm.stone then
+ minetest.register_craft({
+ output = "moreblocks:coal_stone",
+ type = "shapeless",
+ recipe = { cm.coal_lump, cm.stone },
+ })
+
+ minetest.register_craft({
+ output = cm.stone,
+ type = "shapeless",
+ recipe = { cm.coal_lump, "moreblocks:iron_stone" },
+ })
+end
+
+if cm.stone and cm.steel_ingot then
+ minetest.register_craft({
+ output = cm.stone,
+ type = "shapeless",
+ recipe = { cm.steel_ingot, "moreblocks:coal_stone" },
+ })
+
+ minetest.register_craft({
+ output = "moreblocks:iron_stone",
+ type = "shapeless",
+ recipe = { cm.steel_ingot, cm.stone },
+ })
+end
+
+if cm.cactus and cm.brick then
+ minetest.register_craft({
+ output = "moreblocks:cactus_brick",
+ type = "shapeless",
+ recipe = { cm.cactus, cm.brick },
+ })
+end
+
+if cm.cactus and cm.stone then
+ minetest.register_craft({
+ output = "moreblocks:cactus_checker 4",
+ recipe = {
+ { cm.cactus, cm.stone },
+ { cm.stone, cm.cactus },
+ },
+ })
+
+ minetest.register_craft({
+ output = "moreblocks:cactus_checker 4",
+ recipe = {
+ { cm.stone, cm.cactus },
+ { cm.cactus, cm.stone },
+ },
+ })
+end
+
+if cm.jungle_grass then
+ minetest.register_craft({
+ output = "moreblocks:rope 3",
+ recipe = {
+ { cm.jungle_grass },
+ { cm.jungle_grass },
+ { cm.jungle_grass },
+ },
+ })
+end
+
+if cm.dirt then
+ minetest.register_craft({
+ output = "moreblocks:dirt_compressed",
+ recipe = {
+ { cm.dirt, cm.dirt, cm.dirt },
+ { cm.dirt, cm.dirt, cm.dirt },
+ { cm.dirt, cm.dirt, cm.dirt },
+ },
+ })
+
+ minetest.register_craft({
+ output = cm.dirt .. " 9",
+ recipe = { { "moreblocks:dirt_compressed" } },
+ })
+end
+
+if cm.cobble then
+ minetest.register_craft({
+ output = "moreblocks:cobble_compressed",
+ recipe = {
+ { cm.cobble, cm.cobble, cm.cobble },
+ { cm.cobble, cm.cobble, cm.cobble },
+ { cm.cobble, cm.cobble, cm.cobble },
+ },
+ })
+
+ minetest.register_craft({
+ output = cm.cobble .. " 9",
+ recipe = {
+ { "moreblocks:cobble_compressed" },
+ },
+ })
+end
+
+if cm.desert_cobble then
+ minetest.register_craft({
+ output = "moreblocks:desert_cobble_compressed",
+ recipe = {
+ { cm.desert_cobble, cm.desert_cobble, cm.desert_cobble },
+ { cm.desert_cobble, cm.desert_cobble, cm.desert_cobble },
+ { cm.desert_cobble, cm.desert_cobble, cm.desert_cobble },
+ },
+ })
+
+ minetest.register_craft({
+ output = cm.desert_cobble .. " 9",
+ recipe = {
+ { "moreblocks:desert_cobble_compressed" },
+ },
+ })
+end
+
+if cm.pine_tree then
+ minetest.register_craft({
+ type = "cooking",
+ output = "moreblocks:tar",
+ recipe = cm.pine_tree,
+ })
+end
+
+if cm.copper_block and cm.bucket_empty then
+ minetest.register_craft({
+ type = "shapeless",
+ output = "moreblocks:copperpatina",
+ recipe = { "group:water_bucket", cm.copper_block },
+ replacements = {
+ { "group:water_bucket", cm.bucket_empty },
+ },
+ })
+end
+
+if cm.copper_block then
+ minetest.register_craft({
+ type = "shapeless",
+ output = "moreblocks:copperpatina",
+ recipe = { "group:water", cm.copper_block },
+ })
+end
+
+if cm.copper_ingot then
+ minetest.register_craft({
+ output = cm.copper_ingot .. " 9",
+ recipe = {
+ { "moreblocks:copperpatina" },
+ },
+ })
+end
diff --git a/mods/moreblocks/moreblocks/init.lua b/mods/moreblocks/moreblocks/init.lua
new file mode 100644
index 00000000..a5d41771
--- /dev/null
+++ b/mods/moreblocks/moreblocks/init.lua
@@ -0,0 +1,9 @@
+moreblocks = fmod.create()
+
+moreblocks.dofile("resources", "init")
+moreblocks.dofile("api", "init")
+
+moreblocks.dofile("items")
+moreblocks.dofile("nodes")
+moreblocks.dofile("crafting")
+moreblocks.dofile("aliases")
diff --git a/mods/moreblocks/moreblocks/items.lua b/mods/moreblocks/moreblocks/items.lua
new file mode 100644
index 00000000..b959af98
--- /dev/null
+++ b/mods/moreblocks/moreblocks/items.lua
@@ -0,0 +1,7 @@
+-- Items
+local S = moreblocks.S
+
+minetest.register_craftitem("moreblocks:sweeper", {
+ description = S("Sweeper"),
+ inventory_image = "moreblocks_sweeper.png",
+})
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.de.tr b/mods/moreblocks/moreblocks/locale/moreblocks.de.tr
new file mode 100644
index 00000000..d433bb3a
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.de.tr
@@ -0,0 +1,50 @@
+# textdomain: moreblocks
+
+# German translation for More Blocks.
+# Copyright © 2011-2020 Hugo Locurcio and contributors
+# This file is distributed under the same license as the More Blocks package.
+# Xanthin, 2014.
+# CodeXP , 2018.
+
+#: init.lua
+
+[moreblocks] loaded.=[moreblocks] geladen.
+
+#: nodes.lua
+
+All-faces @1=allseitiger @1
+No-faces @1=
+Cactus Brick=Kaktusziegel
+Cactus Checker=Kaktus-Mosaik
+Centered Wooden Tile=Holzfliese mittig
+Checker Stone Tile=Stein-Mosaik
+Circle Stone Bricks=Kreissteinziegel
+Clean Glass=Klares Glas
+Coal Checker=Kohlen-Mosaik
+Coal Glass=Kohleglas
+Clean Super Glow Glass=
+Trap @1=@1falle
+Clean Glow Glass=
+Coal Stone=Kohlestein
+Coal Stone Bricks=Kohlesteinziegel
+Compressed Cobblestone=Gepresster Kopfsteinpflaster
+Compressed Desert Cobblestone=
+Compressed Dirt=Gepresste Erde
+Copper Patina Block=Kupfer Edelrostblock
+Empty Shelf=Leeres Regal
+Full Wooden Tile=Vollholzfliese
+Glow Glass=Leuchtglas
+Iron Checker=Metall-Mosaik
+Iron Glass=metallisiertes Glas
+Iron Stone=Eisenstein
+Iron Stone Bricks=Eisensteinziegel
+Plankstone=Brettstein
+Rope=Seil
+Split Stone Tile=Geteilte Steinfliese
+Stone Bricks=Steinziegel
+Stone Tile=Steinfliese
+Super Glow Glass=Superleuchtglas
+Sweeper=Besen
+Tar=Teer
+Wooden Tile=Holzfliese
+Offset Wooden Tile=Holzfliese versetzt
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.es.tr b/mods/moreblocks/moreblocks/locale/moreblocks.es.tr
new file mode 100644
index 00000000..3b9ec8b6
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.es.tr
@@ -0,0 +1,47 @@
+# textdomain: moreblocks
+
+# Spanish translation for More Blocks.
+# Copyright © 2011-2020 Hugo Locurcio and contributors
+# This file is distributed under the same license as the More Blocks package.
+# kaeza, 2013.
+# CodeXP , 2018.
+# Carlos Barraza 2020.
+
+#: nodes.lua
+
+All-faces @1=@1, todas las caras
+No-faces @1=
+Cactus Brick=Ladrillos de Cactus
+Cactus Checker=Cuadros de Cactus
+Centered Wooden Tile=Parqué Centrado
+Checker Stone Tile=Cuadros de Baldosa de Piedra
+Circle Stone Bricks=Bloques de Piedra Circulares
+Clean Glass=Cristal Limpio
+Coal Checker=Cuadros de Carbón
+Coal Glass=Cristal con Carbón
+Clean Super Glow Glass=Cristal Súper Brillante Limpio
+Trap @1=@1 Falso
+Clean Glow Glass=Cristal Brillante Limpio
+Coal Stone=Carbón y Piedra
+Coal Stone Bricks=Ladrillos de Piedra de Carbon
+Compressed Cobblestone=Adoquín Comprimido
+Compressed Desert Cobblestone=Adoquín del Desierto Comprimido
+Compressed Dirt=Tierra Comprimida
+Copper Patina Block=Bloque de Pátina de Cobre
+Empty Shelf=Estante vacio
+Full Wooden Tile=Parqué Completo
+Glow Glass=Cristal Brillante
+Iron Checker=Cuadros de Hierro
+Iron Glass=Cristal con Hierro
+Iron Stone=Hierro y Piedra
+Iron Stone Bricks=Ladrillo de Piedra de Hierro
+Plankstone=Tablones de piedra
+Rope=Soga
+Split Stone Tile=Baldosas de Piedra Partida
+Stone Bricks=Ladrillos de Piedra
+Stone Tile=Baldosa de Piedra
+Super Glow Glass=Cristal Súper Brillante
+Sweeper=Limpiador
+Tar=Alquitrán
+Wooden Tile=Parqué
+Offset Wooden Tile=Parqué Ajustado
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.fr.tr b/mods/moreblocks/moreblocks/locale/moreblocks.fr.tr
new file mode 100644
index 00000000..c6d86c1a
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.fr.tr
@@ -0,0 +1,51 @@
+# textdomain: moreblocks
+
+# French translation for More Blocks.
+# Copyright © 2011-2020 Hugo Locurcio and contributors
+# This file is distributed under the same license as the More Blocks package.
+# Hugo Locurcio , 2013-2019.
+# Jat15, 2013.
+# CodeXP , 2018.
+
+#: init.lua
+
+[moreblocks] loaded.=[moreblocks] a été chargé.
+
+#: nodes.lua
+
+All-faces @1=@1 (toutes faces)
+No-faces @1=
+Cactus Brick=Briques de cactus
+Cactus Checker=Damier en cactus
+Centered Wooden Tile=Dalle en bois centrée
+Checker Stone Tile=Damier de dalle en pierre
+Circle Stone Bricks=Briques en pierre circulaires
+Clean Glass=Verre propre
+Coal Checker=Damier en charbon
+Coal Glass=Verre de charbon
+Clean Super Glow Glass=
+Trap @1=@1 traversable
+Clean Glow Glass=
+Coal Stone=Pierre de charbon
+Coal Stone Bricks=Briques en pierre de charbon
+Compressed Cobblestone=Pierre taillée compressée
+Compressed Desert Cobblestone=
+Compressed Dirt=Terre compressée
+Copper Patina Block=Bloc de patine de cuivre
+Empty Shelf=Étagère vide
+Full Wooden Tile=Dalle en bois complète
+Glow Glass=Verre brillant
+Iron Checker=Damier de fer
+Iron Glass=Verre de fer
+Iron Stone=Pierre de fer
+Iron Stone Bricks=Briques en pierre de fer
+Plankstone=Pierre-bois
+Rope=Corde
+Split Stone Tile=Dalle en pierre découpée
+Stone Bricks=Briques en pierre
+Stone Tile=Dalle en pierre
+Super Glow Glass=Verre très brillant
+Sweeper=Balai
+Tar=Bitume
+Wooden Tile=Dalle en bois
+Offset Wooden Tile=Dalle en bois décalée
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.it.tr b/mods/moreblocks/moreblocks/locale/moreblocks.it.tr
new file mode 100644
index 00000000..67d4e22b
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.it.tr
@@ -0,0 +1,50 @@
+# textdomain: moreblocks
+
+# Italian translation for More Blocks.
+# Copyright © 2011-2020 Hugo Locurcio and contributors
+# This file is distributed under the same license as the More Blocks package.
+# Emon, 2016.
+# CodeXP , 2018.
+
+#: init.lua
+
+[moreblocks] loaded.=[moreblocks] caricato.
+
+#: nodes.lua
+
+All-faces @1=@1 su ogni lato
+No-faces @1=
+Cactus Brick=Mattoni di cactus
+Cactus Checker=Scacchiera in cactus
+Centered Wooden Tile=Mattonella in legno centrata
+Checker Stone Tile=
+Circle Stone Bricks=Mattoni concentrici in pietra
+Clean Glass=Vetro pulito
+Coal Checker=Scacchiera in carbone
+Coal Glass=Vetro e carbone
+Clean Super Glow Glass=
+Trap @1=@1 trappola
+Clean Glow Glass=
+Coal Stone=Pietra in carbone
+Coal Stone Bricks=Mattoni di pietra in carbone
+Compressed Cobblestone=
+Compressed Desert Cobblestone=
+Compressed Dirt=
+Copper Patina Block=
+Empty Shelf=Scaffale Vuoto
+Full Wooden Tile=Mattonella in legno pieno
+Glow Glass=Vetro luminoso
+Iron Checker=Scacchiera in ferro
+Iron Glass=Vetro e ferro
+Iron Stone=Pietra in ferro
+Iron Stone Bricks=Mattoni di pietra in ferro
+Plankstone=Pietra e legno
+Rope=Corda
+Split Stone Tile=Mattonella in pietra divisa
+Stone Bricks=
+Stone Tile=Mattonella in pietra
+Super Glow Glass=Super vetro luminoso
+Sweeper=Spazzola
+Tar=
+Wooden Tile=Mattonella in legno
+Offset Wooden Tile=
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.pl.tr b/mods/moreblocks/moreblocks/locale/moreblocks.pl.tr
new file mode 100644
index 00000000..35ce9752
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.pl.tr
@@ -0,0 +1,50 @@
+# textdomain: moreblocks
+
+# Polish translation for More Blocks.
+# Copyright © 2011-2020 Hugo Locurcio and contributors
+# This file is distributed under the same license as the More Blocks package.
+# mat9117, 2019
+# CodeXP , 2018.
+
+#: init.lua
+
+[moreblocks] loaded.=[moreblocks] załadowane.
+
+#: nodes.lua
+
+All-faces @1=Wielostronna tekstura @1
+No-faces @1=
+Cactus Brick=Kaktusowa cegła
+Cactus Checker=Kaktusowa szachownica
+Centered Wooden Tile=Wyśrodkowany drewniany kafelek
+Checker Stone Tile=Kamienna szachownica
+Circle Stone Bricks=Okrągłe kamienne cegły
+Clean Glass=Czyste szkło
+Coal Checker=Węglowa szachownica
+Coal Glass=Szkło węglowe
+Clean Super Glow Glass=
+Trap @1=@1 pułapka
+Clean Glow Glass=
+Coal Stone=Kamień węglowy
+Coal Stone Bricks=Węglowe kamienne cegły
+Compressed Cobblestone=Skompresowany bruk
+Compressed Desert Cobblestone=
+Compressed Dirt=Skompresowana ziemia
+Copper Patina Block=Blok patynowanej miedzi
+Empty Shelf=Pusta półka
+Full Wooden Tile=Pełny drewniany kafelek
+Glow Glass=Świecące szkło
+Iron Checker=Żelazna szachownica
+Iron Glass=Żelazne szkło
+Iron Stone=Żelazny kamień
+Iron Stone Bricks=Żelazne kamienne cegły
+Plankstone=Deskokamień
+Rope=Lina
+Split Stone Tile=Kamienny blok kafelkowy
+Stone Bricks=Kamienne cegły
+Stone Tile=Kamienny kafelek
+Super Glow Glass=Super świecące szkło
+Sweeper=Miotła
+Tar=Smoła
+Wooden Tile=Drewniany kafelek
+Offset Wooden Tile=
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.ru.tr b/mods/moreblocks/moreblocks/locale/moreblocks.ru.tr
new file mode 100644
index 00000000..a3e119f6
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.ru.tr
@@ -0,0 +1,53 @@
+# textdomain: moreblocks
+
+# Russian translation for MOREBLOCKS minetest mod.
+# Copyright (C) 2018 Hugo Locurcio and contributors
+# This file is distributed under the same license as the MOREBLOCKS package.
+# CodeXP , 2018.
+#
+#, fuzzy
+
+#: circular_saw.lua
+
+#: init.lua
+
+[MOD] moreblocks loaded.=[MOD] moreblocks загружен.
+
+#: nodes.lua
+
+All-faces @1=Всесторонняя @1
+No-faces @1=Цельная @1
+Cactus Brick=Кирпич из кактуса
+Cactus Checker=Мозаика из кактуса
+Centered Wooden Tile=Центрированная деревянная плитка
+Checker Stone Tile=Каменная шахматная плитка
+Circle Stone Bricks=Круглые каменные кирпичи
+Clean Glass=Чистое стекло
+Coal Checker=Угольная мозаика
+Coal Glass=Угольное стекло
+Clean Super Glow Glass=Чистое супер светящееся стекло
+Trap @1=@1 (рамка)
+Clean Glow Glass=Чистое светящееся стекло
+Coal Stone=Угольный камень
+Coal Stone Bricks=Угольно-каменные кирпичи
+Compressed Cobblestone=Прессованный булыжник
+Compressed Desert Cobblestone=Прессованный пустынный булыжник
+Compressed Dirt=Прессованная земля
+Copper Patina Block=Медный патинированный блок
+Empty Shelf=Пустая полка
+Full Wooden Tile=Деревянная мозаика
+Glow Glass=Светящееся стекло
+Iron Checker=Железная мозаика
+Iron Glass=Металлизированное стекло
+Iron Stone=Железный камень
+Iron Stone Bricks=Железно-каменный кирпич
+Plankstone=Железно-деревянная мозаика
+Rope=Верёвка
+Split Stone Tile=Половинный камень - плитка
+Stone Bricks=Каменный кирпич
+Stone Tile=Каменная плитка
+Super Glow Glass=Супер светящееся стекло
+Sweeper=Метёлка
+Tar=Смола
+Wooden Tile=Деревянная плитка
+Offset Wooden Tile=Деревянная плитка (сверху)
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.template.tr b/mods/moreblocks/moreblocks/locale/moreblocks.template.tr
new file mode 100644
index 00000000..15a7fefa
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.template.tr
@@ -0,0 +1,40 @@
+# textdomain: moreblocks
+
+#: nodes.lua
+
+All-faces @1=
+Cactus Brick=
+Cactus Checker=
+Centered Wooden Tile=
+Checker Stone Tile=
+Circle Stone Bricks=
+Clean Glass=
+Coal Checker=
+Coal Glass=
+Clean Super Glow Glass=
+Trap @1=
+Clean Glow Glass=
+Coal Stone=
+Coal Stone Bricks=
+Compressed Cobblestone=
+Compressed Desert Cobblestone=
+Compressed Dirt=
+Copper Patina Block=
+Empty Shelf=
+Full Wooden Tile=
+Glow Glass=
+Iron Checker=
+Iron Glass=
+Iron Stone=
+Iron Stone Bricks=
+No-faces @1=
+Plankstone=
+Rope=
+Split Stone Tile=
+Stone Bricks=
+Stone Tile=
+Super Glow Glass=
+Sweeper=
+Tar=
+Wooden Tile=
+Offset Wooden Tile=
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.zh_CN.tr b/mods/moreblocks/moreblocks/locale/moreblocks.zh_CN.tr
new file mode 100644
index 00000000..4fe862fe
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.zh_CN.tr
@@ -0,0 +1,45 @@
+# textdomain: moreblocks
+
+# zh_CN translation for More Blocks.
+# Copyright © 2011-2020 Hugo Locurcio and contributors
+# This file is distributed under the same license as the More Blocks package.
+# IFRFSX , 2020.
+
+#: nodes.lua
+
+All-faces @1=全切面@1树木方块
+No-faces @1=
+Cactus Brick=仙人掌砖
+Cactus Checker=仙人掌棋盘方块
+Centered Wooden Tile=居中的木瓦
+Checker Stone Tile=棋盘石瓦
+Circle Stone Bricks=圆石砖
+Clean Glass=干净的玻璃
+Coal Checker=棋盘煤块
+Coal Glass=煤玻璃
+Clean Super Glow Glass=
+Trap @1=陷阱@1
+Clean Glow Glass=
+Coal Stone=煤炭石
+Coal Stone Bricks=煤炭石砖
+Compressed Cobblestone=压缩圆石
+Compressed Desert Cobblestone=
+Compressed Dirt=压缩土
+Copper Patina Block=铜绿方块
+Empty Shelf=空书架
+Full Wooden Tile=全木瓦
+Glow Glass=发光玻璃
+Iron Checker=棋盘铁方块
+Iron Glass=铁玻璃
+Iron Stone=铁石
+Iron Stone Bricks=铁石砖
+Plankstone=板石
+Rope=绳子
+Split Stone Tile=裂石砖
+Stone Bricks=石砖
+Stone Tile=石瓦
+Super Glow Glass=超级发光玻璃
+Sweeper=清扫器
+Tar=焦油
+Wooden Tile=木瓦
+Offset Wooden Tile=胶合木瓦
diff --git a/mods/moreblocks/moreblocks/locale/moreblocks.zh_TW.tr b/mods/moreblocks/moreblocks/locale/moreblocks.zh_TW.tr
new file mode 100644
index 00000000..78a8b2e0
--- /dev/null
+++ b/mods/moreblocks/moreblocks/locale/moreblocks.zh_TW.tr
@@ -0,0 +1,45 @@
+# textdomain: moreblocks
+
+# zh_TW translation for More Blocks.
+# Copyright © 2011-2020 Hugo Locurcio and contributors
+# This file is distributed under the same license as the More Blocks package.
+# IFRFSX , 2020.
+
+#: nodes.lua
+
+All-faces @1=全切面@1樹木方塊
+No-faces @1=
+Cactus Brick=仙人掌磚
+Cactus Checker=仙人掌棋盤方塊
+Centered Wooden Tile=居中的木瓦
+Checker Stone Tile=棋盤石瓦
+Circle Stone Bricks=圓石磚
+Clean Glass=乾淨的玻璃
+Coal Checker=棋盤煤塊
+Coal Glass=煤玻璃
+Clean Super Glow Glass=
+Trap @1=陷阱@1
+Clean Glow Glass=
+Coal Stone=煤炭石
+Coal Stone Bricks=煤炭石磚
+Compressed Cobblestone=壓縮圓石
+Compressed Desert Cobblestone=
+Compressed Dirt=壓縮土
+Copper Patina Block=銅綠方塊
+Empty Shelf=空書架
+Full Wooden Tile=全木瓦
+Glow Glass=發光玻璃
+Iron Checker=棋盤鐵方塊
+Iron Glass=鐵玻璃
+Iron Stone=鐵石
+Iron Stone Bricks=鐵石磚
+Plankstone=板石
+Rope=繩子
+Split Stone Tile=裂石磚
+Stone Bricks=石磚
+Stone Tile=石瓦
+Super Glow Glass=超級發光玻璃
+Sweeper=清掃器
+Tar=焦油
+Wooden Tile=木瓦
+Offset Wooden Tile=膠合木瓦
diff --git a/mods/moreblocks/moreblocks/mod.conf b/mods/moreblocks/moreblocks/mod.conf
new file mode 100644
index 00000000..f22bdb16
--- /dev/null
+++ b/mods/moreblocks/moreblocks/mod.conf
@@ -0,0 +1,10 @@
+name = moreblocks
+title = more blocks
+description = adds various blocks to the game.
+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 = bucket, default, rhotator, screwdriver, stairs, stairsplus, vessels
diff --git a/mods/moreblocks/moreblocks/nodes.lua b/mods/moreblocks/moreblocks/nodes.lua
new file mode 100644
index 00000000..2938e53d
--- /dev/null
+++ b/mods/moreblocks/moreblocks/nodes.lua
@@ -0,0 +1,483 @@
+local S = moreblocks.S
+
+local cm = moreblocks.resources.craft_materials
+local t = moreblocks.resources.textures
+
+local modname = moreblocks.modname
+
+local sound_dirt = moreblocks.resources.sounds.dirt
+local sound_wood = moreblocks.resources.sounds.wood
+local sound_stone = moreblocks.resources.sounds.stone
+local sound_glass = moreblocks.resources.sounds.glass
+local sound_leaves = moreblocks.resources.sounds.leaves
+local sound_metal = moreblocks.resources.sounds.metal
+
+local function is_glasslike(def)
+ return #def.tiles > 1 and (def.drawtype == "glasslike_framed" or def.drawtype == "glasslike_framed_optional")
+end
+
+local function register_stairs(name, def)
+ local itemstring = ("%s:%s"):format(modname, name)
+
+ -- Use the primary tile for all sides of cut glasslike nodes.
+ -- This makes them easier to see
+ if is_glasslike(def) then
+ def = table.copy(def)
+ def.tiles = { def.tiles[1] }
+ end
+
+ if moreblocks.has.stairsplus then
+ if stairsplus.settings.legacy_mode then
+ stairsplus.api.register_group(itemstring, "legacy")
+ else
+ stairsplus.api.register_group(itemstring, "common")
+ end
+ elseif moreblocks.has.stairs then
+ stairs.register_stair_and_slab(
+ ("%s_%s"):format(modname, name),
+ itemstring,
+ def.groups,
+ def.tiles,
+ S("@1 Stair", def.description),
+ S("@1 Slab", def.description),
+ def.sounds,
+ true
+ )
+ end
+end
+
+local function tile_tiles(tex)
+ return { tex, tex, tex, tex, tex .. "^[transformR90", tex .. "^[transformR90" }
+end
+
+local function register_with_stairs(name, def)
+ local itemstring = ("%s:%s"):format(modname, name)
+ def.tiles = def.tiles or { ("%s_%s.png"):format(modname, name) }
+ minetest.register_node(itemstring, def)
+ minetest.register_alias(name, itemstring)
+ register_stairs(name, def)
+end
+
+local function register_no_stairs(name, def)
+ local itemstring = ("%s:%s"):format(modname, name)
+ def.tiles = def.tiles or { ("%s_%s.png"):format(modname, name) }
+ minetest.register_node(itemstring, def)
+ minetest.register_alias(name, itemstring)
+end
+
+local function register_all_faces(name, base)
+ name = "all_faces_" .. name
+ local itemstring = ("%s:%s"):format(modname, name)
+ moreblocks.api.register_all_faces(itemstring, base)
+ register_stairs(name, minetest.registered_nodes[itemstring])
+ minetest.register_alias(name, itemstring)
+end
+
+local function register_no_faces(name, base)
+ name = "no_faces_" .. name
+ local itemstring = ("%s:%s"):format(modname, name)
+ moreblocks.api.register_no_faces(itemstring, base)
+ register_stairs(name, minetest.registered_nodes[itemstring])
+ minetest.register_alias(name, itemstring)
+end
+
+local function register_trap(name, base)
+ name = "trap_" .. name
+ local itemstring = ("%s:%s"):format(modname, name)
+ moreblocks.api.register_trap(itemstring, base)
+ minetest.register_alias(name, itemstring)
+end
+
+register_with_stairs("wood_tile", {
+ description = S("Wooden Tile"),
+ groups = { wood = 1, choppy = 2, oddly_breakable_by_hand = 2, flammable = 3 },
+ is_ground_content = false,
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ tiles = tile_tiles(("%s^moreblocks_wood_tile.png"):format(t.wood)),
+ sounds = sound_wood,
+})
+
+register_with_stairs("wood_tile_center", {
+ description = S("Centered Wooden Tile"),
+ groups = { wood = 1, choppy = 2, oddly_breakable_by_hand = 2, flammable = 3 },
+ is_ground_content = false,
+ tiles = {
+ ("%s^moreblocks_wood_tile_center.png"):format(t.wood),
+ },
+ sounds = sound_wood,
+})
+
+register_with_stairs("wood_tile_full", {
+ description = S("Full Wooden Tile"),
+ groups = { wood = 1, choppy = 2, oddly_breakable_by_hand = 2, flammable = 3 },
+ is_ground_content = false,
+ tiles = tile_tiles("moreblocks_wood_tile_full.png"),
+ sounds = sound_wood,
+})
+
+register_no_stairs("wood_tile_offset", {
+ description = S("Offset Wooden Tile"),
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ groups = { wood = 1, choppy = 2, oddly_breakable_by_hand = 2, flammable = 3 },
+ is_ground_content = false,
+ tiles = {
+ ("%s^moreblocks_wood_tile_offset.png"):format(t.wood),
+ },
+ sounds = sound_wood,
+})
+
+register_with_stairs("circle_stone_bricks", {
+ description = S("Circle Stone Bricks"),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("grey_bricks", {
+ description = S("Stone Bricks"),
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ groups = { cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("coal_stone_bricks", {
+ description = S("Coal Stone Bricks"),
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("iron_stone_bricks", {
+ description = S("Iron Stone Bricks"),
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("stone_tile", {
+ description = S("Stone Tile"),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("split_stone_tile", {
+ description = S("Split Stone Tile"),
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ tiles = {
+ "moreblocks_split_stone_tile_top.png",
+ "moreblocks_split_stone_tile.png",
+ },
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("checker_stone_tile", {
+ description = S("Checker Stone Tile"),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("tar", {
+ description = S("Tar"),
+ groups = { cracky = 2, tar_block = 1 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("dirt_compressed", {
+ description = S("Compressed Dirt"),
+ groups = { crumbly = 2 },
+ is_ground_content = false,
+ sounds = sound_dirt,
+})
+
+register_with_stairs("cobble_compressed", {
+ description = S("Compressed Cobblestone"),
+ groups = { cracky = 1 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("desert_cobble_compressed", {
+ description = S("Compressed Desert Cobblestone"),
+ groups = { cracky = 1 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("plankstone", {
+ description = S("Plankstone"),
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ groups = { cracky = 3 },
+ is_ground_content = false,
+ tiles = tile_tiles("moreblocks_plankstone.png"),
+ sounds = sound_stone,
+})
+
+register_with_stairs("iron_glass", {
+ description = S("Iron Glass"),
+ drawtype = "glasslike_framed_optional",
+ tiles = {
+ ("%s^[colorize:#DEDEDE"):format(t.glass),
+ ("%s^[colorize:#DEDEDE"):format(t.glass_detail),
+ },
+ use_texture_alpha = "clip",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ groups = { cracky = 3, oddly_breakable_by_hand = 3 },
+ sounds = sound_glass,
+})
+
+register_with_stairs("coal_glass", {
+ description = S("Coal Glass"),
+ drawtype = "glasslike_framed_optional",
+ tiles = {
+ ("%s^[colorize:#828282"):format(t.glass),
+ ("%s^[colorize:#828282"):format(t.glass_detail),
+ },
+ use_texture_alpha = "clip",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ groups = { cracky = 3, oddly_breakable_by_hand = 3 },
+ sounds = sound_glass,
+})
+
+register_with_stairs("clean_glass", {
+ description = S("Clean Glass"),
+ drawtype = "glasslike_framed_optional",
+ tiles = {
+ "moreblocks_clean_glass.png",
+ "moreblocks_clean_glass_detail.png",
+ },
+ use_texture_alpha = "clip",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ groups = { cracky = 3, oddly_breakable_by_hand = 3 },
+ sounds = sound_glass,
+})
+
+register_with_stairs("cactus_brick", {
+ description = S("Cactus Brick"),
+ paramtype2 = "facedir",
+ place_param2 = 0,
+ groups = { cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("cactus_checker", {
+ description = S("Cactus Checker"),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ tiles = tile_tiles(("%s^moreblocks_cactus_checker.png"):format(t.stone)),
+ sounds = sound_stone,
+})
+
+register_no_stairs("empty_shelf", {
+ description = S("Empty Shelf"),
+ paramtype2 = "facedir",
+ tiles = {
+ t.wood,
+ t.wood,
+ t.wood,
+ t.wood,
+ "moreblocks_empty_shelf.png",
+ "moreblocks_empty_shelf.png",
+ },
+ groups = { choppy = 3, oddly_breakable_by_hand = 2, flammable = 3 },
+ is_ground_content = false,
+ sounds = sound_wood,
+ furnace_burntime = 15,
+})
+
+register_with_stairs("coal_stone", {
+ description = S("Coal Stone"),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("iron_stone", {
+ description = S("Iron Stone"),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("coal_checker", {
+ description = S("Coal Checker"),
+ tiles = tile_tiles(("%s^moreblocks_coal_checker.png"):format(t.stone)),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("iron_checker", {
+ description = S("Iron Checker"),
+ tiles = tile_tiles(("%s^moreblocks_iron_checker.png"):format(t.stone)),
+ groups = { stone = 1, cracky = 3 },
+ is_ground_content = false,
+ sounds = sound_stone,
+})
+
+register_with_stairs("glow_glass", {
+ description = S("Glow Glass"),
+ drawtype = "glasslike_framed_optional",
+ tiles = {
+ ("%s^[colorize:#E9CD61"):format(t.glass),
+ ("%s^[colorize:#E9CD61"):format(t.glass_detail),
+ },
+ use_texture_alpha = "clip",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ light_source = 11,
+ groups = { cracky = 3, oddly_breakable_by_hand = 3 },
+ sounds = sound_glass,
+})
+
+register_with_stairs("super_glow_glass", {
+ description = S("Super Glow Glass"),
+ drawtype = "glasslike_framed_optional",
+ tiles = {
+ ("%s^[colorize:#FFFF78"):format(t.glass),
+ ("%s^[colorize:#FFFF78"):format(t.glass_detail),
+ },
+ use_texture_alpha = "clip",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ light_source = minetest.LIGHT_MAX,
+ groups = { cracky = 3, oddly_breakable_by_hand = 3 },
+ sounds = sound_glass,
+})
+
+register_with_stairs("clean_glow_glass", {
+ description = S("Clean Glow Glass"),
+ drawtype = "glasslike_framed_optional",
+ tiles = {
+ "moreblocks_clean_glass.png^[colorize:#E9CD61",
+ "moreblocks_clean_glass_detail.png^[colorize:#E9CD61",
+ },
+ use_texture_alpha = "clip",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ light_source = 11,
+ groups = { cracky = 3, oddly_breakable_by_hand = 3 },
+ sounds = sound_glass,
+})
+
+register_with_stairs("clean_super_glow_glass", {
+ description = S("Clean Super Glow Glass"),
+ drawtype = "glasslike_framed_optional",
+ tiles = {
+ "moreblocks_clean_glass.png^[colorize:#FFFF78",
+ "moreblocks_clean_glass_detail.png^[colorize:#FFFF78",
+ },
+ use_texture_alpha = "clip",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ light_source = minetest.LIGHT_MAX,
+ groups = { cracky = 3, oddly_breakable_by_hand = 3 },
+ sounds = sound_glass,
+})
+
+register_with_stairs("copperpatina", {
+ description = S("Copper Patina Block"),
+ groups = { cracky = 1, level = 2 },
+ is_ground_content = false,
+ sounds = sound_metal,
+})
+
+register_no_stairs("rope", {
+ description = S("Rope"),
+ drawtype = "signlike",
+ inventory_image = "moreblocks_rope.png",
+ wield_image = "moreblocks_rope.png",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ paramtype2 = "wallmounted",
+ walkable = false,
+ climbable = true,
+ selection_box = { type = "wallmounted" },
+ groups = { snappy = 3, flammable = 2 },
+ sounds = sound_leaves,
+})
+
+register_trap("clean_glass", "moreblocks:clean_glass")
+register_trap("clean_glow_glass", "moreblocks:clean_glow_glass")
+register_trap("clean_super_glow_glass", "moreblocks:clean_super_glow_glass")
+
+if cm.stone then
+ register_trap("stone", cm.stone)
+end
+
+if cm.desert_stone then
+ register_trap("desert_stone", cm.desert_stone)
+end
+
+if cm.glass then
+ register_trap("glass", cm.glass)
+ register_trap("glow_glass", "moreblocks:glow_glass")
+ register_trap("super_glow_glass", "moreblocks:super_glow_glass")
+end
+
+if cm.obsidian_glass then
+ register_trap("obsidian_glass", cm.obsidian_glass)
+end
+
+if cm.obsidian then
+ register_trap("obsidian", cm.obsidian)
+end
+
+if cm.obsidian then
+ register_trap("obsidian", cm.obsidian)
+end
+
+if cm.sandstone then
+ register_trap("sandstone", cm.sandstone)
+end
+
+if cm.tree then
+ register_all_faces("tree", cm.tree)
+ register_no_faces("tree", cm.tree)
+end
+
+if cm.jungle_tree then
+ register_all_faces("jungle_tree", cm.jungle_tree)
+ register_no_faces("jungle_tree", cm.jungle_tree)
+end
+
+if cm.pine_tree then
+ register_all_faces("pine_tree", cm.pine_tree)
+ register_no_faces("pine_tree", cm.pine_tree)
+end
+
+if cm.acacia_tree then
+ register_all_faces("acacia_tree", cm.acacia_tree)
+ register_no_faces("acacia_tree", cm.acacia_tree)
+end
+
+if cm.aspen_tree then
+ register_all_faces("aspen_tree", cm.aspen_tree)
+ register_no_faces("aspen_tree", cm.aspen_tree)
+end
diff --git a/mods/moreblocks/moreblocks/resources/craft_materials.lua b/mods/moreblocks/moreblocks/resources/craft_materials.lua
new file mode 100644
index 00000000..ae4bafce
--- /dev/null
+++ b/mods/moreblocks/moreblocks/resources/craft_materials.lua
@@ -0,0 +1,68 @@
+local table_set_all = futil.table.set_all
+
+moreblocks.resources.craft_materials = {}
+
+if moreblocks.has.bucket then
+ table_set_all(moreblocks.resources.craft_materials, {
+ bucket_empty = "bucket:bucket_empty",
+ })
+end
+
+if moreblocks.has.default then
+ table_set_all(moreblocks.resources.craft_materials, {
+ acacia_tree = "default:acacia_tree",
+ aspen_tree = "default:aspen_tree",
+ book = "default:book",
+ bookshelf = "default:bookshelf",
+ brick = "default:brick",
+ bronze_ingot = "default:bronze_ingot",
+ cactus = "default:cactus",
+ chest = "default:chest",
+ chest_locked = "default:chest_locked",
+ coal_lump = "default:coal_lump",
+ cobble = "default:cobble",
+ copper_block = "default:copperblock",
+ copper_ingot = "default:copper_ingot",
+ desert_cobble = "default:desert_cobble",
+ desert_stone = "default:desert_stone",
+ dirt = "default:dirt",
+ dirt_with_grass = "default:dirt_with_grass",
+ dry_shrub = "default:dry_shrub",
+ fence_jungle_wood = "default:fence_junglewood",
+ glass = "default:glass",
+ gold_ingot = "default:gold_ingot",
+ jungle_grass = "default:junglegrass",
+ jungle_tree = "default:jungletree",
+ jungle_wood = "default:junglewood",
+ mossy_cobble = "default:mossycobble",
+ obsidian = "default:obsidian",
+ obsidian_glass = "default:obsidian_glass",
+ pine_tree = "default:pine_tree",
+ sandstone = "default:sandstone",
+ steel_ingot = "default:steel_ingot",
+ stick = "default:stick",
+ stone_brick = "default:stonebrick",
+ stone = "default:stone",
+ torch = "default:torch",
+ trap_material = "default:mese_crystal_fragment",
+ tree = "default:tree",
+ wood = "default:wood",
+ })
+end
+
+if moreblocks.has.screwdriver then
+ table_set_all(moreblocks.resources.craft_materials, {
+ screwdriver = "screwdriver:screwdriver",
+ })
+elseif moreblocks.has.rhotator then
+ table_set_all(moreblocks.resources.craft_materials, {
+ screwdriver = "rhotator:screwdriver_multi",
+ })
+end
+
+if moreblocks.has.vessels then
+ table_set_all(moreblocks.resources.craft_materials, {
+ glass_bottle = "vessels:glass_bottle",
+ vessels_shelf = "vessels:shelf",
+ })
+end
diff --git a/mods/moreblocks/moreblocks/resources/init.lua b/mods/moreblocks/moreblocks/resources/init.lua
new file mode 100644
index 00000000..64ec3c70
--- /dev/null
+++ b/mods/moreblocks/moreblocks/resources/init.lua
@@ -0,0 +1,5 @@
+moreblocks.resources = {}
+
+moreblocks.dofile("resources", "craft_materials")
+moreblocks.dofile("resources", "sounds")
+moreblocks.dofile("resources", "textures")
diff --git a/mods/moreblocks/moreblocks/resources/sounds.lua b/mods/moreblocks/moreblocks/resources/sounds.lua
new file mode 100644
index 00000000..c7fe47c9
--- /dev/null
+++ b/mods/moreblocks/moreblocks/resources/sounds.lua
@@ -0,0 +1,14 @@
+local table_set_all = futil.table.set_all
+
+moreblocks.resources.sounds = {}
+
+if moreblocks.has.default then
+ table_set_all(moreblocks.resources.sounds, {
+ dirt = default.node_sound_dirt_defaults(),
+ glass = default.node_sound_glass_defaults(),
+ leaves = default.node_sound_leaves_defaults(),
+ metal = default.node_sound_metal_defaults(),
+ stone = default.node_sound_stone_defaults(),
+ wood = default.node_sound_wood_defaults(),
+ })
+end
diff --git a/mods/moreblocks/moreblocks/resources/textures.lua b/mods/moreblocks/moreblocks/resources/textures.lua
new file mode 100644
index 00000000..89553230
--- /dev/null
+++ b/mods/moreblocks/moreblocks/resources/textures.lua
@@ -0,0 +1,51 @@
+local table_set_all = futil.table.set_all
+
+moreblocks.resources.textures = {
+ desert_stone = "[combine:16x16^[noalpha^[colorize:#85513e",
+ glass = ([[
+ [combine:16x16
+ :0,0=\[combine\:1x16\^[noalpha\^[colorize\:#FFF
+ :0,0=\[combine\:16x1\^[noalpha\^[colorize\:#FFF
+ :0,15=\[combine\:16x1\^[noalpha\^[colorize\:#FFF
+ :15,0=\[combine\:1x16\^[noalpha\^[colorize\:#FFF
+ ]]):gsub("%s", ""),
+ glass_detail = ([[
+ [combine:16x16
+ :0,0=\[combine\:1x16\^[noalpha\^[colorize\:#FFF
+ :0,0=\[combine\:16x1\^[noalpha\^[colorize\:#FFF
+ :0,15=\[combine\:16x1\^[noalpha\^[colorize\:#FFF
+ :15,0=\[combine\:1x16\^[noalpha\^[colorize\:#FFF
+ ]]):gsub("%s", ""),
+ obsidian = "default_obsidian.png",
+ obsidian_glass = ([[
+ [combine:16x16
+ :0,0=\[combine\:1x16\^[noalpha\^[colorize\:#000
+ :0,0=\[combine\:16x1\^[noalpha\^[colorize\:#000
+ :0,15=\[combine\:16x1\^[noalpha\^[colorize\:#000
+ :15,0=\[combine\:1x16\^[noalpha\^[colorize\:#000
+ ]]):gsub("%s", ""),
+ obsidian_glass_detail = ([[
+ [combine:16x16
+ :0,0=\[combine\:1x16\^[noalpha\^[colorize\:#000
+ :0,0=\[combine\:16x1\^[noalpha\^[colorize\:#000
+ :0,15=\[combine\:16x1\^[noalpha\^[colorize\:#000
+ :15,0=\[combine\:1x16\^[noalpha\^[colorize\:#000
+ ]]):gsub("%s", ""),
+ sandstone = "default_sandstone.png",
+ stone = "[combine:16x16^[noalpha^[colorize:#686463",
+ wood = "[combine:16x16^[noalpha^[colorize:#654321",
+}
+
+if moreblocks.has.default then
+ table_set_all(moreblocks.resources.textures, {
+ desert_stone = "default_desert_stone.png",
+ glass = "default_glass.png",
+ glass_detail = "default_glass_detail.png",
+ obsidian = "default_obsidian.png",
+ obsidian_glass = "default_obsidian_glass.png",
+ obsidian_glass_detail = "default_obsidian_glass_detail.png",
+ sandstone = "default_sandstone.png",
+ stone = "default_stone.png",
+ wood = "default_wood.png",
+ })
+end
diff --git a/mods/moreblocks/moreblocks/settingtypes.txt b/mods/moreblocks/moreblocks/settingtypes.txt
new file mode 100644
index 00000000..f979d257
--- /dev/null
+++ b/mods/moreblocks/moreblocks/settingtypes.txt
@@ -0,0 +1,2 @@
+# Add a yellow outline around trap nodes, to make them visibly distinct
+moreblocks.outline_trap_nodes (Outline trap nodes) bool true
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_cactus_brick.png b/mods/moreblocks/moreblocks/textures/moreblocks_cactus_brick.png
new file mode 100644
index 00000000..43d2479f
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_cactus_brick.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_cactus_checker.png b/mods/moreblocks/moreblocks/textures/moreblocks_cactus_checker.png
new file mode 100644
index 00000000..79f2e32c
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_cactus_checker.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_checker_stone_tile.png b/mods/moreblocks/moreblocks/textures/moreblocks_checker_stone_tile.png
new file mode 100644
index 00000000..72615866
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_checker_stone_tile.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_circle_stone_bricks.png b/mods/moreblocks/moreblocks/textures/moreblocks_circle_stone_bricks.png
new file mode 100644
index 00000000..d14057ab
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_circle_stone_bricks.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_clean_glass.png b/mods/moreblocks/moreblocks/textures/moreblocks_clean_glass.png
new file mode 100644
index 00000000..b7669560
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_clean_glass.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_clean_glass_detail.png b/mods/moreblocks/moreblocks/textures/moreblocks_clean_glass_detail.png
new file mode 100644
index 00000000..71414e89
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_clean_glass_detail.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_coal_checker.png b/mods/moreblocks/moreblocks/textures/moreblocks_coal_checker.png
new file mode 100644
index 00000000..181cb979
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_coal_checker.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_coal_glass_stairsplus.png b/mods/moreblocks/moreblocks/textures/moreblocks_coal_glass_stairsplus.png
new file mode 100644
index 00000000..cc2e7972
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_coal_glass_stairsplus.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_coal_stone.png b/mods/moreblocks/moreblocks/textures/moreblocks_coal_stone.png
new file mode 100644
index 00000000..1e514edd
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_coal_stone.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_coal_stone_bricks.png b/mods/moreblocks/moreblocks/textures/moreblocks_coal_stone_bricks.png
new file mode 100644
index 00000000..08e87a74
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_coal_stone_bricks.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_cobble_compressed.png b/mods/moreblocks/moreblocks/textures/moreblocks_cobble_compressed.png
new file mode 100644
index 00000000..94d02b5f
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_cobble_compressed.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_copperpatina.png b/mods/moreblocks/moreblocks/textures/moreblocks_copperpatina.png
new file mode 100644
index 00000000..d4e98b00
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_copperpatina.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_desert_cobble_compressed.png b/mods/moreblocks/moreblocks/textures/moreblocks_desert_cobble_compressed.png
new file mode 100644
index 00000000..307b9b80
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_desert_cobble_compressed.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_dirt_compressed.png b/mods/moreblocks/moreblocks/textures/moreblocks_dirt_compressed.png
new file mode 100644
index 00000000..bdc8220d
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_dirt_compressed.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_empty_shelf.png b/mods/moreblocks/moreblocks/textures/moreblocks_empty_shelf.png
new file mode 100644
index 00000000..bf29124f
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_empty_shelf.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_glass_stairsplus.png b/mods/moreblocks/moreblocks/textures/moreblocks_glass_stairsplus.png
new file mode 100644
index 00000000..8e4cb98d
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_glass_stairsplus.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_glow_glass_stairsplus.png b/mods/moreblocks/moreblocks/textures/moreblocks_glow_glass_stairsplus.png
new file mode 100644
index 00000000..db2831e5
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_glow_glass_stairsplus.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_grey_bricks.png b/mods/moreblocks/moreblocks/textures/moreblocks_grey_bricks.png
new file mode 100644
index 00000000..9839ca25
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_grey_bricks.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_iron_checker.png b/mods/moreblocks/moreblocks/textures/moreblocks_iron_checker.png
new file mode 100644
index 00000000..117cac2d
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_iron_checker.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_iron_glass_stairsplus.png b/mods/moreblocks/moreblocks/textures/moreblocks_iron_glass_stairsplus.png
new file mode 100644
index 00000000..799fbc43
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_iron_glass_stairsplus.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_iron_stone.png b/mods/moreblocks/moreblocks/textures/moreblocks_iron_stone.png
new file mode 100644
index 00000000..5a6e457d
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_iron_stone.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_iron_stone_bricks.png b/mods/moreblocks/moreblocks/textures/moreblocks_iron_stone_bricks.png
new file mode 100644
index 00000000..bc2ef2e5
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_iron_stone_bricks.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_junglestick.png b/mods/moreblocks/moreblocks/textures/moreblocks_junglestick.png
new file mode 100644
index 00000000..7bc6bc44
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_junglestick.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_obsidian_glass_stairsplus.png b/mods/moreblocks/moreblocks/textures/moreblocks_obsidian_glass_stairsplus.png
new file mode 100644
index 00000000..3eb22d0a
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_obsidian_glass_stairsplus.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_plankstone.png b/mods/moreblocks/moreblocks/textures/moreblocks_plankstone.png
new file mode 100644
index 00000000..b1a65c54
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_plankstone.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_plankstone_2.png b/mods/moreblocks/moreblocks/textures/moreblocks_plankstone_2.png
new file mode 100644
index 00000000..953c2f55
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_plankstone_2.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_rope.png b/mods/moreblocks/moreblocks/textures/moreblocks_rope.png
new file mode 100644
index 00000000..f86c07dc
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_rope.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_split_stone_tile.png b/mods/moreblocks/moreblocks/textures/moreblocks_split_stone_tile.png
new file mode 100644
index 00000000..7af4fdda
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_split_stone_tile.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_split_stone_tile_top.png b/mods/moreblocks/moreblocks/textures/moreblocks_split_stone_tile_top.png
new file mode 100644
index 00000000..f3184a86
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_split_stone_tile_top.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_stone_tile.png b/mods/moreblocks/moreblocks/textures/moreblocks_stone_tile.png
new file mode 100644
index 00000000..c2083eaa
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_stone_tile.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_super_glow_glass_stairsplus.png b/mods/moreblocks/moreblocks/textures/moreblocks_super_glow_glass_stairsplus.png
new file mode 100644
index 00000000..c9a2da6e
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_super_glow_glass_stairsplus.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_sweeper.png b/mods/moreblocks/moreblocks/textures/moreblocks_sweeper.png
new file mode 100644
index 00000000..770b988c
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_sweeper.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_tar.png b/mods/moreblocks/moreblocks/textures/moreblocks_tar.png
new file mode 100644
index 00000000..846d068b
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_tar.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_trap_box.png b/mods/moreblocks/moreblocks/textures/moreblocks_trap_box.png
new file mode 100644
index 00000000..6d2aa10f
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_trap_box.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_trap_box_glass.png b/mods/moreblocks/moreblocks/textures/moreblocks_trap_box_glass.png
new file mode 100644
index 00000000..77234d0a
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_trap_box_glass.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_tree_stairsplus.png b/mods/moreblocks/moreblocks/textures/moreblocks_tree_stairsplus.png
new file mode 100644
index 00000000..bed0969c
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_tree_stairsplus.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile.png b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile.png
new file mode 100644
index 00000000..7d9a43c6
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_center.png b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_center.png
new file mode 100644
index 00000000..035b1cb6
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_center.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_full.png b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_full.png
new file mode 100644
index 00000000..edfd5d2c
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_full.png differ
diff --git a/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_offset.png b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_offset.png
new file mode 100644
index 00000000..43795981
Binary files /dev/null and b/mods/moreblocks/moreblocks/textures/moreblocks_wood_tile_offset.png differ
diff --git a/mods/moreblocks/moreblocks_legacy_recipes/.luacheckrc b/mods/moreblocks/moreblocks_legacy_recipes/.luacheckrc
new file mode 100644
index 00000000..e3ec6873
--- /dev/null
+++ b/mods/moreblocks/moreblocks_legacy_recipes/.luacheckrc
@@ -0,0 +1,654 @@
+std = "lua51+luajit+minetest+moreblocks_legacy_recipes"
+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.moreblocks_legacy_recipes = {
+ globals = {
+ },
+ read_globals = {
+ "fmod",
+ },
+}
diff --git a/mods/moreblocks/moreblocks_legacy_recipes/init.lua b/mods/moreblocks/moreblocks_legacy_recipes/init.lua
new file mode 100644
index 00000000..208b254d
--- /dev/null
+++ b/mods/moreblocks/moreblocks_legacy_recipes/init.lua
@@ -0,0 +1,116 @@
+-- disabled by default
+if not minetest.settings:get_bool("moreblocks_legacy_recipes.enabled", false) then
+ return
+end
+
+local modname = minetest.get_current_modname()
+
+-- Redefine some of the default crafting recipes to be more productive
+
+-- Auxiliary function: take a recipe as returned by get_all_craft_recipes
+-- and turn it into a table that can be used to clear a craft or declare a new one
+local reconstruct_internal_craft = function(recipe)
+ local recp = {
+ { "", "", "" },
+ { "", "", "" },
+ { "", "", "" },
+ }
+ local width = recipe.width
+ for idx, item in pairs(recipe.items) do
+ local row = math.ceil(idx / width)
+ local col = idx - (row - 1) * width
+ recp[row][col] = item
+ end
+ return recp
+end
+
+-- Change the amount produced by recipe by apply func to the old amount
+local change_recipe_amount = function(product, recipe, func)
+ -- if width == 0, this is a shapeless recipe, for which the
+ -- internal and Lua API recipe table is the same.
+ -- Otherwise we need to reconstruct the table for the shaped recipe.
+ local shapeless = (recipe.width == 0)
+ local recp = shapeless and recipe.items or reconstruct_internal_craft(recipe)
+
+ local oldamount = tonumber(recipe.output:match(" [0-9]+$") or "1")
+
+ local newamount = func(oldamount)
+
+ -- remove old crafting recipe
+ local redo = { recipe = recp }
+ -- preserve shapelessness
+ if shapeless then
+ redo.type = "shapeless"
+ end
+ minetest.clear_craft(redo)
+
+ -- new output
+ redo.output = ("%s %d"):format(product, newamount)
+ minetest.register_craft(redo)
+
+ minetest.log(
+ "action",
+ ("[MOD]%s: recipe for %s production: %d => %d"):format(modname, product, oldamount, newamount)
+ )
+end
+
+local increase_craft_production = function(product, func)
+ local recipes = minetest.get_all_craft_recipes(product)
+ for _, r in pairs(recipes) do
+ if r.type == "normal" or r.method == "normal" then
+ change_recipe_amount(product, r, func)
+ end
+ end
+end
+
+-- Increase the crafting production according to the rules from the table, which is in the form:
+-- {
+-- { detector, amount changing function }
+-- { detector, amount changing function }
+-- }
+-- TODO: consider exporting this function to other mods
+local increase_craft_production_table = function(map_table)
+ for product, _ in pairs(minetest.registered_items) do
+ for _, tab in pairs(map_table) do
+ local detector = tab[1]
+ local func = tab[2]
+ if detector(product) then
+ increase_craft_production(product, func)
+ -- only apply one boost
+ break
+ end
+ end
+ end
+end
+
+increase_craft_production_table({
+ {
+ function(n)
+ return n:match("^default:sign_wall")
+ end,
+ function(old)
+ return old + 1
+ end,
+ },
+ {
+ function(n)
+ return n == "default:paper"
+ end,
+ function(old)
+ return old * 4
+ end,
+ },
+ {
+ function(n)
+ return n:match("^carts:.*rail$") or n:match("^default:.*rail$")
+ end,
+ function(old)
+ return old + old / 2
+ end,
+ },
+})
+
+minetest.register_craft({
+ type = "toolrepair",
+ additional_wear = -0.10, -- Tool repair buff (10% bonus instead of 2%).
+})
diff --git a/mods/moreblocks/moreblocks_legacy_recipes/mod.conf b/mods/moreblocks/moreblocks_legacy_recipes/mod.conf
new file mode 100644
index 00000000..34ef74d0
--- /dev/null
+++ b/mods/moreblocks/moreblocks_legacy_recipes/mod.conf
@@ -0,0 +1,11 @@
+name = moreblocks_legacy_recipes
+title = moreblocks legacy recipes
+description = adds back recipe changes that used to be part of moreblocks
+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
+min_minetest_version = 5.6.1
+depends = fmod
+optional_depends = carts, default
diff --git a/mods/moreblocks/moreblocks_legacy_recipes/settingtypes.txt b/mods/moreblocks/moreblocks_legacy_recipes/settingtypes.txt
new file mode 100644
index 00000000..579bbf8a
--- /dev/null
+++ b/mods/moreblocks/moreblocks_legacy_recipes/settingtypes.txt
@@ -0,0 +1,2 @@
+# whether to increase the yield of certain crafting recipes as in moreblocks 2.*
+moreblocks_legacy_recipes.enabled (Enable legacy recipes) bool false
diff --git a/mods/moreblocks/screenshot.png b/mods/moreblocks/screenshot.png
new file mode 100644
index 00000000..296b63df
Binary files /dev/null and b/mods/moreblocks/screenshot.png differ
diff --git a/mods/moreblocks/screenshot2.png b/mods/moreblocks/screenshot2.png
new file mode 100644
index 00000000..491e13c0
Binary files /dev/null and b/mods/moreblocks/screenshot2.png differ
diff --git a/mods/moreblocks/screenshot3.png b/mods/moreblocks/screenshot3.png
new file mode 100644
index 00000000..75b1c921
Binary files /dev/null and b/mods/moreblocks/screenshot3.png differ
diff --git a/mods/moreblocks/settingtypes.txt b/mods/moreblocks/settingtypes.txt
new file mode 100644
index 00000000..58acfadb
--- /dev/null
+++ b/mods/moreblocks/settingtypes.txt
@@ -0,0 +1,92 @@
+[invsaw]
+
+# Will be created if it doesn't already exist
+invsaw.priv (Priv to use the inventory saw) string interact
+
+# Will be created if it doesn't already exist
+invsaw.creative_priv (Priv to use the inventory saw w/out a saw item) string creative
+
+# The item that a normal player has to have to use the saw in inventory
+invsaw.saw_item (Saw item) string stairsplus:circular_saw
+
+[moreblocks]
+
+# whether to increase the yield of certain crafting recipes as in moreblocks 2.*
+moreblocks_legacy_recipes.enabled (Enable legacy recipes) bool false
+
+# Add a yellow outline around trap nodes, to make them visibly distinct
+moreblocks.outline_trap_nodes (Outline trap nodes) bool true
+
+[stairs]
+
+# If true, registering stairs w/out a registered recipe item will still create the stair nodes.
+# This is how the stairs mod in minetest game works currently.
+# Unfortunately, they will not be craftable.
+# By default, trying to do this will create an error.
+stairs.legacy_stairs_without_recipeitem (Allow stairs without a base node) bool false
+
+[stairsplus]
+
+# 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
+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
+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
+
+[stairsplus_legacy]
+
+# enable registering stairsplus nodes for basic_materials
+stairsplus_legacy.basic_materials () bool true
+
+# enable registering stairsplus nodes for default
+stairsplus_legacy.default () bool true
+
+# enable registering stairsplus nodes for farming
+stairsplus_legacy.farming () bool true
+
+# enable registering stairsplus nodes for gloopblocks
+stairsplus_legacy.gloopblocks () bool true
+
+# enable registering stairsplus nodes for technic
+stairsplus_legacy.technic () bool true
+
+# enable registering stairsplus nodes for prefab
+stairsplus_legacy.prefab () bool true
+
+# enable registering stairsplus nodes for wool
+stairsplus_legacy.wool () bool true
diff --git a/mods/moreblocks/stairs/.luacheckrc b/mods/moreblocks/stairs/.luacheckrc
new file mode 100644
index 00000000..e1ed320d
--- /dev/null
+++ b/mods/moreblocks/stairs/.luacheckrc
@@ -0,0 +1,656 @@
+std = "lua51+luajit+minetest+stairs"
+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.stairs = {
+ globals = {
+ "stairs",
+ },
+ read_globals = {
+ "fmod",
+ "stairsplus",
+ },
+}
diff --git a/mods/moreblocks/stairs/api.lua b/mods/moreblocks/stairs/api.lua
new file mode 100644
index 00000000..48e8d1c1
--- /dev/null
+++ b/mods/moreblocks/stairs/api.lua
@@ -0,0 +1,216 @@
+local f = string.format
+
+local api = stairsplus.api
+
+local S = stairsplus.S
+
+local legacy = stairs.legacy
+
+local is_legacy_drawtype = stairsplus.compat.is_legacy_drawtype
+local is_legacy_paramtype2 = stairsplus.compat.is_legacy_paramtype2
+
+local default_align_style = stairsplus.settings.default_align_style
+local legacy_stairs_without_recipeitem = stairs.settings.legacy_stairs_without_recipeitem
+
+function stairs.register_stair(subname, node, groups, tiles, description, sounds, worldaligntex)
+ if not minetest.registered_nodes[node] then
+ -- registering a stair for a node that doesn't exist
+ if legacy_stairs_without_recipeitem then
+ legacy.register_stair(subname, node, groups, tiles, description, sounds, worldaligntex)
+ return
+ else
+ error(
+ f(
+ "attempt to register stairs for unknown node %q. "
+ .. "set `stairs.legacy_stairs_without_recipeitem = true` in minetest.conf to enable this behavior.",
+ node
+ )
+ )
+ end
+ end
+
+ local meta = {
+ align_style = worldaligntex and "world" or default_align_style,
+ }
+
+ if is_legacy_drawtype(node) then
+ meta.ignore_drawtype = true
+ end
+ if is_legacy_paramtype2(node) then
+ meta.ignore_paramtype2 = true
+ end
+
+ api.register_single(node, "stair", {
+ groups = groups,
+ tiles = tiles,
+ description = description,
+ sounds = sounds,
+ }, meta)
+
+ minetest.register_alias(("stairs:stair_%s"):format(subname), api.format_name(node, "stair"))
+end
+
+function stairs.register_slab(subname, node, groups, tiles, description, sounds, worldaligntex)
+ if not minetest.registered_nodes[node] then
+ -- registering a stair for a node that doesn't exist
+ if legacy_stairs_without_recipeitem then
+ legacy.register_slab(subname, node, groups, tiles, description, sounds, worldaligntex)
+ return
+ else
+ error(
+ f(
+ "attempt to register stairs for unknown node %q. "
+ .. "set `stairs.legacy_stairs_without_recipeitem = true` in minetest.conf to enable this behavior.",
+ node
+ )
+ )
+ end
+ end
+
+ local meta = {
+ align_style = worldaligntex and "world" or default_align_style,
+ }
+ if is_legacy_drawtype(node) then
+ meta.ignore_drawtype = true
+ end
+ if is_legacy_paramtype2(node) then
+ meta.ignore_paramtype2 = true
+ end
+
+ api.register_single(node, "slab_8", {
+ groups = groups,
+ tiles = tiles,
+ description = description,
+ sounds = sounds,
+ }, meta)
+
+ minetest.register_alias(("stairs:slab_%s"):format(subname), api.format_name(node, "slab_8"))
+end
+
+function stairs.register_stair_inner(subname, node, groups, tiles, description, sounds, worldaligntex, full_description)
+ if not minetest.registered_nodes[node] then
+ -- registering a stair for a node that doesn't exist
+ if legacy_stairs_without_recipeitem then
+ legacy.register_stair_inner(
+ subname,
+ node,
+ groups,
+ tiles,
+ description,
+ sounds,
+ worldaligntex,
+ full_description
+ )
+ return
+ else
+ error(
+ f(
+ "attempt to register stairs for unknown node %q. "
+ .. "set `stairs.legacy_stairs_without_recipeitem = true` in minetest.conf to enable this behavior.",
+ node
+ )
+ )
+ end
+ end
+
+ local meta = {
+ align_style = worldaligntex and "world" or default_align_style,
+ }
+ if is_legacy_drawtype(node) then
+ meta.ignore_drawtype = true
+ end
+ if is_legacy_paramtype2(node) then
+ meta.ignore_paramtype2 = true
+ end
+
+ api.register_single(node, "stair_inner", {
+ groups = groups,
+ tiles = tiles,
+ description = full_description or S("Inner @1", description),
+ sounds = sounds,
+ }, meta)
+
+ minetest.register_alias(("stairs:stair_inner_%s"):format(subname), api.format_name(node, "stair_inner"))
+end
+
+function stairs.register_stair_outer(subname, node, groups, tiles, description, sounds, worldaligntex, full_description)
+ if not minetest.registered_nodes[node] then
+ -- registering a stair for a node that doesn't exist
+ if legacy_stairs_without_recipeitem then
+ legacy.register_stair_outer(
+ subname,
+ node,
+ groups,
+ tiles,
+ description,
+ sounds,
+ worldaligntex,
+ full_description
+ )
+ return
+ else
+ error(
+ f(
+ "attempt to register stairs for unknown node %q. "
+ .. "set `stairs.legacy_stairs_without_recipeitem = true` in minetest.conf to enable this behavior.",
+ node
+ )
+ )
+ end
+ end
+
+ local meta = {
+ align_style = worldaligntex and "world" or default_align_style,
+ }
+ if is_legacy_drawtype(node) then
+ meta.ignore_drawtype = true
+ end
+ if is_legacy_paramtype2(node) then
+ meta.ignore_paramtype2 = true
+ end
+
+ api.register_single(node, "stair_outer", {
+ groups = groups,
+ tiles = tiles,
+ description = full_description or S("Outer @1", description),
+ sounds = sounds,
+ }, meta)
+
+ minetest.register_alias(("stairs:stair_outer_%s"):format(subname), api.format_name(node, "stair_outer"))
+end
+
+function stairs.register_stair_and_slab(
+ subname,
+ recipeitem,
+ groups,
+ images,
+ desc_stair,
+ desc_slab,
+ sounds,
+ worldaligntex,
+ desc_stair_inner,
+ desc_stair_outer
+)
+ stairs.register_stair(subname, recipeitem, groups, images, desc_stair, sounds, worldaligntex)
+ stairs.register_slab(subname, recipeitem, groups, images, desc_slab, sounds, worldaligntex)
+ stairs.register_stair_inner(
+ subname,
+ recipeitem,
+ groups,
+ images,
+ desc_stair,
+ sounds,
+ worldaligntex,
+ desc_stair_inner
+ )
+ stairs.register_stair_outer(
+ subname,
+ recipeitem,
+ groups,
+ images,
+ desc_stair,
+ sounds,
+ worldaligntex,
+ desc_stair_outer
+ )
+end
diff --git a/mods/moreblocks/stairs/compat.lua b/mods/moreblocks/stairs/compat.lua
new file mode 100644
index 00000000..35d03348
--- /dev/null
+++ b/mods/moreblocks/stairs/compat.lua
@@ -0,0 +1,4 @@
+-- for very, very old worlds...
+
+minetest.register_alias("stairs:stair_pinewood", "stairs:stair_pine_wood")
+minetest.register_alias("stairs:slab_pinewood", "stairs:slab_pine_wood")
diff --git a/mods/moreblocks/stairs/init.lua b/mods/moreblocks/stairs/init.lua
new file mode 100644
index 00000000..5168dea7
--- /dev/null
+++ b/mods/moreblocks/stairs/init.lua
@@ -0,0 +1,6 @@
+stairs = fmod.create()
+
+stairs.dofile("util")
+stairs.dofile("legacy")
+stairs.dofile("api")
+stairs.dofile("compat")
diff --git a/mods/moreblocks/stairs/legacy.lua b/mods/moreblocks/stairs/legacy.lua
new file mode 100644
index 00000000..9a35c85f
--- /dev/null
+++ b/mods/moreblocks/stairs/legacy.lua
@@ -0,0 +1,290 @@
+local rotate_and_place = minetest.rotate_and_place
+
+local get_node_vars = stairs.util.get_node_vars
+local get_stair_images = stairs.util.get_stair_images
+
+local legacy = {}
+
+local nodeboxes = {
+ stair = {
+ type = "fixed",
+ fixed = {
+ { -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
+ { -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 },
+ },
+ },
+ slab = {
+ type = "fixed",
+ fixed = { -0.5, -0.5, -0.5, 0.5, 0, 0.5 },
+ },
+ stair_inner = {
+ type = "fixed",
+ fixed = {
+ { -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
+ { -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 },
+ { -0.5, 0.0, -0.5, 0.0, 0.5, 0.0 },
+ },
+ },
+ stair_outer = {
+ type = "fixed",
+ fixed = {
+ { -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
+ { -0.5, 0.0, 0.0, 0.0, 0.5, 0.5 },
+ },
+ },
+}
+
+local function register_generic(name, recipeitem, groups, images, description, sounds, worldaligntex, nodebox)
+ if not nodebox then
+ error()
+ end
+
+ local light_source, texture_alpha, sunlight = get_node_vars(recipeitem)
+
+ local stair_images = get_stair_images(images, worldaligntex)
+
+ minetest.register_node(name, {
+ description = description,
+ drawtype = "nodebox",
+ tiles = stair_images,
+ use_texture_alpha = texture_alpha,
+ sunlight_propagates = sunlight,
+ light_source = light_source,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ groups = groups,
+ sounds = sounds,
+ node_box = nodebox,
+ on_place = function(itemstack, placer, pointed_thing)
+ if pointed_thing.type ~= "node" then
+ return itemstack
+ end
+
+ return rotate_and_place(itemstack, placer, pointed_thing)
+ end,
+ })
+end
+
+function legacy.register_stair(subname, recipeitem, groups, images, description, sounds, worldaligntex)
+ local new_groups = table.copy(groups)
+ new_groups.stair = 1
+
+ register_generic(
+ ":stairs:stair_" .. subname,
+ recipeitem,
+ new_groups,
+ images,
+ description,
+ sounds,
+ worldaligntex,
+ nodeboxes.stair
+ )
+
+ if recipeitem and minetest.registered_nodes[recipeitem] then
+ -- Recipe matches appearence in inventory
+ minetest.register_craft({
+ output = "stairs:stair_" .. subname .. " 8",
+ recipe = {
+ { "", "", recipeitem },
+ { "", recipeitem, recipeitem },
+ { recipeitem, recipeitem, recipeitem },
+ },
+ })
+
+ -- Use stairs to craft full blocks again (1:1)
+ minetest.register_craft({
+ output = recipeitem .. " 3",
+ recipe = {
+ { "stairs:stair_" .. subname, "stairs:stair_" .. subname },
+ { "stairs:stair_" .. subname, "stairs:stair_" .. subname },
+ },
+ })
+
+ -- Fuel
+ local baseburntime = minetest.get_craft_result({
+ method = "fuel",
+ width = 1,
+ items = { recipeitem },
+ }).time
+
+ if baseburntime > 0 then
+ minetest.register_craft({
+ type = "fuel",
+ recipe = "stairs:stair_" .. subname,
+ burntime = math.floor(baseburntime * 0.75),
+ })
+ end
+ end
+end
+
+-- Register slab
+-- Node will be called stairs:slab_
+
+function legacy.register_slab(subname, recipeitem, groups, images, description, sounds, worldaligntex)
+ local new_groups = table.copy(groups)
+ new_groups.slab = 1
+
+ register_generic(
+ ":stairs:slab_" .. subname,
+ recipeitem,
+ new_groups,
+ images,
+ description,
+ sounds,
+ worldaligntex,
+ nodeboxes.slab
+ )
+
+ if recipeitem and minetest.registered_nodes[recipeitem] then
+ minetest.register_craft({
+ output = "stairs:slab_" .. subname .. " 6",
+ recipe = {
+ { recipeitem, recipeitem, recipeitem },
+ },
+ })
+
+ -- Use 2 slabs to craft a full block again (1:1)
+ minetest.register_craft({
+ output = recipeitem,
+ recipe = {
+ { "stairs:slab_" .. subname },
+ { "stairs:slab_" .. subname },
+ },
+ })
+
+ -- Fuel
+ local baseburntime = minetest.get_craft_result({
+ method = "fuel",
+ width = 1,
+ items = { recipeitem },
+ }).time
+ if baseburntime > 0 then
+ minetest.register_craft({
+ type = "fuel",
+ recipe = "stairs:slab_" .. subname,
+ burntime = math.floor(baseburntime * 0.5),
+ })
+ end
+ end
+end
+
+-- Register inner stair
+-- Node will be called stairs:stair_inner_
+
+function legacy.register_stair_inner(
+ subname,
+ recipeitem,
+ groups,
+ images,
+ description,
+ sounds,
+ worldaligntex,
+ full_description
+)
+ local new_groups = table.copy(groups)
+ new_groups.stair = 1
+ if full_description then
+ description = full_description
+ else
+ description = stairs.S("Inner ") .. description
+ end
+
+ register_generic(
+ ":stairs:stair_inner_" .. subname,
+ recipeitem,
+ new_groups,
+ images,
+ description,
+ sounds,
+ worldaligntex,
+ nodeboxes.stair_inner
+ )
+
+ if recipeitem and minetest.registered_nodes[recipeitem] then
+ minetest.register_craft({
+ output = "stairs:stair_inner_" .. subname .. " 7",
+ recipe = {
+ { "", recipeitem, "" },
+ { recipeitem, "", recipeitem },
+ { recipeitem, recipeitem, recipeitem },
+ },
+ })
+
+ -- Fuel
+ local baseburntime = minetest.get_craft_result({
+ method = "fuel",
+ width = 1,
+ items = { recipeitem },
+ }).time
+
+ if baseburntime > 0 then
+ minetest.register_craft({
+ type = "fuel",
+ recipe = "stairs:stair_inner_" .. subname,
+ burntime = math.floor(baseburntime * 0.875),
+ })
+ end
+ end
+end
+
+-- Register outer stair
+-- Node will be called stairs:stair_outer_
+
+function legacy.register_stair_outer(
+ subname,
+ recipeitem,
+ groups,
+ images,
+ description,
+ sounds,
+ worldaligntex,
+ full_description
+)
+ local new_groups = table.copy(groups)
+ new_groups.stair = 1
+
+ if full_description then
+ description = full_description
+ else
+ description = stairs.S("Outer ") .. description
+ end
+
+ register_generic(
+ ":stairs:stair_outer_" .. subname,
+ recipeitem,
+ new_groups,
+ images,
+ description,
+ sounds,
+ worldaligntex,
+ nodeboxes.stair_outer
+ )
+
+ if recipeitem and minetest.registered_nodes[recipeitem] then
+ minetest.register_craft({
+ output = "stairs:stair_outer_" .. subname .. " 6",
+ recipe = {
+ { "", recipeitem, "" },
+ { recipeitem, recipeitem, recipeitem },
+ },
+ })
+
+ -- Fuel
+ local baseburntime = minetest.get_craft_result({
+ method = "fuel",
+ width = 1,
+ items = { recipeitem },
+ }).time
+
+ if baseburntime > 0 then
+ minetest.register_craft({
+ type = "fuel",
+ recipe = "stairs:stair_outer_" .. subname,
+ burntime = math.floor(baseburntime * 0.625),
+ })
+ end
+ end
+end
+
+stairs.legacy = legacy
diff --git a/mods/moreblocks/stairs/locale/stairs.ru.tr b/mods/moreblocks/stairs/locale/stairs.ru.tr
new file mode 100644
index 00000000..99ff8c15
--- /dev/null
+++ b/mods/moreblocks/stairs/locale/stairs.ru.tr
@@ -0,0 +1,4 @@
+# textdomain: stairs
+
+Inner =Внутренние
+Outer =Внешние
diff --git a/mods/moreblocks/stairs/locale/template.txt b/mods/moreblocks/stairs/locale/template.txt
new file mode 100644
index 00000000..c6407ee2
--- /dev/null
+++ b/mods/moreblocks/stairs/locale/template.txt
@@ -0,0 +1,4 @@
+# textdomain: stairs
+
+Inner =
+Outer =
diff --git a/mods/moreblocks/stairs/mod.conf b/mods/moreblocks/stairs/mod.conf
new file mode 100644
index 00000000..d69795a1
--- /dev/null
+++ b/mods/moreblocks/stairs/mod.conf
@@ -0,0 +1,9 @@
+name = stairs
+title = stairs compatibility layer for stairs+
+description = stair 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, stairsplus
diff --git a/mods/moreblocks/stairs/settingtypes.txt b/mods/moreblocks/stairs/settingtypes.txt
new file mode 100644
index 00000000..f2280283
--- /dev/null
+++ b/mods/moreblocks/stairs/settingtypes.txt
@@ -0,0 +1,5 @@
+# If true, registering stairs w/out a registered recipe item will still create the stair nodes.
+# This is how the stairs mod in minetest game works currently.
+# Unfortunately, they will not be craftable.
+# By default, trying to do this will create an error.
+stairs.legacy_stairs_without_recipeitem (Allow stairs without a base node) bool true
diff --git a/mods/moreblocks/stairs/util.lua b/mods/moreblocks/stairs/util.lua
new file mode 100644
index 00000000..54fbe9a3
--- /dev/null
+++ b/mods/moreblocks/stairs/util.lua
@@ -0,0 +1,46 @@
+local default_align_style = stairsplus.settings.default_align_style
+
+local util = {}
+
+-- get node settings to use for stairs
+function util.get_node_vars(nodename)
+ local def = minetest.registered_nodes[nodename]
+
+ if def then
+ return def.light_source, def.use_texture_alpha, def.sunlight_propagates
+ end
+end
+
+function util.get_stair_images(images, worldaligntex)
+ local stair_images = {}
+
+ for i, image in ipairs(images) do
+ local stair_image
+
+ if type(image) == "string" then
+ stair_image = {
+ name = image,
+ backface_culling = true,
+ }
+ else
+ stair_image = table.copy(image)
+ if stair_image.backface_culling == nil then
+ stair_image.backface_culling = true
+ end
+ end
+
+ if stair_image.align_style == nil then
+ if worldaligntex then
+ stair_image.align_style = "world"
+ else
+ stair_image.align_style = default_align_style
+ end
+ end
+
+ stair_images[i] = stair_image
+ end
+
+ return stair_images
+end
+
+stairs.util = util
diff --git a/mods/moreblocks/stairsplus/.luacheckrc b/mods/moreblocks/stairsplus/.luacheckrc
new file mode 100644
index 00000000..6f638197
--- /dev/null
+++ b/mods/moreblocks/stairsplus/.luacheckrc
@@ -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",
+ },
+}
diff --git a/mods/moreblocks/stairsplus/API.md b/mods/moreblocks/stairsplus/API.md
new file mode 100644
index 00000000..1e8534f6
--- /dev/null
+++ b/mods/moreblocks/stairsplus/API.md
@@ -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)`
diff --git a/mods/moreblocks/stairsplus/aliases.lua b/mods/moreblocks/stairsplus/aliases.lua
new file mode 100644
index 00000000..e31f4da7
--- /dev/null
+++ b/mods/moreblocks/stairsplus/aliases.lua
@@ -0,0 +1,2 @@
+minetest.register_alias("circular_saw", "stairsplus:circular_saw")
+minetest.register_alias("moreblocks:circular_saw", "stairsplus:circular_saw")
diff --git a/mods/moreblocks/stairsplus/api/alias.lua b/mods/moreblocks/stairsplus/api/alias.lua
new file mode 100644
index 00000000..cd9d1ed8
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/alias.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/api/group_filters.lua b/mods/moreblocks/stairsplus/api/group_filters.lua
new file mode 100644
index 00000000..72826fbe
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/group_filters.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/api/init.lua b/mods/moreblocks/stairsplus/api/init.lua
new file mode 100644
index 00000000..d976077e
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/init.lua
@@ -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")
diff --git a/mods/moreblocks/stairsplus/api/legacy.lua b/mods/moreblocks/stairsplus/api/legacy.lua
new file mode 100644
index 00000000..65e7fe2d
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/legacy.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/api/node.lua b/mods/moreblocks/stairsplus/api/node.lua
new file mode 100644
index 00000000..62aef545
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/node.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/api/recipe.lua b/mods/moreblocks/stairsplus/api/recipe.lua
new file mode 100644
index 00000000..7662a731
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/recipe.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/api/shape.lua b/mods/moreblocks/stairsplus/api/shape.lua
new file mode 100644
index 00000000..517e6bd2
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/shape.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/api/station.lua b/mods/moreblocks/stairsplus/api/station.lua
new file mode 100644
index 00000000..996946c3
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/station.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/api/whitelist.lua b/mods/moreblocks/stairsplus/api/whitelist.lua
new file mode 100644
index 00000000..9f0729d0
--- /dev/null
+++ b/mods/moreblocks/stairsplus/api/whitelist.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/circular_saw.lua b/mods/moreblocks/stairsplus/circular_saw.lua
new file mode 100644
index 00000000..bedff923
--- /dev/null
+++ b/mods/moreblocks/stairsplus/circular_saw.lua
@@ -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,
+})
diff --git a/mods/moreblocks/stairsplus/compat/i3.lua b/mods/moreblocks/stairsplus/compat/i3.lua
new file mode 100644
index 00000000..b1beb624
--- /dev/null
+++ b/mods/moreblocks/stairsplus/compat/i3.lua
@@ -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)
diff --git a/mods/moreblocks/stairsplus/compat/init.lua b/mods/moreblocks/stairsplus/compat/init.lua
new file mode 100644
index 00000000..14e28230
--- /dev/null
+++ b/mods/moreblocks/stairsplus/compat/init.lua
@@ -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")
diff --git a/mods/moreblocks/stairsplus/compat/old_moreblocks.lua b/mods/moreblocks/stairsplus/compat/old_moreblocks.lua
new file mode 100644
index 00000000..d9f2613c
--- /dev/null
+++ b/mods/moreblocks/stairsplus/compat/old_moreblocks.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/compat/unified_inventory.lua b/mods/moreblocks/stairsplus/compat/unified_inventory.lua
new file mode 100644
index 00000000..10f43895
--- /dev/null
+++ b/mods/moreblocks/stairsplus/compat/unified_inventory.lua
@@ -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)
diff --git a/mods/moreblocks/stairsplus/craft_schemas/init.lua b/mods/moreblocks/stairsplus/craft_schemas/init.lua
new file mode 100644
index 00000000..232bb069
--- /dev/null
+++ b/mods/moreblocks/stairsplus/craft_schemas/init.lua
@@ -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")
diff --git a/mods/moreblocks/stairsplus/craft_schemas/micro_8_all.lua b/mods/moreblocks/stairsplus/craft_schemas/micro_8_all.lua
new file mode 100644
index 00000000..2415860f
--- /dev/null
+++ b/mods/moreblocks/stairsplus/craft_schemas/micro_8_all.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/craft_schemas/other.lua b/mods/moreblocks/stairsplus/craft_schemas/other.lua
new file mode 100644
index 00000000..9b21362c
--- /dev/null
+++ b/mods/moreblocks/stairsplus/craft_schemas/other.lua
@@ -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" },
+})
diff --git a/mods/moreblocks/stairsplus/craft_schemas/slopes.lua b/mods/moreblocks/stairsplus/craft_schemas/slopes.lua
new file mode 100644
index 00000000..7cd67947
--- /dev/null
+++ b/mods/moreblocks/stairsplus/craft_schemas/slopes.lua
@@ -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" },
+})
diff --git a/mods/moreblocks/stairsplus/craft_schemas/stairs.lua b/mods/moreblocks/stairsplus/craft_schemas/stairs.lua
new file mode 100644
index 00000000..60409351
--- /dev/null
+++ b/mods/moreblocks/stairsplus/craft_schemas/stairs.lua
@@ -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", "" },
+ },
+})
diff --git a/mods/moreblocks/stairsplus/craft_schemas/standard_composition.lua b/mods/moreblocks/stairsplus/craft_schemas/standard_composition.lua
new file mode 100644
index 00000000..d603f658
--- /dev/null
+++ b/mods/moreblocks/stairsplus/craft_schemas/standard_composition.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/groups/builtin.lua b/mods/moreblocks/stairsplus/groups/builtin.lua
new file mode 100644
index 00000000..05e4798f
--- /dev/null
+++ b/mods/moreblocks/stairsplus/groups/builtin.lua
@@ -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",
+})
diff --git a/mods/moreblocks/stairsplus/groups/default.lua b/mods/moreblocks/stairsplus/groups/default.lua
new file mode 100644
index 00000000..e328309b
--- /dev/null
+++ b/mods/moreblocks/stairsplus/groups/default.lua
@@ -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
+})
diff --git a/mods/moreblocks/stairsplus/groups/init.lua b/mods/moreblocks/stairsplus/groups/init.lua
new file mode 100644
index 00000000..d9d2a83f
--- /dev/null
+++ b/mods/moreblocks/stairsplus/groups/init.lua
@@ -0,0 +1,3 @@
+stairsplus.dofile("groups", "builtin")
+stairsplus.dofile("groups", "default")
+stairsplus.dofile("groups", "other")
diff --git a/mods/moreblocks/stairsplus/groups/other.lua b/mods/moreblocks/stairsplus/groups/other.lua
new file mode 100644
index 00000000..566321dd
--- /dev/null
+++ b/mods/moreblocks/stairsplus/groups/other.lua
@@ -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",
+})
diff --git a/mods/moreblocks/stairsplus/init.lua b/mods/moreblocks/stairsplus/init.lua
new file mode 100644
index 00000000..935b853e
--- /dev/null
+++ b/mods/moreblocks/stairsplus/init.lua
@@ -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")
diff --git a/mods/moreblocks/stairsplus/locale/default.txt b/mods/moreblocks/stairsplus/locale/default.txt
new file mode 100644
index 00000000..9598c830
--- /dev/null
+++ b/mods/moreblocks/stairsplus/locale/default.txt
@@ -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=
diff --git a/mods/moreblocks/stairsplus/locale/stairsplus.ru.tr b/mods/moreblocks/stairsplus/locale/stairsplus.ru.tr
new file mode 100644
index 00000000..58881401
--- /dev/null
+++ b/mods/moreblocks/stairsplus/locale/stairsplus.ru.tr
@@ -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 альт. ступени)
diff --git a/mods/moreblocks/stairsplus/mod.conf b/mods/moreblocks/stairsplus/mod.conf
new file mode 100644
index 00000000..4cc92399
--- /dev/null
+++ b/mods/moreblocks/stairsplus/mod.conf
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope.obj
new file mode 100644
index 00000000..57298d7d
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_cut.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_cut.obj
new file mode 100644
index 00000000..bf2dd79f
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_cut.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_half.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_half.obj
new file mode 100644
index 00000000..1fa631ce
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_half.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_half_raised.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_half_raised.obj
new file mode 100644
index 00000000..86139d7b
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_half_raised.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_inner.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner.obj
new file mode 100644
index 00000000..c6f811e2
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut.obj
new file mode 100644
index 00000000..babe502c
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut_half.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut_half.obj
new file mode 100644
index 00000000..5af38b58
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut_half.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut_half_raised.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut_half_raised.obj
new file mode 100644
index 00000000..d8c1f81f
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_cut_half_raised.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_half.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_half.obj
new file mode 100644
index 00000000..3158b573
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_half.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_half_raised.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_half_raised.obj
new file mode 100644
index 00000000..bd413897
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_inner_half_raised.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_outer.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer.obj
new file mode 100644
index 00000000..c779309c
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut.obj
new file mode 100644
index 00000000..4dcd46af
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut_half.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut_half.obj
new file mode 100644
index 00000000..c309a4e4
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut_half.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut_half_raised.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut_half_raised.obj
new file mode 100644
index 00000000..78cdfede
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_cut_half_raised.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_half.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_half.obj
new file mode 100644
index 00000000..b87be69a
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_half.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_half_raised.obj b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_half_raised.obj
new file mode 100644
index 00000000..e06304d0
--- /dev/null
+++ b/mods/moreblocks/stairsplus/models/stairsplus_slope_outer_half_raised.obj
@@ -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
diff --git a/mods/moreblocks/stairsplus/resources/craft_materials.lua b/mods/moreblocks/stairsplus/resources/craft_materials.lua
new file mode 100644
index 00000000..8a6e3ad8
--- /dev/null
+++ b/mods/moreblocks/stairsplus/resources/craft_materials.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/resources/formspec_styling.lua b/mods/moreblocks/stairsplus/resources/formspec_styling.lua
new file mode 100644
index 00000000..21e7f1a4
--- /dev/null
+++ b/mods/moreblocks/stairsplus/resources/formspec_styling.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/resources/init.lua b/mods/moreblocks/stairsplus/resources/init.lua
new file mode 100644
index 00000000..8b13d259
--- /dev/null
+++ b/mods/moreblocks/stairsplus/resources/init.lua
@@ -0,0 +1,4 @@
+stairsplus.resources = {}
+
+stairsplus.dofile("resources", "craft_materials")
+stairsplus.dofile("resources", "sounds")
diff --git a/mods/moreblocks/stairsplus/resources/sounds.lua b/mods/moreblocks/stairsplus/resources/sounds.lua
new file mode 100644
index 00000000..1754d8b4
--- /dev/null
+++ b/mods/moreblocks/stairsplus/resources/sounds.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus/scripts/create_whitelist.py b/mods/moreblocks/stairsplus/scripts/create_whitelist.py
new file mode 100644
index 00000000..48fe01c1
--- /dev/null
+++ b/mods/moreblocks/stairsplus/scripts/create_whitelist.py
@@ -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())
diff --git a/mods/moreblocks/stairsplus/scripts/init.lua b/mods/moreblocks/stairsplus/scripts/init.lua
new file mode 100644
index 00000000..3098e74f
--- /dev/null
+++ b/mods/moreblocks/stairsplus/scripts/init.lua
@@ -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,
+})
diff --git a/mods/moreblocks/stairsplus/scripts/pymtdb.py b/mods/moreblocks/stairsplus/scripts/pymtdb.py
new file mode 100644
index 00000000..0fdbc309
--- /dev/null
+++ b/mods/moreblocks/stairsplus/scripts/pymtdb.py
@@ -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
diff --git a/mods/moreblocks/stairsplus/scripts/requirements.txt b/mods/moreblocks/stairsplus/scripts/requirements.txt
new file mode 100644
index 00000000..600533d3
--- /dev/null
+++ b/mods/moreblocks/stairsplus/scripts/requirements.txt
@@ -0,0 +1,3 @@
+progressbar2
+psycopg2
+pyzstd
diff --git a/mods/moreblocks/stairsplus/scripts/stream.py b/mods/moreblocks/stairsplus/scripts/stream.py
new file mode 100644
index 00000000..0df45ed3
--- /dev/null
+++ b/mods/moreblocks/stairsplus/scripts/stream.py
@@ -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)
diff --git a/mods/moreblocks/stairsplus/scripts/translate_schems.py b/mods/moreblocks/stairsplus/scripts/translate_schems.py
new file mode 100644
index 00000000..5019635d
--- /dev/null
+++ b/mods/moreblocks/stairsplus/scripts/translate_schems.py
@@ -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())
diff --git a/mods/moreblocks/stairsplus/scripts/whitelist_helpers.py b/mods/moreblocks/stairsplus/scripts/whitelist_helpers.py
new file mode 100644
index 00000000..bd72cbe9
--- /dev/null
+++ b/mods/moreblocks/stairsplus/scripts/whitelist_helpers.py
@@ -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
diff --git a/mods/moreblocks/stairsplus/settingtypes.txt b/mods/moreblocks/stairsplus/settingtypes.txt
new file mode 100644
index 00000000..1971090d
--- /dev/null
+++ b/mods/moreblocks/stairsplus/settingtypes.txt
@@ -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
diff --git a/mods/moreblocks/stairsplus/shapes/init.lua b/mods/moreblocks/stairsplus/shapes/init.lua
new file mode 100644
index 00000000..7a04245d
--- /dev/null
+++ b/mods/moreblocks/stairsplus/shapes/init.lua
@@ -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)
diff --git a/mods/moreblocks/stairsplus/shapes/micros.lua b/mods/moreblocks/stairsplus/shapes/micros.lua
new file mode 100644
index 00000000..d01cf922
--- /dev/null
+++ b/mods/moreblocks/stairsplus/shapes/micros.lua
@@ -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 },
+ },
+})
diff --git a/mods/moreblocks/stairsplus/shapes/panels.lua b/mods/moreblocks/stairsplus/shapes/panels.lua
new file mode 100644
index 00000000..0740b85e
--- /dev/null
+++ b/mods/moreblocks/stairsplus/shapes/panels.lua
@@ -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 },
+ },
+})
diff --git a/mods/moreblocks/stairsplus/shapes/slabs.lua b/mods/moreblocks/stairsplus/shapes/slabs.lua
new file mode 100644
index 00000000..e79106c9
--- /dev/null
+++ b/mods/moreblocks/stairsplus/shapes/slabs.lua
@@ -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 },
+ },
+ },
+})
diff --git a/mods/moreblocks/stairsplus/shapes/slopes.lua b/mods/moreblocks/stairsplus/shapes/slopes.lua
new file mode 100644
index 00000000..49db4400
--- /dev/null
+++ b/mods/moreblocks/stairsplus/shapes/slopes.lua
@@ -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,
+})
diff --git a/mods/moreblocks/stairsplus/shapes/stairs.lua b/mods/moreblocks/stairsplus/shapes/stairs.lua
new file mode 100644
index 00000000..7b3fc889
--- /dev/null
+++ b/mods/moreblocks/stairsplus/shapes/stairs.lua
@@ -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 },
+ },
+ },
+})
diff --git a/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_bottom.png b/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_bottom.png
new file mode 100644
index 00000000..1a67636d
Binary files /dev/null and b/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_bottom.png differ
diff --git a/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_side.png b/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_side.png
new file mode 100644
index 00000000..567082bb
Binary files /dev/null and b/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_side.png differ
diff --git a/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_top.png b/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_top.png
new file mode 100644
index 00000000..1b491da2
Binary files /dev/null and b/mods/moreblocks/stairsplus/textures/stairsplus_circular_saw_top.png differ
diff --git a/mods/moreblocks/stairsplus/textures/stairsplus_saw_button.png b/mods/moreblocks/stairsplus/textures/stairsplus_saw_button.png
new file mode 100644
index 00000000..8ac80935
Binary files /dev/null and b/mods/moreblocks/stairsplus/textures/stairsplus_saw_button.png differ
diff --git a/mods/moreblocks/stairsplus/util.lua b/mods/moreblocks/stairsplus/util.lua
new file mode 100644
index 00000000..39aba173
--- /dev/null
+++ b/mods/moreblocks/stairsplus/util.lua
@@ -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
diff --git a/mods/moreblocks/stairsplus_legacy/.luacheckrc b/mods/moreblocks/stairsplus_legacy/.luacheckrc
new file mode 100644
index 00000000..dc32e5b1
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/.luacheckrc
@@ -0,0 +1,658 @@
+std = "lua51+luajit+minetest+stairsplus_legacy"
+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_legacy = {
+ globals = {
+ "stairsplus_legacy",
+ },
+ read_globals = {
+ "default",
+ "fmod",
+ "stairs",
+ "stairsplus",
+ },
+}
diff --git a/mods/moreblocks/stairsplus_legacy/basic_materials.lua b/mods/moreblocks/stairsplus_legacy/basic_materials.lua
new file mode 100644
index 00000000..89a160d2
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/basic_materials.lua
@@ -0,0 +1,30 @@
+local materials = stairsplus_legacy.materials
+
+-- old moreblocks registered things *terribly*, hence these extra aliases
+if not stairsplus_legacy.has.gloopblocks then
+ stairsplus.api.register_alias_all("gloopblocks:cement", "basic_materials:cement_block")
+end
+
+-- old moreblocks registered things *terribly*, hence these extra aliases
+if not stairsplus_legacy.has.technic then
+ stairsplus.api.register_alias_all("technic:brass_block", "basic_materials:brass_block")
+ stairsplus.api.register_alias_all("technic:concrete", "basic_materials:concrete_block")
+end
+
+if materials.brass_block and materials.brass_block ~= "basic_materials:brass_block" then
+ stairsplus.api.register_alias_all("basic_materials:brass_block", materials.brass_block)
+elseif minetest.registered_nodes["basic_materials:brass_block"] then
+ stairsplus_legacy.register_legacy("basic_materials:brass_block")
+end
+
+if materials.cement_block and materials.cement_block ~= "basic_materials:cement_block" then
+ stairsplus.api.register_alias_all("basic_materials:cement_block", materials.cement_block)
+elseif minetest.registered_nodes["basic_materials:cement_block"] then
+ stairsplus_legacy.register_legacy("basic_materials:cement_block")
+end
+
+if materials.concrete_block and materials.concrete_block ~= "basic_materials:concrete_block" then
+ stairsplus.api.register_alias_all("basic_materials:concrete_block", materials.concrete_block)
+elseif minetest.registered_nodes["basic_materials:concrete_block"] then
+ stairsplus_legacy.register_legacy("basic_materials:concrete_block")
+end
diff --git a/mods/moreblocks/stairsplus_legacy/default.lua b/mods/moreblocks/stairsplus_legacy/default.lua
new file mode 100644
index 00000000..a2f8f5bd
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/default.lua
@@ -0,0 +1,193 @@
+local default_nodes = { -- Default stairs/slabs/panels/microblocks:
+ "acacia_tree",
+ "acacia_wood",
+ "aspen_tree",
+ "aspen_wood",
+ "brick",
+ "bronzeblock",
+ "cobble",
+ "copperblock",
+ "coral_skeleton",
+ "default:ice",
+ "desert_cobble",
+ "desert_sandstone",
+ "desert_sandstone_block",
+ "desert_sandstone_brick",
+ "desert_stone",
+ "desert_stone_block",
+ "desert_stonebrick",
+ "diamondblock",
+ "goldblock",
+ "ice",
+ "jungletree",
+ "junglewood",
+ "meselamp",
+ "mossycobble",
+ "obsidian",
+ "obsidian_block",
+ "obsidianbrick",
+ "pine_tree",
+ "pine_wood",
+ "sandstone",
+ "sandstone_block",
+ "sandstonebrick",
+ "silver_sandstone",
+ "silver_sandstone_block",
+ "silver_sandstone_brick",
+ "snowblock",
+ "steelblock",
+ "stone",
+ "stone_block",
+ "stonebrick",
+ "tinblock",
+ "tree",
+ "wood",
+}
+
+for _, name in ipairs(default_nodes) do
+ local node = ("default:%s"):format(name)
+ if minetest.registered_nodes[node] then
+ stairsplus_legacy.register_legacy(node)
+ stairsplus.api.register_alias_all(("moreblocks:%s"):format(name), node)
+ stairsplus.api.register_alias_group(("stairs:%s"):format(name), node, "stairs_legacy")
+ end
+end
+
+-- glass problems
+local glass = {
+ "glass",
+ "obsidian_glass",
+}
+
+for _, name in ipairs(glass) do
+ local node = ("default:%s"):format(name)
+ if minetest.registered_nodes[node] then
+ stairsplus_legacy.register_legacy(node, nil, { ignore_paramtype2 = true })
+ stairsplus.api.register_alias_all(("moreblocks:%s"):format(name), node)
+ stairsplus.api.register_alias_group(("stairs:%s"):format(name), node, "stairs_legacy")
+ end
+end
+
+if minetest.registered_nodes["default:glass"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "cooking",
+ output = "default:glass",
+ recipe = "group:sand",
+ })
+end
+
+if minetest.registered_nodes["default:stone"] and minetest.registered_nodes["default:cobble"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "cooking",
+ output = "default:stone",
+ recipe = "default:cobble",
+ })
+end
+
+if minetest.registered_nodes["default:stone"] and minetest.registered_nodes["default:mossycobble"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "cooking",
+ output = "default:stone",
+ recipe = "default:mossycobble",
+ })
+end
+
+if minetest.registered_nodes["default:desert_stone"] and minetest.registered_nodes["default:desert_cobble"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "cooking",
+ output = "default:desert_stone",
+ recipe = "default:desert_cobble",
+ })
+end
+
+stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "group:tree",
+ burntime = 30,
+})
+
+if minetest.registered_nodes["default:tree"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:tree",
+ burntime = 30,
+ })
+end
+
+if minetest.registered_nodes["default:aspen_tree"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:aspen_tree",
+ burntime = 22,
+ })
+end
+
+if minetest.registered_nodes["default:pine_tree"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:pine_tree",
+ burntime = 26,
+ })
+end
+
+if minetest.registered_nodes["default:acacia_tree"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:acacia_tree",
+ burntime = 34,
+ })
+end
+
+if minetest.registered_nodes["default:jungletree"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:jungletree",
+ burntime = 38,
+ })
+end
+
+stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "group:wood",
+ burntime = 7,
+})
+
+if minetest.registered_nodes["default:wood"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:wood",
+ burntime = 7,
+ })
+end
+
+if minetest.registered_nodes["default:aspen_wood"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:aspen_wood",
+ burntime = 5,
+ })
+end
+
+if minetest.registered_nodes["default:pine_wood"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:pine_wood",
+ burntime = 6,
+ })
+end
+
+if minetest.registered_nodes["default:acacia_wood"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:acacia_wood",
+ burntime = 8,
+ })
+end
+
+if minetest.registered_nodes["default:junglewood"] then
+ stairsplus.api.register_crafts_for_shapes({
+ type = "fuel",
+ recipe = "default:junglewood",
+ burntime = 9,
+ })
+end
diff --git a/mods/moreblocks/stairsplus_legacy/farming.lua b/mods/moreblocks/stairsplus_legacy/farming.lua
new file mode 100644
index 00000000..5a164ceb
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/farming.lua
@@ -0,0 +1,9 @@
+local farming_nodes = { "straw" }
+for _, name in pairs(farming_nodes) do
+ local node = ("farming:%s"):format(name)
+ if minetest.registered_nodes[node] then
+ stairsplus_legacy.register_legacy(node)
+ stairsplus.api.register_alias_all(("moreblocks:%s"):format(name), node)
+ stairsplus.api.register_alias_group(("stairs:%s"):format(name), node, "stairs_legacy")
+ end
+end
diff --git a/mods/moreblocks/stairsplus_legacy/gloopblocks.lua b/mods/moreblocks/stairsplus_legacy/gloopblocks.lua
new file mode 100644
index 00000000..da6b07af
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/gloopblocks.lua
@@ -0,0 +1,7 @@
+local materials = stairsplus_legacy.materials
+
+if materials.cement_block and materials.cement_block ~= "gloopblocks:cement" then
+ stairsplus.api.register_alias_all("gloopblocks:cement", materials.cement_block)
+elseif minetest.registered_nodes["gloopblocks:cement"] then
+ stairsplus_legacy.register_legacy("gloopblocks:cement")
+end
diff --git a/mods/moreblocks/stairsplus_legacy/init.lua b/mods/moreblocks/stairsplus_legacy/init.lua
new file mode 100644
index 00000000..4f6a6297
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/init.lua
@@ -0,0 +1,27 @@
+stairsplus_legacy = fmod.create()
+
+stairsplus_legacy.dofile("resources")
+
+function stairsplus_legacy.register_legacy(node, overrides, meta)
+ if stairsplus.settings.legacy_mode then
+ stairsplus.api.register_group(node, "legacy", overrides, meta)
+ else
+ stairsplus.api.register_group(node, "common", overrides, meta)
+ end
+end
+
+local mods = {
+ "basic_materials",
+ "default",
+ "farming",
+ "gloopblocks",
+ "technic",
+ "prefab",
+ "wool",
+}
+
+for _, mod in ipairs(mods) do
+ if stairsplus_legacy.has[mod] and stairsplus_legacy.settings[mod] then
+ stairsplus_legacy.dofile(mod)
+ end
+end
diff --git a/mods/moreblocks/stairsplus_legacy/mod.conf b/mods/moreblocks/stairsplus_legacy/mod.conf
new file mode 100644
index 00000000..35d6a43c
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/mod.conf
@@ -0,0 +1,10 @@
+name = stairsplus_legacy
+title = stairs+ legacy support
+description = support for old registrations which were part of moreblocks/stairsplus
+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, stairsplus
+optional_depends = basic_materials, default, farming, gloopblocks, stairs, prefab, technic, wool
diff --git a/mods/moreblocks/stairsplus_legacy/prefab.lua b/mods/moreblocks/stairsplus_legacy/prefab.lua
new file mode 100644
index 00000000..ef4dbba4
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/prefab.lua
@@ -0,0 +1,9 @@
+local materials = stairsplus_legacy.materials
+
+if materials.concrete_block and materials.concrete_block ~= "prefab:concrete" then
+ local slab_name = stairsplus.api.format_name(materials.concrete_block, "slab_8")
+ local stair_name = stairsplus.api.format_name(materials.concrete_block, "stair")
+
+ minetest.register_alias_force("prefab:concrete_slab", slab_name)
+ minetest.register_alias_force("prefab:concrete_stair", stair_name)
+end
diff --git a/mods/moreblocks/stairsplus_legacy/resources.lua b/mods/moreblocks/stairsplus_legacy/resources.lua
new file mode 100644
index 00000000..6b5ef1f1
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/resources.lua
@@ -0,0 +1,34 @@
+stairsplus_legacy.materials = {}
+
+if stairsplus_legacy.has.prefab then
+ if minetest.registered_nodes["prefab:concrete"] then
+ stairsplus_legacy.materials.concrete_block = "prefab:concrete"
+ end
+end
+
+if stairsplus_legacy.has.gloopblocks then
+ if minetest.registered_nodes["gloopblocks:cement"] then
+ stairsplus_legacy.materials.cement_block = "gloopblocks:cement"
+ end
+end
+
+if stairsplus_legacy.has.technic then
+ if minetest.registered_nodes["technic:brass_block"] then
+ stairsplus_legacy.materials.brass_block = "technic:brass_block"
+ end
+ if minetest.registered_nodes["technic:concrete"] then
+ stairsplus_legacy.materials.concrete_block = "technic:concrete"
+ end
+end
+
+if stairsplus_legacy.has.basic_materials then
+ if minetest.registered_nodes["basic_materials:brass_block"] then
+ stairsplus_legacy.materials.brass_block = "basic_materials:brass_block"
+ end
+ if minetest.registered_nodes["basic_materials:cement_block"] then
+ stairsplus_legacy.materials.cement_block = "basic_materials:cement_block"
+ end
+ if minetest.registered_nodes["basic_materials:concrete_block"] then
+ stairsplus_legacy.materials.concrete_block = "basic_materials:concrete_block"
+ end
+end
diff --git a/mods/moreblocks/stairsplus_legacy/settingtypes.txt b/mods/moreblocks/stairsplus_legacy/settingtypes.txt
new file mode 100644
index 00000000..095398a3
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/settingtypes.txt
@@ -0,0 +1,20 @@
+# enable registering stairsplus nodes for basic_materials
+stairsplus_legacy.basic_materials () bool true
+
+# enable registering stairsplus nodes for default
+stairsplus_legacy.default () bool true
+
+# enable registering stairsplus nodes for farming
+stairsplus_legacy.farming () bool true
+
+# enable registering stairsplus nodes for gloopblocks
+stairsplus_legacy.gloopblocks () bool true
+
+# enable registering stairsplus nodes for technic
+stairsplus_legacy.technic () bool true
+
+# enable registering stairsplus nodes for prefab
+stairsplus_legacy.prefab () bool true
+
+# enable registering stairsplus nodes for wool
+stairsplus_legacy.wool () bool true
diff --git a/mods/moreblocks/stairsplus_legacy/technic.lua b/mods/moreblocks/stairsplus_legacy/technic.lua
new file mode 100644
index 00000000..c77b26ef
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/technic.lua
@@ -0,0 +1,13 @@
+local materials = stairsplus_legacy.materials
+
+if materials.concrete_block and materials.concrete_block ~= "technic:concrete" then
+ stairsplus.api.register_alias_all("technic:concrete", materials.concrete_block)
+elseif minetest.registered_nodes["technic:concrete"] then
+ stairsplus_legacy.register_legacy("technic:concrete")
+end
+
+if materials.brass_block and materials.brass_block ~= "technic:brass_block" then
+ stairsplus.api.register_alias_all("technic:brass_block", materials.brass_block)
+elseif minetest.registered_nodes["technic:brass_block"] then
+ stairsplus_legacy.register_legacy("technic:brass_block")
+end
diff --git a/mods/moreblocks/stairsplus_legacy/wool.lua b/mods/moreblocks/stairsplus_legacy/wool.lua
new file mode 100644
index 00000000..c32901e9
--- /dev/null
+++ b/mods/moreblocks/stairsplus_legacy/wool.lua
@@ -0,0 +1,25 @@
+local dyes = {
+ "white",
+ "grey",
+ "black",
+ "red",
+ "yellow",
+ "green",
+ "cyan",
+ "blue",
+ "magenta",
+ "orange",
+ "violet",
+ "brown",
+ "pink",
+ "dark_grey",
+ "dark_green",
+}
+
+for _, name in ipairs(dyes) do
+ local node = ("wool:%s"):format(name)
+ if minetest.registered_nodes[node] then
+ stairsplus_legacy.register_legacy(node)
+ stairsplus.api.register_alias_all(("moreblocks:%s"):format(name), node)
+ end
+end
diff --git a/mods/morelights/LICENSE b/mods/morelights/LICENSE
new file mode 100644
index 00000000..0a041280
--- /dev/null
+++ b/mods/morelights/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/mods/morelights/README.md b/mods/morelights/README.md
new file mode 100644
index 00000000..c4403a11
--- /dev/null
+++ b/mods/morelights/README.md
@@ -0,0 +1,29 @@
+# morelights
+
+[](https://content.minetest.net/packages/random_geek/morelights/)
+[](https://forum.minetest.net/viewtopic.php?f=11&t=21464)
+
+Minetest mod adding additional lighting nodes.
+
+
+
+The Morelights modpack adds over 30 lighting and accessory nodes to suit
+various styles of builds, both interior and exterior. Includes basic light
+blocks, modern and historical-style lighting, and customizable street lamps.
+
+The modpack is lightweight, with only about **60 kB** of media, including 3D
+models.
+
+Some nodes (ceiling lights, bar lights, poles) can be rotated to serve
+different purposes.
+
+Morelights currently supports Minetest Game, MineClone 2, and Hades Revisited.
+
+## Craft Recipes
+
+Craft recipes for all items can be found in [crafts.md](crafts.md).
+
+## Licenses
+
+Source code is licensed under the LGPL v3.0 license. All other media and assets
+are licensed under the CC BY-SA 4.0 license.
diff --git a/mods/morelights/crafts.md b/mods/morelights/crafts.md
new file mode 100644
index 00000000..14f2b690
--- /dev/null
+++ b/mods/morelights/crafts.md
@@ -0,0 +1,360 @@
+# Craft recipes for Morelights items
+
+Copper = copper ingot (Minetest Game, Hades Revisited) or redstone
+(MineClone 2).
+
+Dye = white or dark grey dye, depending on the desired color of the item.
+For MineClone 2, use bone meal or grey dye.
+
+Dark Wood = jungle wood planks (Minetest Game), spruce wood (MineClone 2), or
+tropical wood planks (Hades Revisited).
+
+Glass = regular, non-stained glass block.
+
+Glass Pane = regular, non-stained glass pane. For Hades Revisited, if
+`hades_extensionmods`/`hades_xpanes` is not available, use regular glass
+instead.
+
+Steel = steel ingot (Minetest Game, Hades Revisited) or iron ingot
+(MineClone 2).
+
+## Basic items
+
+### Light Bulb
+
+Used for most other recipes.
+
+```
++--------+
+| Glass |
++--------+
+| Copper |
++--------+
+| Steel |
++--------+
+```
+
+### Metal Pole
+
+```
++-----+-------+
+| Dye | Steel |
++-----+-------+
+| | Steel |
++-----+-------+
+| | Steel |
++-----+-------+
+```
+
+### Metal Chain
+
+```
++-----+-------+
+| Dye | Steel |
++-----+-------+
+| | |
++-----+-------+
+| | Steel |
++-----+-------+
+```
+
+## Modern
+
+### Modern Light Block
+
+```
++-------+-------+-------+
+| | Steel | |
++-------+-------+-------+
+| Glass | Bulb | Glass |
+| Pane | | Pane |
++-------+-------+-------+
+| | Steel | |
++-------+-------+-------+
+```
+
+### Modern Light Block (small)
+
+```
++-------+-------+-------+
+| | Steel | |
++-------+-------+-------+
+| Glass | Bulb | Glass |
+| Pane | | Pane |
++-------+-------+-------+
+```
+
+### Modern Post Light
+
+```
++-----+-------+
+| Dye | Steel |
++-----+-------+
+| | Bulb |
++-----+-------+
+| | Steel |
++-----+-------+
+```
+
+### Street Lamp Post
+
+```
++-----+-------+-------+
+| Dye | Steel | Steel |
++-----+-------+-------+
+| | Steel | |
++-----+-------+-------+
+| | Steel | |
++-----+-------+-------+
+```
+
+### Ceiling Bar Light (connecting)
+
+```
++--------+-------+--------+
+| Steel | Steel | Steel |
++--------+-------+--------+
+| Copper | Glass | Copper |
++--------+-------+--------+
+```
+
+Also can be crafted from one straight bar light.
+
+### Ceiling Bar Light (straight)
+
+Craft from one connecting bar light.
+
+### Modern Ceiling Light
+
+```
++-------+-------+-------+
+| Steel | Bulb | Steel |
++-------+-------+-------+
+| | Glass | |
+| | Pane | |
++-------+-------+-------+
+```
+
+### Modern Can Light
+
+```
++-------+-------+-------+
+| Dye | Steel | |
++-------+-------+-------+
+| Steel | Bulb | Steel |
++-------+-------+-------+
+```
+
+### Modern Wall Lamp
+
+```
++-------+-------+-------+
+| | Glass | |
+| | Pane | |
++-------+-------+-------+
+| Glass | Bulb | Steel |
+| Pane | | |
++-------+-------+-------+
+| | Dark | Steel |
+| | Dye | |
++-------+-------+-------+
+```
+
+### Modern Table Lamp
+
+```
++------+-------+------+
+| | Steel | |
++------+-------+------+
+| Wool | Bulb | Wool |
++------+-------+------+
+| | Steel | |
++------+-------+------+
+```
+
+Use light/dark grey wool.
+
+### Modern Path Light
+
+```
++-----+-------+
+| Dye | Bulb |
++-----+-------+
+| | Steel |
++-----+-------+
+| | Steel |
++-----+-------+
+```
+
+## Vintage
+
+### Brass Ingot
+
+Minetest Game, Hades Revisited: Craft from one steel and one tin ingot.
+
+MineClone 2: Craft from one iron ingot and one redstone.
+
+If `basic_materials` is installed, its Brass Ingot will be used instead.
+
+### Brass Chain
+
+```
++-------+
+| Brass |
++-------+
+| |
++-------+
+| Brass |
++-------+
+```
+
+### Vintage Light Block
+
+```
++-------+------+-------+
+| | Dark | |
+| | Wood | |
++-------+------+-------+
+| Glass | Bulb | Glass |
+| Pane | | Pane |
++-------+------+-------+
+| | Dark | |
+| | Wood | |
++-------+------+-------+
+```
+
+### Vintage Light Block (small)
+
+```
++-------+------+-------+
+| | Dark | |
+| | Wood | |
++-------+------+-------+
+| Glass | Bulb | Glass |
+| Pane | | Pane |
++-------+------+-------+
+```
+
+### Vintage Lantern (floor, wall, or ceiling)
+
+```
++-------+-------+-------+
+| | Steel | |
++-------+-------+-------+
+| Glass | Bulb | Glass |
+| Pane | | Pane |
++-------+-------+-------+
+| Stick | Steel | Stick |
++-------+-------+-------+
+```
+
+### Vintage Hanging Light Bulb
+
+```
++--------+
+| Steel |
++--------+
+| Copper |
++--------+
+| Bulb |
++--------+
+```
+
+### Vintage Oil Lamp
+
+```
++--------+
+| Glass |
++--------+
+| String |
++--------+
+| Brass |
++--------+
+```
+
+### Vintage Chandelier
+
+```
++-------+-------+-------+
+| | Brass | |
++-------+-------+-------+
+| Bulb | Brass | Bulb |
++-------+-------+-------+
+| Steel | Brass | Steel |
++-------+-------+-------+
+```
+
+# Extras
+
+### Futuristic Light Block
+
+```
++---------+-------+---------+
+| Crystal | Steel | Crystal |
++---------+-------+---------+
+| Glass | Bulb | Glass |
+| Pane | | Pane |
++---------+-------+---------+
+| Crystal | Steel | Crystal |
++---------+-------+---------+
+```
+
+Crystal = mese fragment (Minetest Game, Hades Revisited) or nether quartz
+(MineClone 2).
+
+### Grass Light
+
+```
++-------+-------+
+| | Glass |
+| | Pane |
++-------+-------+
+| | Bulb |
++-------+-------+
+| Grass | Dirt |
++-------+-------+
+```
+
+Note: use regular grass, NOT a grass block.
+
+### Stone Block Light
+
+```
++-------+
+| Glass |
+| Pane |
++-------+
+| Bulb |
++-------+
+| Stone |
+| Block |
++-------+
+```
+
+For MineClone 2, use polished stone.
+
+### Sandstone Block Light
+
+```
++-----------+
+| Glass |
+| Pane |
++-----------+
+| Bulb |
++-----------+
+| Sandstone |
+| Block |
++-----------+
+```
+
+For MineClone 2, use cut sandstone.
+
+For Hades Revisited, use fine sandstone.
+
+### Stair Light
+
+```
++-------+------+-------+
+| Steel | Bulb | Steel |
++-------+------+-------+
+```
diff --git a/mods/morelights/modpack.conf b/mods/morelights/modpack.conf
new file mode 100644
index 00000000..d23d7911
--- /dev/null
+++ b/mods/morelights/modpack.conf
@@ -0,0 +1,6 @@
+name = morelights
+description = A lightweight modpack providing flexible interior and exterior lighting options for different styles of builds.
+min_minetest_version = 5.4
+release = 16081
+author = random_geek
+title = Morelights
diff --git a/mods/morelights/morelights/init.lua b/mods/morelights/morelights/init.lua
new file mode 100644
index 00000000..7941d479
--- /dev/null
+++ b/mods/morelights/morelights/init.lua
@@ -0,0 +1,157 @@
+morelights = {}
+
+-- TODO: Change node definition based on game if groups get too unwieldy.
+
+function morelights.register_variants(variants, fixedDef)
+ for _, variant in ipairs(variants) do
+ local name = variant.name
+ local def = table.copy(fixedDef)
+
+ for k, v in pairs(variant) do
+ if k ~= "name" then
+ def[k] = v
+ end
+ end
+
+ minetest.register_node(name, def)
+ end
+end
+
+function morelights.on_place_hanging(itemstack, placer, pointed_thing,
+ ceilingName)
+ local ceiling = minetest.get_node(
+ vector.add(pointed_thing.above, vector.new(0, 1, 0)))
+
+ if ceiling.name ~= "air"
+ and minetest.get_item_group(ceiling.name, "mounted_ceiling") == 0
+ and not (placer and placer:get_player_control().sneak) then
+ -- Create a dummy itemstack with the ceiling variant's name.
+ local fakeStack = ItemStack(itemstack)
+ fakeStack:set_name(ceilingName)
+
+ minetest.item_place(fakeStack, placer, pointed_thing, 0)
+
+ -- Subtract an item from the real itemstack if a node was placed.
+ itemstack:set_count(fakeStack:get_count())
+ return itemstack
+ end
+
+ minetest.item_place(itemstack, placer, pointed_thing, 0)
+ return itemstack
+end
+
+function morelights.rotate_and_place(itemstack, placer, pointed_thing, lookup)
+ local dir = minetest.dir_to_wallmounted(
+ vector.subtract(pointed_thing.under, pointed_thing.above))
+ local fDirs = lookup or {[0] = 20, 0, 16, 12, 8, 4}
+ minetest.item_place(itemstack, placer, pointed_thing, fDirs[dir] or 0)
+ return itemstack
+end
+
+
+if minetest.get_modpath("mcl_core") then
+ morelights.game = "mineclone2"
+elseif minetest.get_modpath("default") then
+ morelights.game = "minetest_game"
+elseif minetest.get_modpath("hades_core") then
+ morelights.game = "hades_revisited"
+else
+ error("Morelights requires a compatible game " ..
+ "(Minetest Game, MineClone 2, or Hades Revisited).")
+end
+
+if morelights.game == "minetest_game" then
+ morelights.sounds = {
+ default = default.node_sound_defaults(),
+ glass = default.node_sound_glass_defaults(),
+ metal = default.node_sound_metal_defaults()
+ }
+elseif morelights.game == "mineclone2" then
+ morelights.sounds = {
+ default = mcl_sounds.node_sound_defaults(),
+ glass = mcl_sounds.node_sound_glass_defaults(),
+ metal = mcl_sounds.node_sound_metal_defaults()
+ }
+elseif morelights.game == "hades_revisited" then
+ morelights.sounds = {
+ default = hades_sounds.node_sound_defaults(),
+ glass = hades_sounds.node_sound_glass_defaults(),
+ metal = hades_sounds.node_sound_metal_defaults()
+ }
+end
+
+morelights.craft_items = {
+ glass = "default:glass",
+ glass_pane = "xpanes:pane_flat",
+ steel = "default:steel_ingot",
+ copper = "default:copper_ingot",
+ tin = "default:tin_ingot",
+ crystal_fragment = "default:mese_crystal_fragment",
+ dye_dark = "dye:dark_grey",
+ dye_light = "dye:white",
+ wool_dark = "wool:dark_grey",
+ wool_light = "wool:white",
+ wood_dark = "default:junglewood",
+ stone_block = "default:stone_block",
+ sandstone_block = "default:sandstone_block",
+ dirt = "default:dirt",
+ grass = "default:grass_1",
+ string = "farming:string",
+ stick = "default:stick",
+}
+
+local a = morelights.craft_items
+
+if morelights.game == "mineclone2" then
+ a.glass = "mcl_core:glass"
+ a.glass_pane = "xpanes:pane_natural_flat"
+ a.steel = "mcl_core:iron_ingot"
+ -- MCL has neither copper nor tin. :(
+ a.copper = "mesecons:redstone"
+ a.tin = "mcl_core:iron_ingot"
+ a.crystal_fragment = "mcl_nether:quartz"
+ a.dye_dark = "mcl_dye:dark_grey"
+ a.dye_light = "mcl_dye:white"
+ a.wool_dark = "mcl_wool:grey"
+ a.wool_light = "mcl_wool:white"
+ a.wood_dark = "mcl_core:sprucewood"
+ a.stone_block = "mcl_core:stone_smooth"
+ a.sandstone_block = "mcl_core:sandstonesmooth"
+ a.dirt = "mcl_core:dirt"
+ a.grass = "mcl_flowers:tallgrass"
+ a.string = "mcl_mobitems:string"
+ a.stick = "mcl_core:stick"
+elseif morelights.game == "hades_revisited" then
+ a.glass = "hades_core:glass"
+ -- HR doesn't have glass panes. :(
+ a.glass_pane = "hades_core:glass"
+ if minetest.get_modpath("hades_xpanes") then
+ a.glass_pane = "hades_xpanes:pane_flat"
+ end
+
+ a.steel = "hades_core:steel_ingot"
+ a.copper = "hades_core:copper_ingot"
+ a.tin = "hades_core:tin_ingot"
+ a.crystal_fragment = "hades_core:mese_crystal_fragment"
+ a.dye_dark = "dye:dark_grey"
+ a.dye_light = "dye:white"
+ a.wool_dark = "wool:grey"
+ a.wool_light = "wool:white"
+ a.wood_dark = "hades_trees:jungle_wood"
+ a.stone_block = "hades_core:stone_block"
+ a.sandstone_block = "hades_core:sandstone"
+ a.dirt = "hades_core:dirt"
+ a.grass = "hades_grass:grass_1"
+ a.string = "hades_farming:cotton"
+ a.stick = "hades_core:stick"
+end
+
+-- Use basic_materials brass if available, otherwise register our own.
+if minetest.get_modpath("basic_materials") then
+ a.brass = "basic_materials:brass_ingot"
+elseif minetest.get_modpath("hades_extramaterials") then
+ a.brass = "hades_extramaterials:brass_ingot"
+end
+
+local path = minetest.get_modpath("morelights")
+dofile(path .. "/nodes.lua")
diff --git a/mods/morelights/morelights/locale/template.txt b/mods/morelights/morelights/locale/template.txt
new file mode 100644
index 00000000..f2caa815
--- /dev/null
+++ b/mods/morelights/morelights/locale/template.txt
@@ -0,0 +1,7 @@
+# textdomain:morelights
+
+dark=
+light=
+Light Bulb=
+Metal Pole (@1)=
+Metal Chain (@1)=
diff --git a/mods/morelights/morelights/mod.conf b/mods/morelights/morelights/mod.conf
new file mode 100644
index 00000000..6f468470
--- /dev/null
+++ b/mods/morelights/morelights/mod.conf
@@ -0,0 +1,3 @@
+name = morelights
+description = Base mod providing basic items, which the rest of the modpack depends on.
+optional_depends = default, mcl_core, hades_core
diff --git a/mods/morelights/morelights/models/chains.blend b/mods/morelights/morelights/models/chains.blend
new file mode 100644
index 00000000..befc16a5
Binary files /dev/null and b/mods/morelights/morelights/models/chains.blend differ
diff --git a/mods/morelights/morelights/models/morelights_chain.obj b/mods/morelights/morelights/models/morelights_chain.obj
new file mode 100644
index 00000000..113a143a
--- /dev/null
+++ b/mods/morelights/morelights/models/morelights_chain.obj
@@ -0,0 +1,544 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T03:30:43Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [185]
+
+v 0 -0.53125 0
+v 0 -0.515625 0.015625
+v 0 -0.5 0
+v 0 -0.515625 -0.015625
+v 0.054127 -0.5 0
+v 0.040595 -0.492188 0.015625
+v 0.027063 -0.484375 0
+v 0.040595 -0.492188 -0.015625
+v 0.054127 -0.375 0
+v 0.040595 -0.382812 0.015625
+v 0.027063 -0.390625 0
+v 0.040595 -0.382812 -0.015625
+v 0 -0.34375 0
+v 0 -0.359375 0.015625
+v 0 -0.375 0
+v 0 -0.359375 -0.015625
+v -0.054127 -0.375 0
+v -0.040595 -0.382812 0.015625
+v -0.027063 -0.390625 0
+v -0.040595 -0.382812 -0.015625
+v -0.054127 -0.5 0
+v -0.040595 -0.492188 0.015625
+v -0.027063 -0.484375 0
+v -0.040595 -0.492188 -0.015625
+v 0 -0.03125 0
+v 0 -0.015625 0.015625
+v 0 -0.015625 -0.015625
+v 0.054127 0 0
+v 0.040595 0.007812 0.015625
+v 0.027063 0.015625 0
+v 0.040595 0.007812 -0.015625
+v 0.054127 0.125 0
+v 0.040595 0.117188 0.015625
+v 0.027063 0.109375 0
+v 0.040595 0.117188 -0.015625
+v 0 0.15625 0
+v 0 0.140625 0.015625
+v 0 0.125 0
+v 0 0.140625 -0.015625
+v -0.054127 0.125 0
+v -0.040595 0.117188 0.015625
+v -0.027063 0.109375 0
+v -0.040595 0.117188 -0.015625
+v -0.054127 0 0
+v -0.040595 0.007812 0.015625
+v -0.027063 0.015625 0
+v -0.040595 0.007812 -0.015625
+v 0 0.09375 0
+v -0.015625 0.109375 0
+v 0.015625 0.109375 0
+v 0 0.125 0.054127
+v -0.015625 0.132812 0.040595
+v 0 0.140625 0.027063
+v 0.015625 0.132812 0.040595
+v 0 0.25 0.054127
+v -0.015625 0.242188 0.040595
+v 0 0.234375 0.027063
+v 0.015625 0.242188 0.040595
+v 0 0.28125 0
+v -0.015625 0.265625 0
+v 0.015625 0.265625 0
+v 0 0.25 -0.054127
+v -0.015625 0.242188 -0.040595
+v 0 0.234375 -0.027063
+v 0.015625 0.242188 -0.040595
+v 0 0.125 -0.054127
+v -0.015625 0.132812 -0.040595
+v 0 0.140625 -0.027063
+v 0.015625 0.132812 -0.040595
+v 0 0.21875 0
+v 0 0.234375 0.015625
+v 0 0.25 0
+v 0 0.234375 -0.015625
+v 0.054127 0.25 0
+v 0.040595 0.257812 0.015625
+v 0.027063 0.265625 0
+v 0.040595 0.257812 -0.015625
+v 0.054127 0.375 0
+v 0.040595 0.367188 0.015625
+v 0.027063 0.359375 0
+v 0.040595 0.367188 -0.015625
+v 0 0.40625 0
+v 0 0.390625 0.015625
+v 0 0.375 0
+v 0 0.390625 -0.015625
+v -0.054127 0.375 0
+v -0.040595 0.367188 0.015625
+v -0.027063 0.359375 0
+v -0.040595 0.367188 -0.015625
+v -0.054127 0.25 0
+v -0.040595 0.257812 0.015625
+v -0.027063 0.265625 0
+v -0.040595 0.257812 -0.015625
+v 0 0.34375 0
+v -0.015625 0.359375 0
+v 0.015625 0.359375 0
+v 0 0.375 0.054127
+v -0.015625 0.382812 0.040595
+v 0 0.390625 0.027063
+v 0.015625 0.382812 0.040595
+v 0 0.5 0.054127
+v -0.015625 0.492188 0.040595
+v 0 0.484375 0.027063
+v 0.015625 0.492188 0.040595
+v 0 0.53125 0
+v -0.015625 0.515625 0
+v 0 0.5 0
+v 0.015625 0.515625 0
+v 0 0.5 -0.054127
+v -0.015625 0.492188 -0.040595
+v 0 0.484375 -0.027063
+v 0.015625 0.492188 -0.040595
+v 0 0.375 -0.054127
+v -0.015625 0.382812 -0.040595
+v 0 0.390625 -0.027063
+v 0.015625 0.382812 -0.040595
+v 0 -0.40625 0
+v -0.015625 -0.390625 0
+v 0.015625 -0.390625 0
+v 0 -0.375 0.054127
+v -0.015625 -0.367188 0.040595
+v 0 -0.359375 0.027063
+v 0.015625 -0.367188 0.040595
+v 0 -0.25 0.054127
+v -0.015625 -0.257812 0.040595
+v 0 -0.265625 0.027063
+v 0.015625 -0.257812 0.040595
+v 0 -0.21875 0
+v -0.015625 -0.234375 0
+v 0 -0.25 0
+v 0.015625 -0.234375 0
+v 0 -0.25 -0.054127
+v -0.015625 -0.257812 -0.040595
+v 0 -0.265625 -0.027063
+v 0.015625 -0.257812 -0.040595
+v 0 -0.375 -0.054127
+v -0.015625 -0.367188 -0.040595
+v 0 -0.359375 -0.027063
+v 0.015625 -0.367188 -0.040595
+v 0 -0.28125 0
+v 0 -0.265625 0.015625
+v 0 -0.265625 -0.015625
+v 0.054127 -0.25 0
+v 0.040595 -0.242188 0.015625
+v 0.027063 -0.234375 0
+v 0.040595 -0.242188 -0.015625
+v 0.054127 -0.125 0
+v 0.040595 -0.132812 0.015625
+v 0.027063 -0.140625 0
+v 0.040595 -0.132812 -0.015625
+v 0 -0.09375 0
+v 0 -0.109375 0.015625
+v 0 -0.125 0
+v 0 -0.109375 -0.015625
+v -0.054127 -0.125 0
+v -0.040595 -0.132812 0.015625
+v -0.027063 -0.140625 0
+v -0.040595 -0.132812 -0.015625
+v -0.054127 -0.25 0
+v -0.040595 -0.242188 0.015625
+v -0.027063 -0.234375 0
+v -0.040595 -0.242188 -0.015625
+v 0 -0.15625 0
+v -0.015625 -0.140625 0
+v 0.015625 -0.140625 0
+v 0 -0.125 0.054127
+v -0.015625 -0.117188 0.040595
+v 0 -0.109375 0.027063
+v 0.015625 -0.117188 0.040595
+v 0 0 0.054127
+v -0.015625 -0.007812 0.040595
+v 0 -0.015625 0.027063
+v 0.015625 -0.007812 0.040595
+v 0 0.03125 0
+v -0.015625 0.015625 0
+v 0 0 0
+v 0.015625 0.015625 0
+v 0 0 -0.054127
+v -0.015625 -0.007812 -0.040595
+v 0 -0.015625 -0.027063
+v 0.015625 -0.007812 -0.040595
+v 0 -0.125 -0.054127
+v -0.015625 -0.117188 -0.040595
+v 0 -0.109375 -0.027063
+v 0.015625 -0.117188 -0.040595
+
+# normals [24]
+
+vn 0.378 -0.6547 0.6547
+vn -0.378 0.6547 0.6547
+vn -0.378 0.6547 -0.6547
+vn 0.378 -0.6547 -0.6547
+vn 0.7559 0 0.6547
+vn -0.7559 0 0.6547
+vn -0.7559 0 -0.6547
+vn 0.7559 0 -0.6547
+vn 0.378 0.6547 0.6547
+vn -0.378 -0.6547 0.6547
+vn -0.378 -0.6547 -0.6547
+vn 0.378 0.6547 -0.6547
+vn -0.6547 -0.6547 0.378
+vn -0.6547 0.6547 -0.378
+vn 0.6547 0.6547 -0.378
+vn 0.6547 -0.6547 0.378
+vn -0.6547 0 0.7559
+vn -0.6547 0 -0.7559
+vn 0.6547 0 -0.7559
+vn 0.6547 0 0.7559
+vn -0.6547 0.6547 0.378
+vn -0.6547 -0.6547 -0.378
+vn 0.6547 -0.6547 -0.378
+vn 0.6547 0.6547 0.378
+
+# uvs [123]
+
+vt 0.5 -0.03125
+vt 0.4375 0
+vt 0.453125 0.007812
+vt 0.5 -0.015625
+vt 0.46875 0.015625
+vt 0.5 0
+vt 0.4375 0.125
+vt 0.453125 0.117188
+vt 0.46875 0.109375
+vt 0.5 0.15625
+vt 0.5 0.140625
+vt 0.5 0.125
+vt 0.5625 0.125
+vt 0.546875 0.117188
+vt 0.53125 0.109375
+vt 0.5625 0
+vt 0.546875 0.007812
+vt 0.53125 0.015625
+vt 0.5 0.46875
+vt 0.4375 0.5
+vt 0.453125 0.507812
+vt 0.5 0.484375
+vt 0.46875 0.515625
+vt 0.5 0.5
+vt 0.4375 0.625
+vt 0.453125 0.617188
+vt 0.46875 0.609375
+vt 0.5 0.65625
+vt 0.5 0.640625
+vt 0.5 0.625
+vt 0.5625 0.625
+vt 0.546875 0.617188
+vt 0.53125 0.609375
+vt 0.5625 0.5
+vt 0.546875 0.507812
+vt 0.53125 0.515625
+vt 0.5 0.59375
+vt 0.453125 0.632812
+vt 0.5 0.609375
+vt 0.46875 0.640625
+vt 0.4375 0.75
+vt 0.453125 0.742188
+vt 0.46875 0.734375
+vt 0.5 0.78125
+vt 0.5 0.765625
+vt 0.5 0.75
+vt 0.5625 0.75
+vt 0.546875 0.742188
+vt 0.53125 0.734375
+vt 0.546875 0.632812
+vt 0.53125 0.640625
+vt 0.5 0.71875
+vt 0.453125 0.757812
+vt 0.5 0.734375
+vt 0.46875 0.765625
+vt 0.4375 0.875
+vt 0.453125 0.867188
+vt 0.46875 0.859375
+vt 0.5 0.90625
+vt 0.5 0.890625
+vt 0.5 0.875
+vt 0.5625 0.875
+vt 0.546875 0.867188
+vt 0.53125 0.859375
+vt 0.546875 0.757812
+vt 0.53125 0.765625
+vt 0.5 0.84375
+vt 0.453125 0.882812
+vt 0.5 0.859375
+vt 0.46875 0.890625
+vt 0.4375 1
+vt 0.453125 0.992188
+vt 0.46875 0.984375
+vt 0.5 1.03125
+vt 0.5 1.015625
+vt 0.5 1
+vt 0.5625 1
+vt 0.546875 0.992188
+vt 0.53125 0.984375
+vt 0.546875 0.882812
+vt 0.53125 0.890625
+vt 0.5 0.09375
+vt 0.453125 0.132812
+vt 0.5 0.109375
+vt 0.46875 0.140625
+vt 0.4375 0.25
+vt 0.453125 0.242188
+vt 0.46875 0.234375
+vt 0.5 0.28125
+vt 0.5 0.265625
+vt 0.5 0.25
+vt 0.5625 0.25
+vt 0.546875 0.242188
+vt 0.53125 0.234375
+vt 0.546875 0.132812
+vt 0.53125 0.140625
+vt 0.5 0.21875
+vt 0.453125 0.257812
+vt 0.5 0.234375
+vt 0.46875 0.265625
+vt 0.4375 0.375
+vt 0.453125 0.367188
+vt 0.46875 0.359375
+vt 0.5 0.40625
+vt 0.5 0.390625
+vt 0.5 0.375
+vt 0.5625 0.375
+vt 0.546875 0.367188
+vt 0.53125 0.359375
+vt 0.546875 0.257812
+vt 0.53125 0.265625
+vt 0.5 0.34375
+vt 0.453125 0.382812
+vt 0.5 0.359375
+vt 0.46875 0.390625
+vt 0.453125 0.492188
+vt 0.46875 0.484375
+vt 0.5 0.53125
+vt 0.5 0.515625
+vt 0.546875 0.492188
+vt 0.53125 0.484375
+vt 0.546875 0.382812
+vt 0.53125 0.390625
+
+# objects [1]
+
+o Chain_Torus
+
+s off
+f 1/1/1 5/2/1 6/3/1 2/4/1
+f 2/4/2 6/3/2 7/5/2 3/6/2
+f 3/6/3 7/5/3 8/3/3 4/4/3
+f 4/4/4 8/3/4 5/2/4 1/1/4
+f 5/2/5 9/7/5 10/8/5 6/3/5
+f 6/3/6 10/8/6 11/9/6 7/5/6
+f 7/5/7 11/9/7 12/8/7 8/3/7
+f 8/3/8 12/8/8 9/7/8 5/2/8
+f 9/7/9 13/10/9 14/11/9 10/8/9
+f 10/8/10 14/11/10 15/12/10 11/9/10
+f 11/9/11 15/12/11 16/11/11 12/8/11
+f 12/8/12 16/11/12 13/10/12 9/7/12
+f 13/10/2 17/13/2 18/14/2 14/11/2
+f 14/11/1 18/14/1 19/15/1 15/12/1
+f 15/12/4 19/15/4 20/14/4 16/11/4
+f 16/11/3 20/14/3 17/13/3 13/10/3
+f 17/13/6 21/16/6 22/17/6 18/14/6
+f 18/14/5 22/17/5 23/18/5 19/15/5
+f 19/15/8 23/18/8 24/17/8 20/14/8
+f 20/14/7 24/17/7 21/16/7 17/13/7
+f 21/16/10 1/1/10 2/4/10 22/17/10
+f 22/17/9 2/4/9 3/6/9 23/18/9
+f 23/18/12 3/6/12 4/4/12 24/17/12
+f 24/17/11 4/4/11 1/1/11 21/16/11
+f 25/19/1 28/20/1 29/21/1 26/22/1
+f 26/22/2 29/21/2 30/23/2 176/24/2
+f 176/24/3 30/23/3 31/21/3 27/22/3
+f 27/22/4 31/21/4 28/20/4 25/19/4
+f 28/20/5 32/25/5 33/26/5 29/21/5
+f 29/21/6 33/26/6 34/27/6 30/23/6
+f 30/23/7 34/27/7 35/26/7 31/21/7
+f 31/21/8 35/26/8 32/25/8 28/20/8
+f 32/25/9 36/28/9 37/29/9 33/26/9
+f 33/26/10 37/29/10 38/30/10 34/27/10
+f 34/27/11 38/30/11 39/29/11 35/26/11
+f 35/26/12 39/29/12 36/28/12 32/25/12
+f 36/28/2 40/31/2 41/32/2 37/29/2
+f 37/29/1 41/32/1 42/33/1 38/30/1
+f 38/30/4 42/33/4 43/32/4 39/29/4
+f 39/29/3 43/32/3 40/31/3 36/28/3
+f 40/31/6 44/34/6 45/35/6 41/32/6
+f 41/32/5 45/35/5 46/36/5 42/33/5
+f 42/33/8 46/36/8 47/35/8 43/32/8
+f 43/32/7 47/35/7 44/34/7 40/31/7
+f 44/34/10 25/19/10 26/22/10 45/35/10
+f 45/35/9 26/22/9 176/24/9 46/36/9
+f 46/36/12 176/24/12 27/22/12 47/35/12
+f 47/35/11 27/22/11 25/19/11 44/34/11
+f 48/37/13 51/25/13 52/38/13 49/39/13
+f 49/39/14 52/38/14 53/40/14 38/30/14
+f 38/30/15 53/40/15 54/38/15 50/39/15
+f 50/39/16 54/38/16 51/25/16 48/37/16
+f 51/25/17 55/41/17 56/42/17 52/38/17
+f 52/38/18 56/42/18 57/43/18 53/40/18
+f 53/40/19 57/43/19 58/42/19 54/38/19
+f 54/38/20 58/42/20 55/41/20 51/25/20
+f 55/41/21 59/44/21 60/45/21 56/42/21
+f 56/42/22 60/45/22 72/46/22 57/43/22
+f 57/43/23 72/46/23 61/45/23 58/42/23
+f 58/42/24 61/45/24 59/44/24 55/41/24
+f 59/44/14 62/47/14 63/48/14 60/45/14
+f 60/45/13 63/48/13 64/49/13 72/46/13
+f 72/46/16 64/49/16 65/48/16 61/45/16
+f 61/45/15 65/48/15 62/47/15 59/44/15
+f 62/47/18 66/31/18 67/50/18 63/48/18
+f 63/48/17 67/50/17 68/51/17 64/49/17
+f 64/49/20 68/51/20 69/50/20 65/48/20
+f 65/48/19 69/50/19 66/31/19 62/47/19
+f 66/31/22 48/37/22 49/39/22 67/50/22
+f 67/50/21 49/39/21 38/30/21 68/51/21
+f 68/51/24 38/30/24 50/39/24 69/50/24
+f 69/50/23 50/39/23 48/37/23 66/31/23
+f 70/52/1 74/41/1 75/53/1 71/54/1
+f 71/54/2 75/53/2 76/55/2 72/46/2
+f 72/46/3 76/55/3 77/53/3 73/54/3
+f 73/54/4 77/53/4 74/41/4 70/52/4
+f 74/41/5 78/56/5 79/57/5 75/53/5
+f 75/53/6 79/57/6 80/58/6 76/55/6
+f 76/55/7 80/58/7 81/57/7 77/53/7
+f 77/53/8 81/57/8 78/56/8 74/41/8
+f 78/56/9 82/59/9 83/60/9 79/57/9
+f 79/57/10 83/60/10 84/61/10 80/58/10
+f 80/58/11 84/61/11 85/60/11 81/57/11
+f 81/57/12 85/60/12 82/59/12 78/56/12
+f 82/59/2 86/62/2 87/63/2 83/60/2
+f 83/60/1 87/63/1 88/64/1 84/61/1
+f 84/61/4 88/64/4 89/63/4 85/60/4
+f 85/60/3 89/63/3 86/62/3 82/59/3
+f 86/62/6 90/47/6 91/65/6 87/63/6
+f 87/63/5 91/65/5 92/66/5 88/64/5
+f 88/64/8 92/66/8 93/65/8 89/63/8
+f 89/63/7 93/65/7 90/47/7 86/62/7
+f 90/47/10 70/52/10 71/54/10 91/65/10
+f 91/65/9 71/54/9 72/46/9 92/66/9
+f 92/66/12 72/46/12 73/54/12 93/65/12
+f 93/65/11 73/54/11 70/52/11 90/47/11
+f 94/67/13 97/56/13 98/68/13 95/69/13
+f 95/69/14 98/68/14 99/70/14 84/61/14
+f 84/61/15 99/70/15 100/68/15 96/69/15
+f 96/69/16 100/68/16 97/56/16 94/67/16
+f 97/56/17 101/71/17 102/72/17 98/68/17
+f 98/68/18 102/72/18 103/73/18 99/70/18
+f 99/70/19 103/73/19 104/72/19 100/68/19
+f 100/68/20 104/72/20 101/71/20 97/56/20
+f 101/71/21 105/74/21 106/75/21 102/72/21
+f 102/72/22 106/75/22 107/76/22 103/73/22
+f 103/73/23 107/76/23 108/75/23 104/72/23
+f 104/72/24 108/75/24 105/74/24 101/71/24
+f 105/74/14 109/77/14 110/78/14 106/75/14
+f 106/75/13 110/78/13 111/79/13 107/76/13
+f 107/76/16 111/79/16 112/78/16 108/75/16
+f 108/75/15 112/78/15 109/77/15 105/74/15
+f 109/77/18 113/62/18 114/80/18 110/78/18
+f 110/78/17 114/80/17 115/81/17 111/79/17
+f 111/79/20 115/81/20 116/80/20 112/78/20
+f 112/78/19 116/80/19 113/62/19 109/77/19
+f 113/62/22 94/67/22 95/69/22 114/80/22
+f 114/80/21 95/69/21 84/61/21 115/81/21
+f 115/81/24 84/61/24 96/69/24 116/80/24
+f 116/80/23 96/69/23 94/67/23 113/62/23
+f 117/82/13 120/7/13 121/83/13 118/84/13
+f 118/84/14 121/83/14 122/85/14 15/12/14
+f 15/12/15 122/85/15 123/83/15 119/84/15
+f 119/84/16 123/83/16 120/7/16 117/82/16
+f 120/7/17 124/86/17 125/87/17 121/83/17
+f 121/83/18 125/87/18 126/88/18 122/85/18
+f 122/85/19 126/88/19 127/87/19 123/83/19
+f 123/83/20 127/87/20 124/86/20 120/7/20
+f 124/86/21 128/89/21 129/90/21 125/87/21
+f 125/87/22 129/90/22 130/91/22 126/88/22
+f 126/88/23 130/91/23 131/90/23 127/87/23
+f 127/87/24 131/90/24 128/89/24 124/86/24
+f 128/89/14 132/92/14 133/93/14 129/90/14
+f 129/90/13 133/93/13 134/94/13 130/91/13
+f 130/91/16 134/94/16 135/93/16 131/90/16
+f 131/90/15 135/93/15 132/92/15 128/89/15
+f 132/92/18 136/13/18 137/95/18 133/93/18
+f 133/93/17 137/95/17 138/96/17 134/94/17
+f 134/94/20 138/96/20 139/95/20 135/93/20
+f 135/93/19 139/95/19 136/13/19 132/92/19
+f 136/13/22 117/82/22 118/84/22 137/95/22
+f 137/95/21 118/84/21 15/12/21 138/96/21
+f 138/96/24 15/12/24 119/84/24 139/95/24
+f 139/95/23 119/84/23 117/82/23 136/13/23
+f 140/97/1 143/86/1 144/98/1 141/99/1
+f 141/99/2 144/98/2 145/100/2 130/91/2
+f 130/91/3 145/100/3 146/98/3 142/99/3
+f 142/99/4 146/98/4 143/86/4 140/97/4
+f 143/86/5 147/101/5 148/102/5 144/98/5
+f 144/98/6 148/102/6 149/103/6 145/100/6
+f 145/100/7 149/103/7 150/102/7 146/98/7
+f 146/98/8 150/102/8 147/101/8 143/86/8
+f 147/101/9 151/104/9 152/105/9 148/102/9
+f 148/102/10 152/105/10 153/106/10 149/103/10
+f 149/103/11 153/106/11 154/105/11 150/102/11
+f 150/102/12 154/105/12 151/104/12 147/101/12
+f 151/104/2 155/107/2 156/108/2 152/105/2
+f 152/105/1 156/108/1 157/109/1 153/106/1
+f 153/106/4 157/109/4 158/108/4 154/105/4
+f 154/105/3 158/108/3 155/107/3 151/104/3
+f 155/107/6 159/92/6 160/110/6 156/108/6
+f 156/108/5 160/110/5 161/111/5 157/109/5
+f 157/109/8 161/111/8 162/110/8 158/108/8
+f 158/108/7 162/110/7 159/92/7 155/107/7
+f 159/92/10 140/97/10 141/99/10 160/110/10
+f 160/110/9 141/99/9 130/91/9 161/111/9
+f 161/111/12 130/91/12 142/99/12 162/110/12
+f 162/110/11 142/99/11 140/97/11 159/92/11
+f 163/112/13 166/101/13 167/113/13 164/114/13
+f 164/114/14 167/113/14 168/115/14 153/106/14
+f 153/106/15 168/115/15 169/113/15 165/114/15
+f 165/114/16 169/113/16 166/101/16 163/112/16
+f 166/101/17 170/20/17 171/116/17 167/113/17
+f 167/113/18 171/116/18 172/117/18 168/115/18
+f 168/115/19 172/117/19 173/116/19 169/113/19
+f 169/113/20 173/116/20 170/20/20 166/101/20
+f 170/20/21 174/118/21 175/119/21 171/116/21
+f 171/116/22 175/119/22 176/24/22 172/117/22
+f 172/117/23 176/24/23 177/119/23 173/116/23
+f 173/116/24 177/119/24 174/118/24 170/20/24
+f 174/118/14 178/34/14 179/120/14 175/119/14
+f 175/119/13 179/120/13 180/121/13 176/24/13
+f 176/24/16 180/121/16 181/120/16 177/119/16
+f 177/119/15 181/120/15 178/34/15 174/118/15
+f 178/34/18 182/107/18 183/122/18 179/120/18
+f 179/120/17 183/122/17 184/123/17 180/121/17
+f 180/121/20 184/123/20 185/122/20 181/120/20
+f 181/120/19 185/122/19 182/107/19 178/34/19
+f 182/107/22 163/112/22 164/114/22 183/122/22
+f 183/122/21 164/114/21 153/106/21 184/123/21
+f 184/123/24 153/106/24 165/114/24 185/122/24
+f 185/122/23 165/114/23 163/112/23 182/107/23
+
diff --git a/mods/morelights/morelights/models/morelights_chain_ceiling.obj b/mods/morelights/morelights/models/morelights_chain_ceiling.obj
new file mode 100644
index 00000000..5611905a
--- /dev/null
+++ b/mods/morelights/morelights/models/morelights_chain_ceiling.obj
@@ -0,0 +1,558 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T03:50:15Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: 'chains.blend'
+# www.blender.org
+
+# vertices [189]
+
+v -0.1875 0.4375 0.1875
+v -0.1875 0.5 0.1875
+v -0.1875 0.5 -0.1875
+v -0.1875 0.4375 -0.1875
+v 0.1875 0.5 -0.1875
+v 0.1875 0.4375 -0.1875
+v 0.1875 0.5 0.1875
+v 0.1875 0.4375 0.1875
+v 0 -0.53125 0
+v 0 -0.515625 0.015625
+v 0 -0.5 0
+v 0 -0.515625 -0.015625
+v 0.054127 -0.5 0
+v 0.040595 -0.492188 0.015625
+v 0.027063 -0.484375 0
+v 0.040595 -0.492188 -0.015625
+v 0.054127 -0.375 0
+v 0.040595 -0.382812 0.015625
+v 0.027063 -0.390625 0
+v 0.040595 -0.382812 -0.015625
+v 0 -0.34375 0
+v 0 -0.359375 0.015625
+v 0 -0.375 0
+v 0 -0.359375 -0.015625
+v -0.054127 -0.375 0
+v -0.040595 -0.382812 0.015625
+v -0.027063 -0.390625 0
+v -0.040595 -0.382812 -0.015625
+v -0.054127 -0.5 0
+v -0.040595 -0.492188 0.015625
+v -0.027063 -0.484375 0
+v -0.040595 -0.492188 -0.015625
+v 0 -0.03125 0
+v 0 -0.015625 0.015625
+v 0 -0.015625 -0.015625
+v 0.054127 0 0
+v 0.040595 0.007812 0.015625
+v 0.027063 0.015625 0
+v 0.040595 0.007813 -0.015625
+v 0.054127 0.125 0
+v 0.040595 0.117188 0.015625
+v 0.027063 0.109375 0
+v 0.040595 0.117188 -0.015625
+v 0 0.15625 0
+v 0 0.140625 0.015625
+v 0 0.125 0
+v 0 0.140625 -0.015625
+v -0.054127 0.125 0
+v -0.040595 0.117188 0.015625
+v -0.027063 0.109375 0
+v -0.040595 0.117188 -0.015625
+v -0.054127 0 0
+v -0.040595 0.007812 0.015625
+v -0.027063 0.015625 0
+v -0.040595 0.007813 -0.015625
+v 0 0.09375 0
+v -0.015625 0.109375 0
+v 0.015625 0.109375 0
+v 0 0.125 0.054127
+v -0.015625 0.132812 0.040595
+v 0 0.140625 0.027063
+v 0.015625 0.132812 0.040595
+v 0 0.25 0.054127
+v -0.015625 0.242188 0.040595
+v 0 0.234375 0.027063
+v 0.015625 0.242188 0.040595
+v 0 0.28125 0
+v -0.015625 0.265625 0
+v 0.015625 0.265625 0
+v 0 0.25 -0.054127
+v -0.015625 0.242188 -0.040595
+v 0 0.234375 -0.027063
+v 0.015625 0.242188 -0.040595
+v 0 0.125 -0.054127
+v -0.015625 0.132812 -0.040595
+v 0 0.140625 -0.027063
+v 0.015625 0.132812 -0.040595
+v 0 0.21875 0
+v 0 0.234375 0.015625
+v 0 0.25 0
+v 0 0.234375 -0.015625
+v 0.054127 0.25 0
+v 0.040595 0.257812 0.015625
+v 0.027063 0.265625 0
+v 0.040595 0.257812 -0.015625
+v 0.054127 0.375 0
+v 0.040595 0.367188 0.015625
+v 0.027063 0.359375 0
+v 0.040595 0.367188 -0.015625
+v 0 0.40625 0
+v 0 0.390625 0.015625
+v 0 0.375 0
+v 0 0.390625 -0.015625
+v -0.054127 0.375 0
+v -0.040595 0.367188 0.015625
+v -0.027063 0.359375 0
+v -0.040595 0.367188 -0.015625
+v -0.054127 0.25 0
+v -0.040595 0.257812 0.015625
+v -0.027063 0.265625 0
+v -0.040595 0.257812 -0.015625
+v 0 0.34375 0
+v -0.015625 0.359375 0
+v 0.015625 0.359375 0
+v 0 0.375 0.054127
+v -0.015625 0.382812 0.040595
+v 0 0.390625 0.027063
+v 0.015625 0.382812 0.040595
+v 0 0.4375 0.054127
+v -0.015625 0.4375 0.040595
+v 0 0.4375 0.027063
+v 0.015625 0.4375 0.040595
+v 0 0.4375 -0.054127
+v -0.015625 0.4375 -0.040595
+v 0 0.4375 -0.027063
+v 0.015625 0.4375 -0.040595
+v 0 0.375 -0.054127
+v -0.015625 0.382812 -0.040595
+v 0 0.390625 -0.027063
+v 0.015625 0.382812 -0.040595
+v 0 -0.40625 0
+v -0.015625 -0.390625 0
+v 0.015625 -0.390625 0
+v 0 -0.375 0.054127
+v -0.015625 -0.367188 0.040595
+v 0 -0.359375 0.027063
+v 0.015625 -0.367188 0.040595
+v 0 -0.25 0.054127
+v -0.015625 -0.257812 0.040595
+v 0 -0.265625 0.027063
+v 0.015625 -0.257812 0.040595
+v 0 -0.21875 0
+v -0.015625 -0.234375 0
+v 0 -0.25 0
+v 0.015625 -0.234375 0
+v 0 -0.25 -0.054127
+v -0.015625 -0.257812 -0.040595
+v 0 -0.265625 -0.027063
+v 0.015625 -0.257812 -0.040595
+v 0 -0.375 -0.054127
+v -0.015625 -0.367188 -0.040595
+v 0 -0.359375 -0.027063
+v 0.015625 -0.367188 -0.040595
+v 0 -0.28125 0
+v 0 -0.265625 0.015625
+v 0 -0.265625 -0.015625
+v 0.054127 -0.25 0
+v 0.040595 -0.242188 0.015625
+v 0.027063 -0.234375 0
+v 0.040595 -0.242188 -0.015625
+v 0.054127 -0.125 0
+v 0.040595 -0.132812 0.015625
+v 0.027063 -0.140625 0
+v 0.040595 -0.132812 -0.015625
+v 0 -0.09375 0
+v 0 -0.109375 0.015625
+v 0 -0.125 0
+v 0 -0.109375 -0.015625
+v -0.054127 -0.125 0
+v -0.040595 -0.132812 0.015625
+v -0.027063 -0.140625 0
+v -0.040595 -0.132812 -0.015625
+v -0.054127 -0.25 0
+v -0.040595 -0.242188 0.015625
+v -0.027063 -0.234375 0
+v -0.040595 -0.242188 -0.015625
+v 0 -0.15625 0
+v -0.015625 -0.140625 0
+v 0.015625 -0.140625 0
+v 0 -0.125 0.054127
+v -0.015625 -0.117188 0.040595
+v 0 -0.109375 0.027063
+v 0.015625 -0.117188 0.040595
+v 0 0 0.054127
+v -0.015625 -0.007813 0.040595
+v 0 -0.015625 0.027063
+v 0.015625 -0.007813 0.040595
+v 0 0.03125 0
+v -0.015625 0.015625 0
+v 0 0 0
+v 0.015625 0.015625 0
+v 0 0 -0.054127
+v -0.015625 -0.007812 -0.040595
+v 0 -0.015625 -0.027063
+v 0.015625 -0.007812 -0.040595
+v 0 -0.125 -0.054127
+v -0.015625 -0.117187 -0.040595
+v 0 -0.109375 -0.027063
+v 0.015625 -0.117187 -0.040595
+
+# normals [30]
+
+vn 0.378 -0.6547 0.6547
+vn -0.378 0.6547 0.6547
+vn -0.378 0.6547 -0.6547
+vn 0.378 -0.6547 -0.6547
+vn 0.7559 0 0.6547
+vn -0.7559 0 0.6547
+vn -0.7559 0 -0.6547
+vn 0.7559 0 -0.6547
+vn 0.378 0.6547 0.6547
+vn -0.378 -0.6547 0.6547
+vn -0.378 -0.6547 -0.6547
+vn 0.378 0.6547 -0.6547
+vn -0.6547 -0.6547 0.378
+vn -0.6547 0.6547 -0.378
+vn 0.6547 0.6547 -0.378
+vn 0.6547 -0.6547 0.378
+vn -0.6547 0 0.7559
+vn -0.6547 0 -0.7559
+vn 0.6547 0 -0.7559
+vn 0.6547 0 0.7559
+vn -0.6547 0.6547 0.378
+vn -0.6547 -0.6547 -0.378
+vn 0.6547 -0.6547 -0.378
+vn 0.6547 0.6547 0.378
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 0 1
+vn 0 -1 0
+vn 0 1 0
+
+# uvs [128]
+
+vt 0.5 -0.03125
+vt 0.4375 0
+vt 0.453125 0.007812
+vt 0.5 -0.015625
+vt 0.46875 0.015625
+vt 0.5 0
+vt 0.4375 0.125
+vt 0.453125 0.117188
+vt 0.46875 0.109375
+vt 0.5 0.15625
+vt 0.5 0.140625
+vt 0.5 0.125
+vt 0.5625 0.125
+vt 0.546875 0.117188
+vt 0.53125 0.109375
+vt 0.5625 0
+vt 0.546875 0.007812
+vt 0.53125 0.015625
+vt 0.5 0.46875
+vt 0.4375 0.5
+vt 0.453125 0.507812
+vt 0.5 0.484375
+vt 0.46875 0.515625
+vt 0.5 0.5
+vt 0.4375 0.625
+vt 0.453125 0.617188
+vt 0.46875 0.609375
+vt 0.5 0.65625
+vt 0.5 0.640625
+vt 0.5 0.625
+vt 0.5625 0.625
+vt 0.546875 0.617188
+vt 0.53125 0.609375
+vt 0.5625 0.5
+vt 0.546875 0.507812
+vt 0.53125 0.515625
+vt 0.5 0.59375
+vt 0.453125 0.632812
+vt 0.5 0.609375
+vt 0.46875 0.640625
+vt 0.4375 0.75
+vt 0.453125 0.742188
+vt 0.46875 0.734375
+vt 0.5 0.78125
+vt 0.5 0.765625
+vt 0.5 0.75
+vt 0.5625 0.75
+vt 0.546875 0.742188
+vt 0.53125 0.734375
+vt 0.546875 0.632812
+vt 0.53125 0.640625
+vt 0.5 0.71875
+vt 0.453125 0.757812
+vt 0.5 0.734375
+vt 0.46875 0.765625
+vt 0.4375 0.875
+vt 0.453125 0.867188
+vt 0.46875 0.859375
+vt 0.5 0.90625
+vt 0.5 0.890625
+vt 0.5 0.875
+vt 0.5625 0.875
+vt 0.546875 0.867188
+vt 0.53125 0.859375
+vt 0.546875 0.757812
+vt 0.53125 0.765625
+vt 0.5 0.84375
+vt 0.453125 0.882812
+vt 0.5 0.859375
+vt 0.46875 0.890625
+vt 0.4375 0.9375
+vt 0.453125 0.9375
+vt 0.46875 0.9375
+vt 0.5625 0.9375
+vt 0.546875 0.882812
+vt 0.546875 0.9375
+vt 0.53125 0.890625
+vt 0.53125 0.9375
+vt 0.5 0.09375
+vt 0.453125 0.132812
+vt 0.5 0.109375
+vt 0.46875 0.140625
+vt 0.4375 0.25
+vt 0.453125 0.242188
+vt 0.46875 0.234375
+vt 0.5 0.28125
+vt 0.5 0.265625
+vt 0.5 0.25
+vt 0.5625 0.25
+vt 0.546875 0.242188
+vt 0.53125 0.234375
+vt 0.546875 0.132812
+vt 0.53125 0.140625
+vt 0.5 0.21875
+vt 0.453125 0.257812
+vt 0.5 0.234375
+vt 0.46875 0.265625
+vt 0.4375 0.375
+vt 0.453125 0.367188
+vt 0.46875 0.359375
+vt 0.5 0.40625
+vt 0.5 0.390625
+vt 0.5 0.375
+vt 0.5625 0.375
+vt 0.546875 0.367188
+vt 0.53125 0.359375
+vt 0.546875 0.257812
+vt 0.53125 0.265625
+vt 0.5 0.34375
+vt 0.453125 0.382812
+vt 0.5 0.359375
+vt 0.46875 0.390625
+vt 0.453125 0.492188
+vt 0.46875 0.484375
+vt 0.5 0.53125
+vt 0.5 0.515625
+vt 0.546875 0.492188
+vt 0.53125 0.484375
+vt 0.546875 0.382812
+vt 0.53125 0.390625
+vt 0.6875 0.9375
+vt 0.6875 1
+vt 0.3125 1
+vt 0.3125 0.9375
+vt 0.3125 0.3125
+vt 0.3125 0.6875
+vt 0.6875 0.6875
+vt 0.6875 0.3125
+
+# objects [1]
+
+o CeilingChain_TorusCube
+
+s off
+f 9/1/1 13/2/1 14/3/1 10/4/1
+f 10/4/2 14/3/2 15/5/2 11/6/2
+f 11/6/3 15/5/3 16/3/3 12/4/3
+f 12/4/4 16/3/4 13/2/4 9/1/4
+f 13/2/5 17/7/5 18/8/5 14/3/5
+f 14/3/6 18/8/6 19/9/6 15/5/6
+f 15/5/7 19/9/7 20/8/7 16/3/7
+f 16/3/8 20/8/8 17/7/8 13/2/8
+f 17/7/9 21/10/9 22/11/9 18/8/9
+f 18/8/10 22/11/10 23/12/10 19/9/10
+f 19/9/11 23/12/11 24/11/11 20/8/11
+f 20/8/12 24/11/12 21/10/12 17/7/12
+f 21/10/2 25/13/2 26/14/2 22/11/2
+f 22/11/1 26/14/1 27/15/1 23/12/1
+f 23/12/4 27/15/4 28/14/4 24/11/4
+f 24/11/3 28/14/3 25/13/3 21/10/3
+f 25/13/6 29/16/6 30/17/6 26/14/6
+f 26/14/5 30/17/5 31/18/5 27/15/5
+f 27/15/8 31/18/8 32/17/8 28/14/8
+f 28/14/7 32/17/7 29/16/7 25/13/7
+f 29/16/10 9/1/10 10/4/10 30/17/10
+f 30/17/9 10/4/9 11/6/9 31/18/9
+f 31/18/12 11/6/12 12/4/12 32/17/12
+f 32/17/11 12/4/11 9/1/11 29/16/11
+f 33/19/1 36/20/1 37/21/1 34/22/1
+f 34/22/2 37/21/2 38/23/2 180/24/2
+f 180/24/3 38/23/3 39/21/3 35/22/3
+f 35/22/4 39/21/4 36/20/4 33/19/4
+f 36/20/5 40/25/5 41/26/5 37/21/5
+f 37/21/6 41/26/6 42/27/6 38/23/6
+f 38/23/7 42/27/7 43/26/7 39/21/7
+f 39/21/8 43/26/8 40/25/8 36/20/8
+f 40/25/9 44/28/9 45/29/9 41/26/9
+f 41/26/10 45/29/10 46/30/10 42/27/10
+f 42/27/11 46/30/11 47/29/11 43/26/11
+f 43/26/12 47/29/12 44/28/12 40/25/12
+f 44/28/2 48/31/2 49/32/2 45/29/2
+f 45/29/1 49/32/1 50/33/1 46/30/1
+f 46/30/4 50/33/4 51/32/4 47/29/4
+f 47/29/3 51/32/3 48/31/3 44/28/3
+f 48/31/6 52/34/6 53/35/6 49/32/6
+f 49/32/5 53/35/5 54/36/5 50/33/5
+f 50/33/8 54/36/8 55/35/8 51/32/8
+f 51/32/7 55/35/7 52/34/7 48/31/7
+f 52/34/10 33/19/10 34/22/10 53/35/10
+f 53/35/9 34/22/9 180/24/9 54/36/9
+f 54/36/12 180/24/12 35/22/12 55/35/12
+f 55/35/11 35/22/11 33/19/11 52/34/11
+f 56/37/13 59/25/13 60/38/13 57/39/13
+f 57/39/14 60/38/14 61/40/14 46/30/14
+f 46/30/15 61/40/15 62/38/15 58/39/15
+f 58/39/16 62/38/16 59/25/16 56/37/16
+f 59/25/17 63/41/17 64/42/17 60/38/17
+f 60/38/18 64/42/18 65/43/18 61/40/18
+f 61/40/19 65/43/19 66/42/19 62/38/19
+f 62/38/20 66/42/20 63/41/20 59/25/20
+f 63/41/21 67/44/21 68/45/21 64/42/21
+f 64/42/22 68/45/22 80/46/22 65/43/22
+f 65/43/23 80/46/23 69/45/23 66/42/23
+f 66/42/24 69/45/24 67/44/24 63/41/24
+f 67/44/14 70/47/14 71/48/14 68/45/14
+f 68/45/13 71/48/13 72/49/13 80/46/13
+f 80/46/16 72/49/16 73/48/16 69/45/16
+f 69/45/15 73/48/15 70/47/15 67/44/15
+f 70/47/18 74/31/18 75/50/18 71/48/18
+f 71/48/17 75/50/17 76/51/17 72/49/17
+f 72/49/20 76/51/20 77/50/20 73/48/20
+f 73/48/19 77/50/19 74/31/19 70/47/19
+f 74/31/22 56/37/22 57/39/22 75/50/22
+f 75/50/21 57/39/21 46/30/21 76/51/21
+f 76/51/24 46/30/24 58/39/24 77/50/24
+f 77/50/23 58/39/23 56/37/23 74/31/23
+f 78/52/1 82/41/1 83/53/1 79/54/1
+f 79/54/2 83/53/2 84/55/2 80/46/2
+f 80/46/3 84/55/3 85/53/3 81/54/3
+f 81/54/4 85/53/4 82/41/4 78/52/4
+f 82/41/5 86/56/5 87/57/5 83/53/5
+f 83/53/6 87/57/6 88/58/6 84/55/6
+f 84/55/7 88/58/7 89/57/7 85/53/7
+f 85/53/8 89/57/8 86/56/8 82/41/8
+f 86/56/9 90/59/9 91/60/9 87/57/9
+f 87/57/10 91/60/10 92/61/10 88/58/10
+f 88/58/11 92/61/11 93/60/11 89/57/11
+f 89/57/12 93/60/12 90/59/12 86/56/12
+f 90/59/2 94/62/2 95/63/2 91/60/2
+f 91/60/1 95/63/1 96/64/1 92/61/1
+f 92/61/4 96/64/4 97/63/4 93/60/4
+f 93/60/3 97/63/3 94/62/3 90/59/3
+f 94/62/6 98/47/6 99/65/6 95/63/6
+f 95/63/5 99/65/5 100/66/5 96/64/5
+f 96/64/8 100/66/8 101/65/8 97/63/8
+f 97/63/7 101/65/7 98/47/7 94/62/7
+f 98/47/10 78/52/10 79/54/10 99/65/10
+f 99/65/9 79/54/9 80/46/9 100/66/9
+f 100/66/12 80/46/12 81/54/12 101/65/12
+f 101/65/11 81/54/11 78/52/11 98/47/11
+f 102/67/13 105/56/13 106/68/13 103/69/13
+f 103/69/14 106/68/14 107/70/14 92/61/14
+f 92/61/15 107/70/15 108/68/15 104/69/15
+f 104/69/16 108/68/16 105/56/16 102/67/16
+f 105/56/17 109/71/17 110/72/17 106/68/17
+f 106/68/18 110/72/18 111/73/18 107/70/18
+f 107/70/19 111/73/19 112/72/19 108/68/19
+f 108/68/20 112/72/20 109/71/20 105/56/20
+f 113/74/18 117/62/18 118/75/18 114/76/18
+f 114/76/17 118/75/17 119/77/17 115/78/17
+f 115/78/20 119/77/20 120/75/20 116/76/20
+f 116/76/19 120/75/19 117/62/19 113/74/19
+f 117/62/22 102/67/22 103/69/22 118/75/22
+f 118/75/21 103/69/21 92/61/21 119/77/21
+f 119/77/24 92/61/24 104/69/24 120/75/24
+f 120/75/23 104/69/23 102/67/23 117/62/23
+f 121/79/13 124/7/13 125/80/13 122/81/13
+f 122/81/14 125/80/14 126/82/14 23/12/14
+f 23/12/15 126/82/15 127/80/15 123/81/15
+f 123/81/16 127/80/16 124/7/16 121/79/16
+f 124/7/17 128/83/17 129/84/17 125/80/17
+f 125/80/18 129/84/18 130/85/18 126/82/18
+f 126/82/19 130/85/19 131/84/19 127/80/19
+f 127/80/20 131/84/20 128/83/20 124/7/20
+f 128/83/21 132/86/21 133/87/21 129/84/21
+f 129/84/22 133/87/22 134/88/22 130/85/22
+f 130/85/23 134/88/23 135/87/23 131/84/23
+f 131/84/24 135/87/24 132/86/24 128/83/24
+f 132/86/14 136/89/14 137/90/14 133/87/14
+f 133/87/13 137/90/13 138/91/13 134/88/13
+f 134/88/16 138/91/16 139/90/16 135/87/16
+f 135/87/15 139/90/15 136/89/15 132/86/15
+f 136/89/18 140/13/18 141/92/18 137/90/18
+f 137/90/17 141/92/17 142/93/17 138/91/17
+f 138/91/20 142/93/20 143/92/20 139/90/20
+f 139/90/19 143/92/19 140/13/19 136/89/19
+f 140/13/22 121/79/22 122/81/22 141/92/22
+f 141/92/21 122/81/21 23/12/21 142/93/21
+f 142/93/24 23/12/24 123/81/24 143/92/24
+f 143/92/23 123/81/23 121/79/23 140/13/23
+f 144/94/1 147/83/1 148/95/1 145/96/1
+f 145/96/2 148/95/2 149/97/2 134/88/2
+f 134/88/3 149/97/3 150/95/3 146/96/3
+f 146/96/4 150/95/4 147/83/4 144/94/4
+f 147/83/5 151/98/5 152/99/5 148/95/5
+f 148/95/6 152/99/6 153/100/6 149/97/6
+f 149/97/7 153/100/7 154/99/7 150/95/7
+f 150/95/8 154/99/8 151/98/8 147/83/8
+f 151/98/9 155/101/9 156/102/9 152/99/9
+f 152/99/10 156/102/10 157/103/10 153/100/10
+f 153/100/11 157/103/11 158/102/11 154/99/11
+f 154/99/12 158/102/12 155/101/12 151/98/12
+f 155/101/2 159/104/2 160/105/2 156/102/2
+f 156/102/1 160/105/1 161/106/1 157/103/1
+f 157/103/4 161/106/4 162/105/4 158/102/4
+f 158/102/3 162/105/3 159/104/3 155/101/3
+f 159/104/6 163/89/6 164/107/6 160/105/6
+f 160/105/5 164/107/5 165/108/5 161/106/5
+f 161/106/8 165/108/8 166/107/8 162/105/8
+f 162/105/7 166/107/7 163/89/7 159/104/7
+f 163/89/10 144/94/10 145/96/10 164/107/10
+f 164/107/9 145/96/9 134/88/9 165/108/9
+f 165/108/12 134/88/12 146/96/12 166/107/12
+f 166/107/11 146/96/11 144/94/11 163/89/11
+f 167/109/13 170/98/13 171/110/13 168/111/13
+f 168/111/14 171/110/14 172/112/14 157/103/14
+f 157/103/15 172/112/15 173/110/15 169/111/15
+f 169/111/16 173/110/16 170/98/16 167/109/16
+f 170/98/17 174/20/17 175/113/17 171/110/17
+f 171/110/18 175/113/18 176/114/18 172/112/18
+f 172/112/19 176/114/19 177/113/19 173/110/19
+f 173/110/20 177/113/20 174/20/20 170/98/20
+f 174/20/21 178/115/21 179/116/21 175/113/21
+f 175/113/22 179/116/22 180/24/22 176/114/22
+f 176/114/23 180/24/23 181/116/23 177/113/23
+f 177/113/24 181/116/24 178/115/24 174/20/24
+f 178/115/14 182/34/14 183/117/14 179/116/14
+f 179/116/13 183/117/13 184/118/13 180/24/13
+f 180/24/16 184/118/16 185/117/16 181/116/16
+f 181/116/15 185/117/15 182/34/15 178/115/15
+f 182/34/18 186/104/18 187/119/18 183/117/18
+f 183/117/17 187/119/17 188/120/17 184/118/17
+f 184/118/20 188/120/20 189/119/20 185/117/20
+f 185/117/19 189/119/19 186/104/19 182/34/19
+f 186/104/22 167/109/22 168/111/22 187/119/22
+f 187/119/21 168/111/21 157/103/21 188/120/21
+f 188/120/24 157/103/24 169/111/24 189/119/24
+f 189/119/23 169/111/23 167/109/23 186/104/23
+s 1
+f 1/121/25 2/122/25 3/123/25 4/124/25
+f 4/121/26 3/122/26 5/123/26 6/124/26
+f 6/121/27 5/122/27 7/123/27 8/124/27
+f 8/121/28 7/122/28 2/123/28 1/124/28
+f 4/125/29 6/126/29 8/127/29 1/128/29
+f 5/125/30 3/128/30 2/127/30 7/126/30
+
diff --git a/mods/morelights/morelights/nodes.lua b/mods/morelights/morelights/nodes.lua
new file mode 100644
index 00000000..663b0122
--- /dev/null
+++ b/mods/morelights/morelights/nodes.lua
@@ -0,0 +1,222 @@
+local S = minetest.get_translator("morelights")
+
+
+minetest.register_craftitem("morelights:bulb", {
+ description = S("Light Bulb"),
+ inventory_image = "morelights_bulb.png"
+})
+
+morelights.register_variants({
+ {
+ name = "morelights:pole_d",
+ description = S("Metal Pole (@1)", S("dark")),
+ tiles = {"morelights_metal_dark_32.png"},
+ inventory_image = "morelights_pole_d_inv.png",
+ wield_image = "morelights_pole_d_inv.png",
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.on_place_hanging(itemstack, placer,
+ pointed_thing, "morelights:pole_ceiling_d")
+ end
+ },
+ {
+ name = "morelights:pole_l",
+ description = S("Metal Pole (@1)", S("light")),
+ tiles = {"morelights_metal_light_32.png"},
+ inventory_image = "morelights_pole_l_inv.png",
+ wield_image = "morelights_pole_l_inv.png",
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.on_place_hanging(itemstack, placer,
+ pointed_thing, "morelights:pole_ceiling_l")
+ end
+ }
+},
+{
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {-1/32, -1/2, -1/32, 1/32, 1/2, 1/32}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/16, -1/2, -1/16, 1/16, 1/2, 1/16}
+ },
+ paramtype = "light",
+ paramtype2 = "wallmounted",
+ sunlight_propagates = true,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1,
+ mounted_ceiling = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal
+})
+
+morelights.register_variants({
+ {
+ name = "morelights:pole_ceiling_d",
+ tiles = {"morelights_metal_dark_32.png"},
+ drop = "morelights:pole_d"
+ },
+ {
+ name = "morelights:pole_ceiling_l",
+ tiles = {"morelights_metal_light_32.png"},
+ drop = "morelights:pole_l"
+ }
+},
+{
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-3/16, -7/16, -3/16, 3/16, -1/2, 3/16},
+ {-1/32, 1/2, -1/32, 1/32, -7/16, 1/32}
+ }
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {
+ {-3/16, -7/16, -3/16, 3/16, -1/2, 3/16},
+ {-1/16, 1/2, -1/16, 1/16, -7/16, 1/16}
+ }
+ },
+ paramtype = "light",
+ paramtype2 = "wallmounted",
+ sunlight_propagates = true,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1,
+ mounted_ceiling = 1, not_in_creative_inventory = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal
+})
+
+morelights.register_variants({
+ {
+ name = "morelights:chain_d",
+ description = S("Metal Chain (@1)", S("dark")),
+ tiles = {"morelights_metal_dark_32.png"},
+ inventory_image = "morelights_chain_d_inv.png",
+ wield_image = "morelights_chain_d_inv.png",
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.on_place_hanging(itemstack, placer,
+ pointed_thing, "morelights:chain_ceiling_d")
+ end
+ },
+ {
+ name = "morelights:chain_l",
+ description = S("Metal Chain (@1)", S("light")),
+ tiles = {"morelights_metal_light_32.png"},
+ inventory_image = "morelights_chain_l_inv.png",
+ wield_image = "morelights_chain_l_inv.png",
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.on_place_hanging(itemstack, placer,
+ pointed_thing, "morelights:chain_ceiling_l")
+ end
+ }
+},
+{
+ drawtype = "mesh",
+ mesh = "morelights_chain.obj",
+ collision_box = {
+ type = "fixed",
+ fixed = {-1/16, -1/2, -1/16, 1/16, 1/2, 1/16}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/16, -1/2, -1/16, 1/16, 1/2, 1/16}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1,
+ mounted_ceiling = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal
+})
+
+morelights.register_variants({
+ {
+ name = "morelights:chain_ceiling_d",
+ tiles = {"morelights_metal_dark_32.png"},
+ drop = "morelights:chain_d"
+ },
+ {
+ name = "morelights:chain_ceiling_l",
+ tiles = {"morelights_metal_light_32.png"},
+ drop = "morelights:chain_l"
+ }
+},
+{
+ drawtype = "mesh",
+ mesh = "morelights_chain_ceiling.obj",
+ collision_box = {
+ type = "fixed",
+ fixed = {
+ {-3/16, 7/16, -3/16, 3/16, 1/2, 3/16},
+ {-1/16, -1/2, -1/16, 1/16, 7/16, 1/16}
+ }
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {
+ {-3/16, 7/16, -3/16, 3/16, 1/2, 3/16},
+ {-1/16, -1/2, -1/16, 1/16, 7/16, 1/16}
+ }
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1,
+ mounted_ceiling = 1, not_in_creative_inventory = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal
+})
+
+--
+-- Craft recipes
+--
+
+local a = morelights.craft_items
+
+minetest.register_craft({
+ output = "morelights:bulb",
+ recipe = {
+ {"", a.glass, ""},
+ {"", a.copper, ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights:pole_d 2",
+ recipe = {
+ {a.dye_dark, a.steel, ""},
+ {"", a.steel, ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights:pole_l 2",
+ recipe = {
+ {a.dye_light, a.steel, ""},
+ {"", a.steel, ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights:chain_d",
+ recipe = {
+ {a.dye_dark, a.steel, ""},
+ {"", "", ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights:chain_l",
+ recipe = {
+ {a.dye_light, a.steel, ""},
+ {"", "", ""},
+ {"", a.steel, ""}
+ }
+})
diff --git a/mods/morelights/morelights/textures/morelights_bulb.png b/mods/morelights/morelights/textures/morelights_bulb.png
new file mode 100644
index 00000000..5dd8c549
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_bulb.png differ
diff --git a/mods/morelights/morelights/textures/morelights_chain_d_inv.png b/mods/morelights/morelights/textures/morelights_chain_d_inv.png
new file mode 100644
index 00000000..506f6ae2
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_chain_d_inv.png differ
diff --git a/mods/morelights/morelights/textures/morelights_chain_l_inv.png b/mods/morelights/morelights/textures/morelights_chain_l_inv.png
new file mode 100644
index 00000000..6c1f53f5
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_chain_l_inv.png differ
diff --git a/mods/morelights/morelights/textures/morelights_metal_dark.png b/mods/morelights/morelights/textures/morelights_metal_dark.png
new file mode 100644
index 00000000..121f5af9
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_metal_dark.png differ
diff --git a/mods/morelights/morelights/textures/morelights_metal_dark_32.png b/mods/morelights/morelights/textures/morelights_metal_dark_32.png
new file mode 100644
index 00000000..d68267a5
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_metal_dark_32.png differ
diff --git a/mods/morelights/morelights/textures/morelights_metal_light.png b/mods/morelights/morelights/textures/morelights_metal_light.png
new file mode 100644
index 00000000..5046c24e
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_metal_light.png differ
diff --git a/mods/morelights/morelights/textures/morelights_metal_light_32.png b/mods/morelights/morelights/textures/morelights_metal_light_32.png
new file mode 100644
index 00000000..8e3cefe7
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_metal_light_32.png differ
diff --git a/mods/morelights/morelights/textures/morelights_pole_d_inv.png b/mods/morelights/morelights/textures/morelights_pole_d_inv.png
new file mode 100644
index 00000000..a178c449
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_pole_d_inv.png differ
diff --git a/mods/morelights/morelights/textures/morelights_pole_l_inv.png b/mods/morelights/morelights/textures/morelights_pole_l_inv.png
new file mode 100644
index 00000000..eb18277e
Binary files /dev/null and b/mods/morelights/morelights/textures/morelights_pole_l_inv.png differ
diff --git a/mods/morelights/morelights_extras/init.lua b/mods/morelights/morelights_extras/init.lua
new file mode 100644
index 00000000..741d1a2c
--- /dev/null
+++ b/mods/morelights/morelights_extras/init.lua
@@ -0,0 +1,247 @@
+local S = minetest.get_translator("morelights_extras")
+
+
+minetest.register_node("morelights_extras:f_block", {
+ description = S("Futuristic Light Block"),
+ tiles = {"morelights_extras_f_block.png"},
+ paramtype = "light",
+ light_source = minetest.LIGHT_MAX,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.glass
+})
+
+do
+ local def = {
+ description = S("Grass Light"),
+ tiles = {
+ "default_grass.png^morelights_extras_blocklight.png",
+ "default_dirt.png",
+ "default_dirt.png^default_grass_side.png"
+ },
+ paramtype = "light",
+ light_source = 12,
+ groups = {
+ cracky = 2,
+ oddly_breakable_by_hand = 3,
+ handy = 1,
+ soil = 1,
+ },
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.glass
+ }
+
+ if morelights.game == "mineclone2" then
+ -- For MineClone 2, use node coloring to match environment.
+ -- See the mcl_core:dirt_with_grass definition in
+ -- ITEMS/mcl_core/nodes_base.lua.
+ def.tiles = {
+ "mcl_core_grass_block_top.png",
+ {name = "default_dirt.png", color = "#FFFFFF"}
+ }
+ def.overlay_tiles = {
+ {name = "morelights_extras_blocklight.png", color = "#FFFFFF"},
+ "",
+ {
+ name = "mcl_core_grass_block_side_overlay.png",
+ tileable_vertical = false
+ }
+ }
+ def.paramtype2 = "color"
+ def.palette = "mcl_core_palette_grass.png"
+ def.palette_index = 0
+ def.color = "#55aa60"
+ def.drop = "morelights_extras:dirt_with_grass"
+
+ def.on_construct = function(pos)
+ local node = minetest.get_node(pos)
+ if node.param2 == 0 then
+ local grass_node = mcl_core.get_grass_block_type(pos)
+ if grass_node.param2 ~= 0 then
+ minetest.set_node(pos, {
+ name = "morelights_extras:dirt_with_grass",
+ param2 = grass_node.param2
+ })
+ end
+ end
+ end
+ elseif morelights.game == "hades_revisited" then
+ -- For Hades Revisited, grass color is seasonal.
+ -- See hades_core/dirt.lua, ABM in hades_core/functions.lua.
+ def.tiles = {
+ "hades_core_grass_cover_colorable.png",
+ {name = "default_dirt.png", color = "#FFFFFF"},
+ }
+ def.overlay_tiles = {
+ {name = "morelights_extras_blocklight.png", color = "#FFFFFF"},
+ "",
+ {
+ name = "hades_core_grass_side_cover_colorable.png",
+ tileable_vertical = false
+ },
+ }
+ def.paramtype2 = "color"
+ def.palette = "hades_core_palette_grass.png"
+ def.palette_index = 0
+ def.color = "#acef6a"
+ -- To enable seasonal grass coloring.
+ def.groups.dirt_with_grass = 1
+ -- To prevent color retention on digging.
+ def.drop = "morelights_extras:dirt_with_grass"
+
+ def.on_place = function(itemstack, placer, pointed_thing)
+ local param2 = hades_core.get_seasonal_grass_color_param2()
+ return minetest.item_place(itemstack, placer, pointed_thing, param2)
+ end
+ end
+
+ minetest.register_node("morelights_extras:dirt_with_grass", def)
+end
+
+do
+ local tile = "default_stone_block.png"
+ if morelights.game == "mineclone2" then
+ tile = "mcl_stairs_stone_slab_top.png"
+ end
+
+ minetest.register_node("morelights_extras:stone_block", {
+ description = S("Stone Block Light"),
+ tiles = {tile .. "^morelights_extras_blocklight.png"},
+ paramtype = "light",
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.glass
+ })
+end
+
+do
+ local tile = "default_sandstone_block.png"
+ if morelights.game == "mineclone2" then
+ tile = "mcl_core_sandstone_smooth.png"
+ elseif morelights.game == "hades_revisited" then
+ tile = "default_sandstone.png"
+ end
+
+ minetest.register_node("morelights_extras:sandstone_block", {
+ description = S("Sandstone Block Light"),
+ tiles = {tile .. "^morelights_extras_blocklight.png"},
+ paramtype = "light",
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.glass
+ })
+end
+
+minetest.register_node("morelights_extras:stairlight", {
+ description = S("Stair Light (place on stairs)"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {-1/4, -13/16, -1/16, 1/4, -11/16, 0}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -13/16, -1/16, 1/4, -11/16, 0}
+ },
+ walkable = false,
+ tiles = {"morelights_metal_dark.png"},
+ overlay_tiles = {"", "morelights_extras_stairlight.png",
+ "", "", "morelights_extras_stairlight.png"},
+ inventory_image = "morelights_extras_stairlight_inv.png",
+ wield_image = "morelights_extras_stairlight_inv.png",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ light_source = 10,
+ sunlight_propagates = true,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1,
+ attached_node = 1},
+ _mcl_hardness = 0.15,
+ node_placement_prediction = "",
+ sounds = morelights.sounds.glass,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ if pointed_thing.type ~= "node" then
+ return itemstack
+ end
+
+ -- See builtin/game/item.lua.
+ local node = minetest.get_node(pointed_thing.under)
+
+ if placer and not placer:get_player_control().sneak then
+ local def = minetest.registered_nodes[node.name]
+ if def and def.on_rightclick then
+ return def.on_rightclick(pointed_thing.under, node, placer,
+ itemstack, pointed_thing) or itemstack
+ end
+ end
+
+ if node.param2 < 4 and (
+ node.name:match("^stairs:stair_")
+ or node.name:match("^mcl_stairs:stair_")
+ or node.name:match("^hades_stairs:stair_")
+ or minetest.get_item_group(node.name, "morelights_supports_stairlight") ~= 0
+ ) then
+ -- Set `above` to the node actually above the stair, since that's
+ -- where the node is placed.
+ pointed_thing.above =
+ vector.add(pointed_thing.under, vector.new(0, 1, 0))
+ return minetest.item_place_node(itemstack, placer, pointed_thing,
+ node.param2)
+ end
+
+ return itemstack
+ end,
+
+ on_rotate = screwdriver.rotate_simple
+})
+
+--
+-- Craft recipes
+--
+
+local a = morelights.craft_items
+
+minetest.register_craft({
+ output = "morelights_extras:f_block",
+ recipe = {
+ {a.crystal_fragment, a.steel, a.crystal_fragment},
+ {a.glass_pane, "morelights:bulb", a.glass_pane},
+ {a.crystal_fragment, a.steel, a.crystal_fragment}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_extras:dirt_with_grass",
+ recipe = {
+ {"", a.glass_pane, ""},
+ {"", "morelights:bulb", ""},
+ {a.grass, a.dirt, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_extras:stone_block",
+ recipe = {
+ {"", a.glass_pane, ""},
+ {"", "morelights:bulb", ""},
+ {"", a.stone_block, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_extras:sandstone_block",
+ recipe = {
+ {"", a.glass_pane, ""},
+ {"", "morelights:bulb", ""},
+ {"", a.sandstone_block, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_extras:stairlight",
+ recipe = {
+ {a.steel, "morelights:bulb", a.steel}
+ }
+})
diff --git a/mods/morelights/morelights_extras/locale/template.txt b/mods/morelights/morelights_extras/locale/template.txt
new file mode 100644
index 00000000..7f9e1962
--- /dev/null
+++ b/mods/morelights/morelights_extras/locale/template.txt
@@ -0,0 +1,7 @@
+# textdomain:morelights_extras
+
+Futuristic Light Block=
+Grass Light=
+Stone Block Light=
+Sandstone Block Light=
+Stair Light (place on stairs)=
diff --git a/mods/morelights/morelights_extras/mod.conf b/mods/morelights/morelights_extras/mod.conf
new file mode 100644
index 00000000..cba1468c
--- /dev/null
+++ b/mods/morelights/morelights_extras/mod.conf
@@ -0,0 +1,3 @@
+name = morelights_extras
+description = Provides miscellaneous lighting nodes.
+depends = morelights
diff --git a/mods/morelights/morelights_extras/textures/morelights_extras_blocklight.png b/mods/morelights/morelights_extras/textures/morelights_extras_blocklight.png
new file mode 100644
index 00000000..953c2a9c
Binary files /dev/null and b/mods/morelights/morelights_extras/textures/morelights_extras_blocklight.png differ
diff --git a/mods/morelights/morelights_extras/textures/morelights_extras_f_block.png b/mods/morelights/morelights_extras/textures/morelights_extras_f_block.png
new file mode 100644
index 00000000..c898dde8
Binary files /dev/null and b/mods/morelights/morelights_extras/textures/morelights_extras_f_block.png differ
diff --git a/mods/morelights/morelights_extras/textures/morelights_extras_stairlight.png b/mods/morelights/morelights_extras/textures/morelights_extras_stairlight.png
new file mode 100644
index 00000000..d3957759
Binary files /dev/null and b/mods/morelights/morelights_extras/textures/morelights_extras_stairlight.png differ
diff --git a/mods/morelights/morelights_extras/textures/morelights_extras_stairlight_inv.png b/mods/morelights/morelights_extras/textures/morelights_extras_stairlight_inv.png
new file mode 100644
index 00000000..c62708be
Binary files /dev/null and b/mods/morelights/morelights_extras/textures/morelights_extras_stairlight_inv.png differ
diff --git a/mods/morelights/morelights_modern/init.lua b/mods/morelights/morelights_modern/init.lua
new file mode 100644
index 00000000..02d67bec
--- /dev/null
+++ b/mods/morelights/morelights_modern/init.lua
@@ -0,0 +1,465 @@
+local S = minetest.get_translator("morelights_modern")
+
+
+minetest.register_node("morelights_modern:block", {
+ description = S("Modern Light Block"),
+ tiles = {"morelights_metal_dark.png^morelights_modern_block.png"},
+ paramtype = "light",
+ light_source = minetest.LIGHT_MAX,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.glass
+})
+
+minetest.register_node("morelights_modern:smallblock", {
+ description = S("Modern Light Block (small)"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 0, 1/4}
+ },
+ tiles = {
+ "morelights_metal_dark.png^morelights_modern_smallblock.png",
+ "morelights_metal_dark.png^morelights_modern_smallblock.png",
+ "[combine:16x16:0,0=morelights_metal_dark.png" ..
+ ":0,4=morelights_modern_smallblock.png"
+ },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sunlight_propagates = true,
+ light_source = 12,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.rotate_and_place(itemstack, placer, pointed_thing)
+ end
+})
+
+morelights.register_variants({
+ {
+ name = "morelights_modern:post_d",
+ description = S("Modern Post Light (@1)", S("dark")),
+ tiles = {
+ "morelights_metal_dark.png",
+ "morelights_metal_dark.png",
+ "morelights_metal_dark.png^morelights_modern_post.png"
+ }
+ },
+ {
+ name = "morelights_modern:post_l",
+ description = S("Modern Post Light (@1)", S("light")),
+ tiles = {
+ "morelights_metal_light.png",
+ "morelights_metal_light.png",
+ "morelights_metal_light.png^morelights_modern_post.png"
+ }
+ }
+},
+{
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = minetest.LIGHT_MAX,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.metal
+})
+
+morelights.register_variants({
+ {
+ name = "morelights_modern:streetpost_d",
+ description = S("Street Lamp Post (@1) - connects to bar lights",
+ S("dark")),
+ tiles = {"morelights_metal_dark.png"}
+ },
+ {
+ name = "morelights_modern:streetpost_l",
+ description = S("Street Lamp Post (@1) - connects to bar lights",
+ S("light")),
+ tiles = {"morelights_metal_light.png"}
+ }
+},
+{
+ drawtype = "nodebox",
+ node_box = {
+ type = "connected",
+ fixed = {-1/16, -1/2, -1/16, 1/16, 1/2, 1/16},
+ connect_front = {-1/16, 3/8, -1/2, 1/16, 7/16, -1/16},
+ connect_left = {-1/2, 3/8, -1/16, -1/16, 7/16, 1/16},
+ connect_back = {-1/16, 3/8, 1/16, 1/16, 7/16, 1/2},
+ connect_right = {1/16, 3/8, -1/16, 1/2, 7/16, 1/16},
+ },
+ connects_to = {
+ "morelights_modern:barlight_c",
+ "morelights_modern:barlight_s"
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.metal
+})
+
+minetest.register_node("morelights_modern:barlight_c", {
+ description = S("Ceiling Bar Light (connecting)"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "connected",
+ fixed = {-1/8, 3/8, -1/8, 1/8, 1/2, 1/8},
+ connect_front = {-1/8, 3/8, -1/2, 1/8, 1/2, -1/8},
+ connect_left = {-1/2, 3/8, -1/8, -1/8, 1/2, 1/8},
+ connect_back = {-1/8, 3/8, 1/8, 1/8, 1/2, 1/2},
+ connect_right = {1/8, 3/8, -1/8, 1/2, 1/2, 1/8},
+ },
+ connects_to = {
+ "morelights_modern:barlight_c",
+ "morelights_modern:barlight_s",
+ "morelights_modern:streetpost_d",
+ "morelights_modern:streetpost_l"
+ },
+ tiles = {
+ "morelights_metal_dark.png",
+ "morelights_modern_barlight.png",
+ "morelights_metal_dark.png"
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = minetest.LIGHT_MAX,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1, not_blocking_trains = 1},
+ _mcl_hardness = 0.15,
+ sounds = morelights.sounds.glass
+})
+
+-- TODO: Determine orientation of bar lights from nearby nodes.
+minetest.register_node("morelights_modern:barlight_s", {
+ description = S("Ceiling Bar Light (straight)"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {-1/2, 3/8, -1/8, 1/2, 1/2, 1/8},
+ },
+ tiles = {
+ "morelights_metal_dark.png",
+ "morelights_modern_barlight.png",
+ "morelights_metal_dark.png"
+ },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sunlight_propagates = true,
+ light_source = minetest.LIGHT_MAX,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1, not_blocking_trains = 1},
+ _mcl_hardness = 0.15,
+ sounds = morelights.sounds.glass
+})
+
+minetest.register_node("morelights_modern:ceilinglight", {
+ description = S("Modern Ceiling Light"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {-1/4, 3/8, -1/4, 1/4, 1/2, 1/4}
+ },
+ tiles = {
+ "morelights_metal_dark.png",
+ "morelights_metal_dark.png^morelights_modern_block.png"
+ },
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sunlight_propagates = true,
+ light_source = minetest.LIGHT_MAX,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1, not_blocking_trains = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.rotate_and_place(itemstack, placer, pointed_thing,
+ {[0] = 0, 20, 12, 16, 4, 8})
+ end
+})
+
+morelights.register_variants({
+ {
+ name = "morelights_modern:canlight_d",
+ description = S("Modern Can Light (@1)", S("dark")),
+ tiles = {"morelights_metal_dark.png^morelights_modern_canlight.png"}
+ },
+ {
+ name = "morelights_modern:canlight_l",
+ description = S("Modern Can Light (@1)", S("light")),
+ tiles = {"morelights_metal_light.png^morelights_modern_canlight.png"}
+ },
+},
+{
+ drawtype = "mesh",
+ mesh = "morelights_modern_canlight.obj",
+ collision_box = {
+ type = "fixed",
+ fixed = {-1/8, 0, -1/8, 1/8, 1/2, 1/8}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/8, 0, -1/8, 1/8, 1/2, 1/8}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal
+})
+
+minetest.register_node("morelights_modern:walllamp", {
+ description = S("Modern Wall Lamp"),
+ drawtype = "mesh",
+ mesh = "morelights_modern_walllamp.obj",
+ collision_box = {
+ type = "fixed",
+ fixed = {-1/8, -3/8, 1/8, 1/8, 1/4, 1/2}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/8, -3/8, 1/8, 1/8, 1/4, 1/2}
+ },
+ tiles = {"morelights_metal_dark_32.png^morelights_modern_walllamp.png"},
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sunlight_propagates = true,
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.rotate_and_place(itemstack, placer, pointed_thing,
+ {[0] = 6, 4, 1, 3, 0, 2})
+ end
+})
+
+morelights.register_variants({
+ {
+ name = "morelights_modern:tablelamp_d",
+ description = S("Modern Table Lamp (@1)", S("dark")),
+ tiles = {
+ "morelights_metal_light_32.png^morelights_modern_tablelamp_o.png",
+ "morelights_modern_tablelamp_d.png"
+ }
+ },
+ {
+ name = "morelights_modern:tablelamp_l",
+ description = S("Modern Table Lamp (@1)", S("light")),
+ tiles = {
+ "morelights_metal_dark_32.png^morelights_modern_tablelamp_o.png",
+ "morelights_modern_tablelamp_l.png"
+ }
+ },
+},
+{
+ drawtype = "mesh",
+ mesh = "morelights_modern_tablelamp.obj",
+ collision_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 7/16, 1/4}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 7/16, 1/4}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 10,
+ groups = {choppy = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.default
+})
+
+morelights.register_variants({
+ {
+ name = "morelights_modern:pathlight_d",
+ description = S("Modern Path Light (@1)", S("dark")),
+ tiles = {
+ "morelights_metal_dark_32.png^morelights_modern_pathlight.png"
+ }
+ },
+ {
+ name = "morelights_modern:pathlight_l",
+ description = S("Modern Path Light (@1)", S("light")),
+ tiles = {
+ "morelights_metal_light_32.png^morelights_modern_pathlight.png"
+ }
+ }
+},
+{
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-1/32, -8/16, -1/32, 1/32, 1/8, 1/32},
+ {-1/16, 1/8, -1/16, 1/16, 5/16, 1/16},
+ {-1/8, 5/16, -1/8, 1/8, 3/8, 1/8}
+ }
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/8, -1/2, -1/8, 1/8, 3/8, 1/8}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 8,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal
+})
+
+--
+-- Craft recipes
+--
+
+local a = morelights.craft_items
+
+minetest.register_craft({
+ output = "morelights_modern:block",
+ recipe = {
+ {"", a.steel, ""},
+ {a.glass_pane, "morelights:bulb", a.glass_pane},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:smallblock",
+ recipe = {
+ {"", a.glass_pane, ""},
+ {a.steel, "morelights:bulb", a.steel}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:post_d",
+ recipe = {
+ {a.dye_dark, a.steel, ""},
+ {"", "morelights:bulb", ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:post_l",
+ recipe = {
+ {a.dye_light, a.steel, ""},
+ {"", "morelights:bulb", ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:streetpost_d 2",
+ recipe = {
+ {a.dye_dark, a.steel, a.steel},
+ {"", a.steel, ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:streetpost_l 2",
+ recipe = {
+ {a.dye_light, a.steel, a.steel},
+ {"", a.steel, ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:barlight_c 4",
+ recipe = {
+ {a.steel, a.steel, a.steel},
+ {a.copper, a.glass, a.copper}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:barlight_c",
+ type = "shapeless",
+ recipe = {"morelights_modern:barlight_s"}
+})
+
+minetest.register_craft({
+ output = "morelights_modern:barlight_s",
+ type = "shapeless",
+ recipe = {"morelights_modern:barlight_c"}
+})
+
+minetest.register_craft({
+ output = "morelights_modern:ceilinglight",
+ recipe = {
+ {a.steel, "morelights:bulb", a.steel},
+ {"", a.glass_pane, ""},
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:canlight_d",
+ recipe = {
+ {a.dye_dark, a.steel, ""},
+ {a.steel, "morelights:bulb", a.steel},
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:canlight_l",
+ recipe = {
+ {a.dye_light, a.steel, ""},
+ {a.steel, "morelights:bulb", a.steel},
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:walllamp",
+ recipe = {
+ {"", a.glass_pane, ""},
+ {a.glass_pane, "morelights:bulb", a.steel},
+ {"", a.dye_dark, a.steel}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:tablelamp_d",
+ recipe = {
+ {"", a.steel, ""},
+ {a.wool_dark, "morelights:bulb", a.wool_dark},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:tablelamp_l",
+ recipe = {
+ {"", a.steel, ""},
+ {a.wool_light, "morelights:bulb", a.wool_light},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:pathlight_d",
+ recipe = {
+ {a.dye_dark, "morelights:bulb", ""},
+ {"", a.steel, ""},
+ {"", a.steel, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_modern:pathlight_l",
+ recipe = {
+ {a.dye_light, "morelights:bulb", ""},
+ {"", a.steel, ""},
+ {"", a.steel, ""}
+ }
+})
diff --git a/mods/morelights/morelights_modern/locale/template.txt b/mods/morelights/morelights_modern/locale/template.txt
new file mode 100644
index 00000000..516543de
--- /dev/null
+++ b/mods/morelights/morelights_modern/locale/template.txt
@@ -0,0 +1,15 @@
+# textdomain:morelights_modern
+
+dark=
+light=
+Modern Light Block=
+Modern Light Block (small)=
+Modern Post Light (@1)=
+Street Lamp Post (@1) - connects to bar lights=
+Ceiling Bar Light (connecting)=
+Ceiling Bar Light (straight)=
+Modern Ceiling Light=
+Modern Can Light (@1)=
+Modern Wall Lamp=
+Modern Table Lamp (@1)=
+Modern Path Light (@1)=
diff --git a/mods/morelights/morelights_modern/mod.conf b/mods/morelights/morelights_modern/mod.conf
new file mode 100644
index 00000000..a39125fc
--- /dev/null
+++ b/mods/morelights/morelights_modern/mod.conf
@@ -0,0 +1,3 @@
+name = morelights_modern
+description = Provides modern-style lighting nodes.
+depends = morelights
diff --git a/mods/morelights/morelights_modern/models/morelights_modern_canlight.obj b/mods/morelights/morelights_modern/models/morelights_modern_canlight.obj
new file mode 100644
index 00000000..280d96ef
--- /dev/null
+++ b/mods/morelights/morelights_modern/models/morelights_modern_canlight.obj
@@ -0,0 +1,140 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T18:42:48Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.79 (sub 0) OBJ File: 'modern_can_2.blend'
+# www.blender.org
+
+# vertices [32]
+
+v -0.051777 0 -0.125
+v -0.051777 0.5 -0.125
+v 0.051777 0.5 -0.125
+v 0.051777 0 -0.125
+v 0.125 0 0.051777
+v 0.051777 0 0.125
+v -0.051777 0 0.125
+v -0.125 0 0.051777
+v -0.125 0 -0.051777
+v -0.1 0 -0.041421
+v -0.1 0 0.041421
+v -0.041421 0 0.1
+v 0.041421 0 0.1
+v 0.1 0 0.041421
+v 0.125 0 -0.051777
+v 0.1 0 -0.041421
+v 0.041421 0 -0.1
+v -0.041421 0 -0.1
+v -0.125 0.5 -0.051777
+v -0.125 0.5 0.051777
+v -0.051777 0.5 0.125
+v 0.051777 0.5 0.125
+v 0.125 0.5 0.051777
+v 0.125 0.5 -0.051777
+v 0.041421 0.125 -0.1
+v -0.041421 0.125 -0.1
+v -0.1 0.125 -0.041421
+v 0.1 0.125 -0.041421
+v 0.1 0.125 0.041421
+v 0.041421 0.125 0.1
+v -0.041421 0.125 0.1
+v -0.1 0.125 0.041421
+
+# normals [10]
+
+vn 0 0 -1
+vn -0.7071 0 -0.7071
+vn 0 1 0
+vn 0.7071 0 -0.7071
+vn 1 0 0
+vn 0.7071 0 0.7071
+vn 0 0 1
+vn -0.7071 0 0.7071
+vn -1 0 0
+vn 0 -1 0
+
+# uvs [52]
+
+vt 0.375 0.5
+vt 0.375 1
+vt 0.25 1
+vt 0.25 0.5
+vt 0.5 0.5
+vt 0.5 1
+vt 0.239277 0.9375
+vt 0.135723 0.9375
+vt 0.0625 0.864257
+vt 0.0625 0.760704
+vt 0.135723 0.6875
+vt 0.239277 0.6875
+vt 0.3125 0.760704
+vt 0.3125 0.864257
+vt 0.125 1
+vt 0.125 0.5
+vt 0 1
+vt 0 0.5
+vt 1 0.5
+vt 1 1
+vt 0.875 1
+vt 0.875 0.5
+vt 0.75 1
+vt 0.75 0.5
+vt 0.625 1
+vt 0.625 0.5
+vt 0.375 0.4375
+vt 0.25 0.4375
+vt 0.25 0.3125
+vt 0.375 0.3125
+vt 0.5 0.4375
+vt 0.5 0.3125
+vt 0.176777 0
+vt 0.25 0.073223
+vt 0.25 0.176777
+vt 0.176777 0.25
+vt 0.073223 0.25
+vt 0 0.176777
+vt 0 0.073223
+vt 0.073223 0
+vt 0.125 0.4375
+vt 0.125 0.3125
+vt 0.625 0.4375
+vt 0.625 0.3125
+vt 0.75 0.4375
+vt 0.75 0.3125
+vt 0.875 0.4375
+vt 0.875 0.3125
+vt 1 0.4375
+vt 1 0.3125
+vt 0 0.4375
+vt 0 0.3125
+
+# objects [1]
+
+o CanLight
+
+s off
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 9/5/2 19/6/2 2/2/2 1/1/2
+f 3/7/3 2/8/3 19/9/3 20/10/3 21/11/3 22/12/3 23/13/3 24/14/3
+f 4/4/4 3/3/4 24/15/4 15/16/4
+f 15/16/5 24/15/5 23/17/5 5/18/5
+f 5/19/6 23/20/6 22/21/6 6/22/6
+f 6/22/7 22/21/7 21/23/7 7/24/7
+f 7/24/8 21/23/8 20/25/8 8/26/8
+f 8/26/9 20/25/9 19/6/9 9/5/9
+f 18/27/7 17/28/7 25/29/7 26/30/7
+f 10/31/6 18/27/6 26/30/6 27/32/6
+f 25/33/10 28/34/10 29/35/10 30/36/10 31/37/10 32/38/10 27/39/10 26/40/10
+f 17/28/8 16/41/8 28/42/8 25/29/8
+f 11/43/5 10/31/5 27/32/5 32/44/5
+f 12/45/4 11/43/4 32/44/4 31/46/4
+f 13/47/1 12/45/1 31/46/1 30/48/1
+f 14/49/2 13/47/2 30/48/2 29/50/2
+f 16/41/9 14/51/9 29/52/9 28/42/9
+f 10/31/10 9/5/10 1/1/10 18/27/10
+f 1/1/10 4/4/10 17/28/10 18/27/10
+f 4/4/10 15/16/10 16/41/10 17/28/10
+f 14/51/10 16/41/10 15/16/10 5/18/10
+f 14/49/10 5/19/10 6/22/10 13/47/10
+f 13/47/10 6/22/10 7/24/10 12/45/10
+f 7/24/10 8/26/10 11/43/10 12/45/10
+f 11/43/10 8/26/10 9/5/10 10/31/10
+
diff --git a/mods/morelights/morelights_modern/models/morelights_modern_tablelamp.obj b/mods/morelights/morelights_modern/models/morelights_modern_tablelamp.obj
new file mode 100644
index 00000000..7c19dbb4
--- /dev/null
+++ b/mods/morelights/morelights_modern/models/morelights_modern_tablelamp.obj
@@ -0,0 +1,146 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T06:56:07Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [44]
+
+v -0.1875 -0.5 0.1875
+v -0.1875 -0.4375 0.1875
+v -0.1875 -0.4375 -0.1875
+v -0.1875 -0.5 -0.1875
+v 0.1875 -0.4375 -0.1875
+v 0.1875 -0.5 -0.1875
+v 0.1875 -0.4375 0.1875
+v 0.1875 -0.5 0.1875
+v 0.03125 0.3125 0.03125
+v 0.21875 0.3125 0.03125
+v 0.21875 0.34375 0
+v 0.03125 0.34375 0
+v 0.21875 0.3125 -0.03125
+v 0.03125 0.3125 -0.03125
+v -0.21875 0.3125 0.03125
+v -0.03125 0.3125 0.03125
+v -0.03125 0.34375 0
+v -0.21875 0.34375 0
+v -0.03125 0.3125 -0.03125
+v -0.21875 0.3125 -0.03125
+v -0.03125 -0.4375 0.03125
+v -0.03125 0.375 0.03125
+v -0.03125 0.375 -0.03125
+v -0.03125 -0.4375 -0.03125
+v 0.03125 0.375 -0.03125
+v 0.03125 -0.4375 -0.03125
+v 0.03125 0.375 0.03125
+v 0.03125 -0.4375 0.03125
+v -0.25 -0.0625 0.25
+v -0.25 0.4375 0.25
+v -0.25 0.4375 -0.25
+v -0.25 -0.0625 -0.25
+v 0.25 0.4375 -0.25
+v 0.25 -0.0625 -0.25
+v 0.25 0.4375 0.25
+v 0.25 -0.0625 0.25
+v -0.21875 -0.0625 -0.21875
+v -0.21875 0.4375 -0.21875
+v 0.21875 0.4375 -0.21875
+v 0.21875 -0.0625 -0.21875
+v -0.21875 -0.0625 0.21875
+v -0.21875 0.4375 0.21875
+v 0.21875 0.4375 0.21875
+v 0.21875 -0.0625 0.21875
+
+# normals [8]
+
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 0 1
+vn 0 -1 0
+vn 0 1 0
+vn 0 0.7071 0.7071
+vn 0 0.7071 -0.7071
+
+# uvs [39]
+
+vt 0.3125 0.0625
+vt 0.3125 0
+vt 0.6875 0
+vt 0.6875 0.0625
+vt 0.6875 0.3125
+vt 0.6875 0.6875
+vt 0.3125 0.6875
+vt 0.3125 0.3125
+vt 0.53125 0.46875
+vt 0.71875 0.46875
+vt 0.71875 0.5
+vt 0.53125 0.5
+vt 0.71875 0.53125
+vt 0.53125 0.53125
+vt 0.28125 0.46875
+vt 0.46875 0.46875
+vt 0.46875 0.5
+vt 0.28125 0.5
+vt 0.46875 0.53125
+vt 0.28125 0.53125
+vt 0.3125 0.875
+vt 0.25 0.875
+vt 0.25 0.0625
+vt 0.3125 0.53125
+vt 0.25 0.53125
+vt 0.25 0.46875
+vt 0.3125 0.46875
+vt 0 0
+vt 0 1
+vt 1 1
+vt 1 0
+vt 0.0625 0
+vt 0.0625 1
+vt 0.9375 1
+vt 0.9375 0
+vt 0.9375 0.0625
+vt 0.0625 0.0625
+vt 0.0625 0.9375
+vt 0.9375 0.9375
+
+# objects [2]
+
+g LampBase
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 4/1/2 3/2/2 5/3/2 6/4/2
+f 6/3/3 5/4/3 7/1/3 8/2/3
+f 8/3/4 7/4/4 2/1/4 1/2/4
+f 4/5/5 6/6/5 8/7/5 1/8/5
+f 5/5/6 3/6/6 2/7/6 7/8/6
+f 9/9/7 10/10/7 11/11/7 12/12/7
+f 12/12/8 11/11/8 13/13/8 14/14/8
+f 15/15/7 16/16/7 17/17/7 18/18/7
+f 18/18/8 17/17/8 19/19/8 20/20/8
+f 21/1/1 22/21/1 23/22/1 24/23/1
+f 24/1/2 23/21/2 25/22/2 26/23/2
+f 26/1/3 25/21/3 27/22/3 28/23/3
+f 28/1/4 27/21/4 22/22/4 21/23/4
+f 25/24/6 23/25/6 22/26/6 27/27/6
+
+g LampShade
+
+s 1
+f 29/28/1 30/29/1 31/30/1 32/31/1
+f 32/28/2 31/29/2 33/30/2 34/31/2
+f 34/31/3 33/30/3 35/29/3 36/28/3
+f 36/31/4 35/30/4 30/29/4 29/28/4
+f 37/32/2 38/33/2 39/34/2 40/35/2
+f 41/32/1 42/33/1 38/34/1 37/35/1
+f 40/32/3 39/33/3 43/34/3 44/35/3
+f 44/32/4 43/33/4 42/34/4 41/35/4
+f 30/28/6 35/31/6 43/36/6 42/37/6
+f 31/29/6 30/28/6 42/37/6 38/38/6
+f 33/30/6 31/29/6 38/38/6 39/39/6
+f 35/31/6 33/30/6 39/39/6 43/36/6
+f 29/29/5 32/28/5 37/37/5 41/38/5
+f 32/28/5 34/31/5 40/36/5 37/37/5
+f 34/31/5 36/30/5 44/39/5 40/36/5
+f 36/30/5 29/29/5 41/38/5 44/39/5
+
diff --git a/mods/morelights/morelights_modern/models/morelights_modern_walllamp.obj b/mods/morelights/morelights_modern/models/morelights_modern_walllamp.obj
new file mode 100644
index 00000000..3baab52f
--- /dev/null
+++ b/mods/morelights/morelights_modern/models/morelights_modern_walllamp.obj
@@ -0,0 +1,115 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T19:07:59Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [28]
+
+v 0.125 -0.25 0.375
+v 0.125 0.25 0.375
+v -0.125 0.25 0.375
+v -0.125 -0.25 0.375
+v -0.125 0.25 0.125
+v -0.125 -0.25 0.125
+v 0.125 0.25 0.125
+v 0.125 -0.25 0.125
+v -0.03125 -0.34375 0.4375
+v -0.03125 -0.28125 0.4375
+v -0.03125 -0.28125 0.28125
+v -0.03125 -0.34375 0.21875
+v -0.03125 -0.25 0.28125
+v -0.03125 -0.25 0.21875
+v 0.03125 -0.34375 0.21875
+v 0.03125 -0.28125 0.28125
+v 0.03125 -0.28125 0.4375
+v 0.03125 -0.34375 0.4375
+v 0.03125 -0.25 0.21875
+v 0.03125 -0.25 0.28125
+v 0.125 -0.375 0.5
+v 0.125 0.125 0.5
+v -0.125 0.125 0.5
+v -0.125 -0.375 0.5
+v -0.125 0.125 0.4375
+v -0.125 -0.375 0.4375
+v 0.125 0.125 0.4375
+v 0.125 -0.375 0.4375
+
+# normals [6]
+
+vn 0 0 1
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 -1 0
+vn 0 1 0
+
+# uvs [41]
+
+vt 1 0.5
+vt 1 1
+vt 0.75 1
+vt 0.75 0.5
+vt 0.5 1
+vt 0.5 0.5
+vt 0.25 1
+vt 0.25 0.5
+vt 0 1
+vt 0 0.5
+vt 0 0.25
+vt 0.25 0.25
+vt 0.03125 0.0625
+vt 0.03125 0.125
+vt 0.1875 0.125
+vt 0.25 0.0625
+vt 0.1875 0.15625
+vt 0.25 0.15625
+vt 0.25 0
+vt 0.03125 0
+vt 0.1875 0.25
+vt 0.03125 0.25
+vt 0.03125 0.1875
+vt 0.1875 0.1875
+vt 0.34375 0.0625
+vt 0.34375 0
+vt 0.21875 0.1875
+vt 0.21875 0.25
+vt 0.9375 0
+vt 0.9375 0.5
+vt 0.6875 0.5
+vt 0.6875 0
+vt 0.625 0.5
+vt 0.625 0
+vt 0.375 0.5
+vt 0.375 0
+vt 0.3125 0.5
+vt 0.3125 0
+vt 0.9375 0.25
+vt 1 0.25
+vt 1 0
+
+# objects [1]
+
+o WallLamp
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 4/4/2 3/3/2 5/5/2 6/6/2
+f 6/6/3 5/5/3 7/7/3 8/8/3
+f 8/8/4 7/7/4 2/9/4 1/10/4
+f 4/8/5 6/10/5 8/11/5 1/12/5
+f 5/8/6 3/10/6 2/11/6 7/12/6
+f 9/13/2 10/14/2 11/15/2 12/16/2
+f 12/16/2 11/15/2 13/17/2 14/18/2
+f 15/16/4 16/15/4 17/14/4 18/13/4
+f 9/13/5 12/16/5 15/19/5 18/20/5
+f 11/21/6 10/22/6 17/23/6 16/24/6
+f 16/15/4 15/16/4 19/18/4 20/17/4
+f 15/19/3 12/16/3 14/25/3 19/26/3
+f 11/21/1 16/24/1 20/27/1 13/28/1
+f 21/29/1 22/30/1 23/31/1 24/32/1
+f 24/32/2 23/31/2 25/33/2 26/34/2
+f 26/34/3 25/33/3 27/35/3 28/36/3
+f 28/36/4 27/35/4 22/37/4 21/38/4
+f 24/39/5 26/40/5 28/1/5 21/30/5
+f 25/29/6 23/41/6 22/40/6 27/39/6
+
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_barlight.png b/mods/morelights/morelights_modern/textures/morelights_modern_barlight.png
new file mode 100644
index 00000000..d691b576
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_barlight.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_block.png b/mods/morelights/morelights_modern/textures/morelights_modern_block.png
new file mode 100644
index 00000000..e533a195
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_block.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_canlight.png b/mods/morelights/morelights_modern/textures/morelights_modern_canlight.png
new file mode 100644
index 00000000..f2e54878
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_canlight.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_pathlight.png b/mods/morelights/morelights_modern/textures/morelights_modern_pathlight.png
new file mode 100644
index 00000000..dabda5e2
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_pathlight.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_post.png b/mods/morelights/morelights_modern/textures/morelights_modern_post.png
new file mode 100644
index 00000000..3581a0f9
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_post.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_smallblock.png b/mods/morelights/morelights_modern/textures/morelights_modern_smallblock.png
new file mode 100644
index 00000000..d6bd515f
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_smallblock.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_d.png b/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_d.png
new file mode 100644
index 00000000..410cd784
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_d.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_l.png b/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_l.png
new file mode 100644
index 00000000..2d2b4ee8
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_l.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_o.png b/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_o.png
new file mode 100644
index 00000000..826cb089
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_tablelamp_o.png differ
diff --git a/mods/morelights/morelights_modern/textures/morelights_modern_walllamp.png b/mods/morelights/morelights_modern/textures/morelights_modern_walllamp.png
new file mode 100644
index 00000000..d7fae711
Binary files /dev/null and b/mods/morelights/morelights_modern/textures/morelights_modern_walllamp.png differ
diff --git a/mods/morelights/morelights_vintage/init.lua b/mods/morelights/morelights_vintage/init.lua
new file mode 100644
index 00000000..545637fd
--- /dev/null
+++ b/mods/morelights/morelights_vintage/init.lua
@@ -0,0 +1,364 @@
+local S = minetest.get_translator("morelights_vintage")
+
+
+-- Register custom brass ingot if a suitable replacement doesn't exist.
+if morelights.craft_items.brass == nil then
+ morelights.craft_items.brass = "morelights_vintage:brass_ingot"
+
+ minetest.register_craftitem("morelights_vintage:brass_ingot", {
+ description = S("Brass Ingot"),
+ inventory_image = "default_steel_ingot.png^[multiply:#FFCE69"
+ })
+
+ minetest.register_craft({
+ output = "morelights_vintage:brass_ingot 2",
+ type = "shapeless",
+ recipe = {
+ morelights.craft_items.copper,
+ morelights.craft_items.tin
+ }
+ })
+end
+
+minetest.register_node("morelights_vintage:chain_b", {
+ description = S("Brass Chain"),
+ drawtype = "mesh",
+ mesh = "morelights_chain.obj",
+ collision_box = {
+ type = "fixed",
+ fixed = {-1/16, -8/16, -1/16, 1/16, 8/16, 1/16}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/16, -8/16, -1/16, 1/16, 8/16, 1/16}
+ },
+ tiles = {"morelights_vintage_brass_32.png"},
+ inventory_image = "morelights_vintage_chain_b_inv.png",
+ wield_image = "morelights_vintage_chain_b_inv.png",
+ paramtype = "light",
+ sunlight_propagates = true,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1,
+ mounted_ceiling = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.on_place_hanging(itemstack, placer, pointed_thing,
+ "morelights_vintage:chain_ceiling_b")
+ end
+})
+
+minetest.register_node("morelights_vintage:chain_ceiling_b", {
+ drawtype = "mesh",
+ mesh = "morelights_chain_ceiling.obj",
+ collision_box = {
+ type = "fixed",
+ fixed = {
+ {-3/16, 7/16, -3/16, 3/16, 1/2, 3/16},
+ {-1/16, -1/2, -1/16, 1/16, 7/16, 1/16}
+ }
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {
+ {-3/16, 7/16, -3/16, 3/16, 1/2, 3/16},
+ {-1/16, -1/2, -1/16, 1/16, 7/16, 1/16}
+ }
+ },
+ tiles = {"morelights_vintage_brass_32.png"},
+ drop = "morelights_vintage:chain_b",
+ paramtype = "light",
+ sunlight_propagates = true,
+ groups = {cracky = 3, oddly_breakable_by_hand = 3, handy = 1,
+ mounted_ceiling = 1, mounted_ceiling = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.metal
+})
+
+minetest.register_node("morelights_vintage:block", {
+ description = S("Vintage Light Block"),
+ tiles = {"morelights_vintage_block_glass.png^morelights_vintage_block_frame.png"},
+ paramtype = "light",
+ light_source = minetest.LIGHT_MAX,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.glass
+})
+
+minetest.register_node("morelights_vintage:smallblock", {
+ description = S("Vintage Light Block (small)"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 0, 1/4}
+ },
+ tiles = {
+ "morelights_vintage_block_glass.png^morelights_vintage_block_frame.png",
+ "morelights_vintage_block_glass.png^morelights_vintage_block_frame.png",
+ "[combine:16x16:0,4=" ..
+ "(morelights_vintage_block_glass.png^morelights_vintage_block_frame.png)"
+ },
+ use_texture_alpha = "opaque",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ sunlight_propagates = true,
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return morelights.rotate_and_place(itemstack, placer, pointed_thing)
+ end
+})
+
+minetest.register_node("morelights_vintage:lantern_f", {
+ description = S("Vintage Lantern (floor, wall, or ceiling)"),
+ drawtype = "mesh",
+ mesh = "morelights_vintage_lantern_f.obj",
+ tiles = {
+ "morelights_vintage_lantern_frame.png^morelights_vintage_lantern_glass.png",
+ "morelights_metal_dark_32.png"
+ },
+ use_texture_alpha = "opaque",
+ collision_box = {
+ type = "fixed",
+ fixed = {-3/16, -1/2, -3/16, 3/16, 1/16, 3/16}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-3/16, -1/2, -3/16, 3/16, 1/16, 3/16}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass,
+
+ on_place = function(itemstack, placer, pointed_thing)
+ local wdir = minetest.dir_to_wallmounted(
+ vector.subtract(pointed_thing.under, pointed_thing.above))
+ local fakeStack = ItemStack(itemstack)
+
+ if wdir == 0 then
+ fakeStack:set_name("morelights_vintage:lantern_c")
+ elseif wdir == 1 then
+ fakeStack:set_name("morelights_vintage:lantern_f")
+ else
+ fakeStack:set_name("morelights_vintage:lantern_w")
+ end
+
+ minetest.item_place(fakeStack, placer, pointed_thing, wdir)
+ itemstack:set_count(fakeStack:get_count())
+
+ return itemstack
+ end
+})
+
+minetest.register_node("morelights_vintage:lantern_c", {
+ drawtype = "mesh",
+ mesh = "morelights_vintage_lantern_c.obj",
+ tiles = {
+ "morelights_vintage_lantern_frame.png^morelights_vintage_lantern_glass.png",
+ "morelights_metal_dark_32.png"
+ },
+ use_texture_alpha = "opaque",
+ collision_box = {
+ type = "fixed",
+ fixed = {-3/16, -1/16, -3/16, 3/16, 1/2, 3/16}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-3/16, 0, -3/16, 3/16, 1/2, 3/16}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1,
+ not_in_creative_inventory = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass,
+ drop = "morelights_vintage:lantern_f"
+})
+
+minetest.register_node("morelights_vintage:lantern_w", {
+ drawtype = "mesh",
+ mesh = "morelights_vintage_lantern_w.obj",
+ tiles = {
+ "morelights_vintage_lantern_frame.png^morelights_vintage_lantern_glass.png",
+ "morelights_metal_dark_32.png"
+ },
+ use_texture_alpha = "clip",
+ collision_box = {
+ type = "fixed",
+ fixed = {-3/16, -1/4, -5/16, 3/16, 1/8, 3/16}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-3/16, -1/4, -5/16, 3/16, 1/8, 3/16}
+ },
+ paramtype = "light",
+ paramtype2 = "wallmounted",
+ sunlight_propagates = true,
+ light_source = 12,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1,
+ not_in_creative_inventory = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass,
+ drop = "morelights_vintage:lantern_f"
+})
+
+minetest.register_node("morelights_vintage:hangingbulb", {
+ description = S("Vintage Hanging Light Bulb"),
+ drawtype = "mesh",
+ mesh = "morelights_vintage_hangingbulb.obj",
+ tiles = {
+ "morelights_vintage_hangingbulb.png" ..
+ "^[lowpart:50:morelights_metal_dark_32.png"
+ },
+ inventory_image = "morelights_vintage_hangingbulb_inv.png",
+ wield_image = "morelights_vintage_hangingbulb_inv.png",
+ use_texture_alpha = "blend",
+ collision_box = {
+ type = "fixed",
+ fixed = {-1/8, -1/8, -1/8, 1/8, 1/2, 1/8}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/8, -1/8, -1/8, 1/8, 1/2, 1/8}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 10,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.15,
+ sounds = morelights.sounds.glass
+})
+
+minetest.register_node("morelights_vintage:oillamp", {
+ description = S("Vintage Oil Lamp"),
+ drawtype = "mesh",
+ mesh = "morelights_vintage_oillamp.obj",
+ tiles = {
+ {
+ name = "morelights_vintage_oil_flame.png",
+ animation = {
+ type = "sheet_2d",
+ frames_w = 16,
+ frames_h = 1,
+ frame_length = 0.3
+ }
+ },
+ "morelights_vintage_oillamp.png",
+ "morelights_vintage_brass_32.png"
+ },
+ use_texture_alpha = "clip",
+ collision_box = {
+ type = "fixed",
+ fixed = {-1/8, -1/2, -1/8, 1/8, 1/4, 1/8}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/8, -1/2, -1/8, 1/8, 1/4, 1/8}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 8,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.2,
+ sounds = morelights.sounds.glass
+})
+
+minetest.register_node("morelights_vintage:chandelier", {
+ description = S("Vintage Chandelier"),
+ drawtype = "mesh",
+ mesh = "morelights_vintage_chandelier.obj",
+ tiles = {
+ "morelights_vintage_chandelier.png",
+ "morelights_vintage_brass_32.png^[multiply:#DFDFDF"
+ },
+ use_texture_alpha = "clip",
+ collision_box = {
+ type = "fixed",
+ fixed = {-3/8, -1/2, -3/8, 3/8, 1/2, 3/8}
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-3/8, -1/2, -3/8, 3/8, 1/2, 3/8}
+ },
+ paramtype = "light",
+ sunlight_propagates = true,
+ light_source = 10,
+ groups = {cracky = 2, oddly_breakable_by_hand = 3, handy = 1},
+ _mcl_hardness = 0.3,
+ sounds = morelights.sounds.glass
+})
+
+--
+-- Craft recipes
+--
+
+local a = morelights.craft_items
+
+minetest.register_craft({
+ output = "morelights_vintage:chain_b",
+ recipe = {
+ {"", a.brass, ""},
+ {"", "", ""},
+ {"", a.brass, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_vintage:block",
+ recipe = {
+ {"", a.wood_dark, ""},
+ {a.glass_pane, "morelights:bulb", a.glass_pane},
+ {"", a.wood_dark, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_vintage:smallblock",
+ recipe = {
+ {"", a.glass_pane, ""},
+ {a.wood_dark, "morelights:bulb", a.wood_dark}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_vintage:lantern_f",
+ recipe = {
+ {"", a.steel, ""},
+ {a.glass_pane, "morelights:bulb", a.glass_pane},
+ {a.stick, a.steel, a.stick}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_vintage:hangingbulb",
+ recipe = {
+ {"", a.steel, ""},
+ {"", a.copper, ""},
+ {"", "morelights:bulb", ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_vintage:oillamp",
+ recipe = {
+ {"", a.glass, ""},
+ {"", a.string, ""},
+ {"", a.brass, ""}
+ }
+})
+
+minetest.register_craft({
+ output = "morelights_vintage:chandelier",
+ recipe = {
+ {"", a.brass, ""},
+ {"morelights:bulb", a.brass, "morelights:bulb"},
+ {a.steel, a.brass, a.steel}
+ }
+})
diff --git a/mods/morelights/morelights_vintage/locale/template.txt b/mods/morelights/morelights_vintage/locale/template.txt
new file mode 100644
index 00000000..84913ab4
--- /dev/null
+++ b/mods/morelights/morelights_vintage/locale/template.txt
@@ -0,0 +1,10 @@
+# textdomain:morelights_vintage
+
+Brass Ingot=
+Brass Chain=
+Vintage Light Block=
+Vintage Light Block (small)=
+Vintage Lantern (floor, wall, or ceiling)=
+Vintage Hanging Light Bulb=
+Vintage Oil Lamp=
+Vintage Chandelier=
diff --git a/mods/morelights/morelights_vintage/mod.conf b/mods/morelights/morelights_vintage/mod.conf
new file mode 100644
index 00000000..022d03d7
--- /dev/null
+++ b/mods/morelights/morelights_vintage/mod.conf
@@ -0,0 +1,3 @@
+name = morelights_vintage
+description = Provides antique and historical-style lighting nodes.
+depends = morelights
diff --git a/mods/morelights/morelights_vintage/models/morelights_vintage_chandelier.obj b/mods/morelights/morelights_vintage/models/morelights_vintage_chandelier.obj
new file mode 100644
index 00000000..4bdcedd3
--- /dev/null
+++ b/mods/morelights/morelights_vintage/models/morelights_vintage_chandelier.obj
@@ -0,0 +1,86 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T19:28:47Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [22]
+
+v 0.25 -0.5 -0.433013
+v 0 -0.5 0
+v 0 0.5 0
+v 0.25 0.5 -0.433013
+v -0.25 -0.5 -0.433013
+v -0.25 0.5 -0.433013
+v 0.5 -0.5 0
+v 0.5 0.5 0
+v -0.25 -0.5 0.433013
+v -0.25 0.5 0.433013
+v 0.25 -0.5 0.433013
+v 0.25 0.5 0.433013
+v -0.5 -0.5 0
+v -0.5 0.5 0
+v 0.03125 -0.375 -0.03125
+v 0.03125 -0.375 0.03125
+v -0.03125 -0.375 0.03125
+v -0.03125 -0.375 -0.03125
+v 0.03125 0.5 -0.03125
+v -0.03125 0.5 -0.03125
+v -0.03125 0.5 0.03125
+v 0.03125 0.5 0.03125
+
+# normals [8]
+
+vn -0.866 0 -0.5
+vn -0.866 0 0.5
+vn 0 0 -1
+vn 0 -1 0
+vn 0 1 0
+vn 1 0 0
+vn 0 0 1
+vn -1 0 0
+
+# uvs [20]
+
+vt 0 0
+vt 0.5 0
+vt 0.5 1
+vt 0 1
+vt 1 0
+vt 1 1
+vt 0.4375 0.0625
+vt 0.375 0.0625
+vt 0.375 0
+vt 0.4375 0
+vt 0.4375 0.9375
+vt 0.4375 1
+vt 0.375 1
+vt 0.375 0.9375
+vt 0.625 0.0625
+vt 0.625 0.9375
+vt 0.5625 0.9375
+vt 0.5625 0.0625
+vt 0.5 0.9375
+vt 0.5 0.0625
+
+# objects [2]
+
+g Chandelier
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 5/1/2 2/2/2 3/3/2 6/4/2
+f 7/1/3 2/2/3 3/3/3 8/4/3
+f 3/3/1 2/2/1 9/5/1 10/6/1
+f 3/3/2 2/2/2 11/5/2 12/6/2
+f 3/3/3 2/2/3 13/5/3 14/6/3
+
+g Pole
+
+s 1
+f 15/7/4 16/8/4 17/9/4 18/10/4
+f 19/11/5 20/12/5 21/13/5 22/14/5
+f 15/7/6 19/11/6 22/14/6 16/8/6
+f 16/15/7 22/16/7 21/17/7 17/18/7
+f 17/18/8 21/17/8 20/19/8 18/20/8
+f 19/11/3 15/7/3 18/20/3 20/19/3
+
diff --git a/mods/morelights/morelights_vintage/models/morelights_vintage_hangingbulb.obj b/mods/morelights/morelights_vintage/models/morelights_vintage_hangingbulb.obj
new file mode 100644
index 00000000..f3651be4
--- /dev/null
+++ b/mods/morelights/morelights_vintage/models/morelights_vintage_hangingbulb.obj
@@ -0,0 +1,192 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T20:35:12Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [38]
+
+v -0.03125 0.34375 -0.03125
+v -0.03125 0.09375 -0.03125
+v 0 0.09375 0
+v 0 0.34375 0
+v -0.03125 0.09375 0.03125
+v -0.03125 0.34375 0.03125
+v 0.03125 0.09375 0.03125
+v 0.03125 0.34375 0.03125
+v 0.03125 0.34375 -0.03125
+v 0.03125 0.09375 -0.03125
+v 0.0625 0.03125 0.09375
+v 0.0625 0.3125 0.09375
+v -0.0625 0.3125 0.09375
+v -0.0625 0.03125 0.09375
+v 0.09375 0.03125 -0.0625
+v 0.09375 0.3125 -0.0625
+v 0.09375 0.3125 0.0625
+v 0.09375 0.03125 0.0625
+v -0.09375 0.03125 0.0625
+v -0.09375 0.3125 0.0625
+v -0.09375 0.3125 -0.0625
+v -0.09375 0.03125 -0.0625
+v -0.0625 0.03125 -0.09375
+v -0.0625 0.3125 -0.09375
+v 0.0625 0.3125 -0.09375
+v 0.0625 0.03125 -0.09375
+v -0.0625 0 0.0625
+v -0.0625 0.34375 0.0625
+v -0.0625 0 -0.0625
+v -0.0625 0.34375 -0.0625
+v 0.0625 0 0.0625
+v 0.0625 0.34375 0.0625
+v 0.0625 0 -0.0625
+v 0.0625 0.34375 -0.0625
+v -0.0625 0.5 0.0625
+v -0.0625 0.5 -0.0625
+v 0.0625 0.5 -0.0625
+v 0.0625 0.5 0.0625
+
+# normals [26]
+
+vn -0.7071 0 0.7071
+vn -0.7071 0 -0.7071
+vn 0 0 1
+vn 1 0 0
+vn -1 0 0
+vn 0 0 -1
+vn -0.5774 -0.5774 0.5774
+vn -0.5774 0.5774 0.5774
+vn -0.5774 -0.5774 -0.5774
+vn -0.5774 0.5774 -0.5774
+vn 0.5774 -0.5774 0.5774
+vn 0.5774 0.5774 0.5774
+vn 0.5774 -0.5774 -0.5774
+vn 0.5774 0.5774 -0.5774
+vn -0.7071 -0.7071 0
+vn -0.7071 0.7071 0
+vn 0 -0.7071 -0.7071
+vn 0 0.7071 -0.7071
+vn 0.7071 0 -0.7071
+vn 0.7071 -0.7071 0
+vn 0.7071 0.7071 0
+vn 0.7071 0 0.7071
+vn 0 -0.7071 0.7071
+vn 0 0.7071 0.7071
+vn 0 -1 0
+vn 0 1 0
+
+# uvs [65]
+
+vt 0 1
+vt 0 0.75
+vt 0.03125 0.75
+vt 0.03125 1
+vt 0.0625 0.75
+vt 0.0625 1
+vt 0.4375 0.65625
+vt 0.4375 0.9375
+vt 0.3125 0.9375
+vt 0.3125 0.65625
+vt 0.625 0.65625
+vt 0.625 0.9375
+vt 0.5 0.9375
+vt 0.5 0.65625
+vt 1 0.65625
+vt 1 0.9375
+vt 0.875 0.9375
+vt 0.875 0.65625
+vt 0.8125 0.65625
+vt 0.8125 0.9375
+vt 0.6875 0.9375
+vt 0.6875 0.65625
+vt 0.28125 0.59375
+vt 0.25 0.65625
+vt 0.28125 1
+vt 0.25 0.9375
+vt 0.84375 0.59375
+vt 0.84375 1
+vt 0.46875 0.59375
+vt 0.46875 1
+vt 0.65625 0.59375
+vt 0.65625 1
+vt 0.875 0.59375
+vt 1 0.59375
+vt 1 1
+vt 0.875 1
+vt 0.6875 0.59375
+vt 0.8125 0.59375
+vt 0.8125 1
+vt 0.6875 1
+vt 0.5 0.59375
+vt 0.625 0.59375
+vt 0.625 1
+vt 0.5 1
+vt 0.3125 0.59375
+vt 0.4375 0.59375
+vt 0.4375 1
+vt 0.3125 1
+vt 0.25 1
+vt 0.125 1
+vt 0.125 0.875
+vt 0.25 0.875
+vt 0.5 0
+vt 0.5 0.15625
+vt 0.375 0.15625
+vt 0.375 0
+vt 0.25 0.15625
+vt 0.25 0
+vt 0.125 0.15625
+vt 0.125 0
+vt 0 0.15625
+vt 0 0
+vt 0.125 0.28125
+vt 0 0.28125
+vt 0.25 0.28125
+
+# objects [3]
+
+g Light
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 5/5/2 6/6/2 4/4/2 3/3/2
+f 4/4/1 3/3/1 7/2/1 8/1/1
+f 3/3/2 4/4/2 9/6/2 10/5/2
+
+g Bulb
+
+s 1
+f 11/7/3 12/8/3 13/9/3 14/10/3
+f 15/11/4 16/12/4 17/13/4 18/14/4
+f 19/15/5 20/16/5 21/17/5 22/18/5
+f 23/19/6 24/20/6 25/21/6 26/22/6
+f 27/23/7 14/10/7 19/24/7
+f 13/9/8 28/25/8 20/26/8
+f 29/27/9 22/18/9 23/19/9
+f 30/28/10 24/20/10 21/17/10
+f 31/29/11 18/14/11 11/7/11
+f 32/30/12 12/8/12 17/13/12
+f 33/31/13 26/22/13 15/11/13
+f 34/32/14 16/12/14 25/21/14
+f 29/33/15 27/34/15 19/15/15 22/18/15
+f 14/10/1 13/9/1 20/26/1 19/24/1
+f 28/35/16 30/36/16 21/17/16 20/16/16
+f 24/20/2 23/19/2 22/18/2 21/17/2
+f 33/37/17 29/38/17 23/19/17 26/22/17
+f 30/39/18 34/40/18 25/21/18 24/20/18
+f 16/12/19 15/11/19 26/22/19 25/21/19
+f 31/41/20 33/42/20 15/11/20 18/14/20
+f 34/43/21 32/44/21 17/13/21 16/12/21
+f 12/8/22 11/7/22 18/14/22 17/13/22
+f 27/45/23 31/46/23 11/7/23 14/10/23
+f 32/47/24 28/48/24 13/9/24 12/8/24
+f 29/49/25 33/50/25 31/51/25 27/52/25
+
+g Base
+
+s 1
+f 28/53/5 35/54/5 36/55/5 30/56/5
+f 30/56/6 36/55/6 37/57/6 34/58/6
+f 34/58/4 37/57/4 38/59/4 32/60/4
+f 32/60/3 38/59/3 35/61/3 28/62/3
+f 30/59/25 34/63/25 32/64/25 28/61/25
+f 37/65/26 36/63/26 35/59/26 38/57/26
+
diff --git a/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_c.obj b/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_c.obj
new file mode 100644
index 00000000..1be64478
--- /dev/null
+++ b/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_c.obj
@@ -0,0 +1,101 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T19:35:50Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [24]
+
+v -0.125 0 0.125
+v -0.125 0.375 0.125
+v -0.125 0.375 -0.125
+v -0.125 0 -0.125
+v 0.125 0.375 -0.125
+v 0.125 0 -0.125
+v 0.125 0.375 0.125
+v 0.125 0 0.125
+v -0.15625 0.375 0.15625
+v -0.15625 0.4375 0.15625
+v -0.15625 0.4375 -0.15625
+v -0.15625 0.375 -0.15625
+v 0.15625 0.4375 -0.15625
+v 0.15625 0.375 -0.15625
+v 0.15625 0.4375 0.15625
+v 0.15625 0.375 0.15625
+v -0.0625 0.4375 0.0625
+v -0.0625 0.5 0.0625
+v -0.0625 0.5 -0.0625
+v -0.0625 0.4375 -0.0625
+v 0.0625 0.5 -0.0625
+v 0.0625 0.4375 -0.0625
+v 0.0625 0.5 0.0625
+v 0.0625 0.4375 0.0625
+
+# normals [6]
+
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 0 1
+vn 0 -1 0
+vn 0 1 0
+
+# uvs [31]
+
+vt 0.75 0.5
+vt 0.75 0.875
+vt 0.5 0.875
+vt 0.5 0.5
+vt 0.25 0.875
+vt 0.25 0.5
+vt 0 0.875
+vt 0 0.5
+vt 1 0.5
+vt 1 0.875
+vt 0.75 0.25
+vt 1 0.25
+vt 0.5 0.8125
+vt 0.8125 0.8125
+vt 0.8125 0.875
+vt 0.1875 0.875
+vt 0.1875 0.8125
+vt 0.1875 0.5
+vt 1 0.375
+vt 1 0.4375
+vt 0.875 0.4375
+vt 0.875 0.375
+vt 0.75 0.4375
+vt 0.75 0.375
+vt 0.625 0.4375
+vt 0.625 0.375
+vt 0.5 0.4375
+vt 0.5 0.375
+vt 0.625 0.625
+vt 0.5 0.625
+vt 0.625 0.5
+
+# objects [2]
+
+g LanternGlobe
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 4/4/2 3/3/2 5/5/2 6/6/2
+f 6/6/3 5/5/3 7/7/3 8/8/3
+f 8/9/4 7/10/4 2/2/4 1/1/4
+f 4/11/5 6/12/5 8/9/5 1/1/5
+
+g LanternMetal
+
+s 1
+f 9/3/1 10/13/1 11/14/1 12/15/1
+f 12/14/2 11/15/2 13/3/2 14/13/2
+f 14/13/3 13/3/3 15/16/3 16/17/3
+f 16/16/4 15/17/4 10/13/4 9/3/4
+f 12/13/5 14/17/5 16/18/5 9/4/5
+f 13/4/6 11/13/6 10/17/6 15/18/6
+f 17/19/1 18/20/1 19/21/1 20/22/1
+f 20/22/2 19/21/2 21/23/2 22/24/2
+f 22/24/3 21/23/3 23/25/3 24/26/3
+f 24/26/4 23/25/4 18/27/4 17/28/4
+f 21/29/6 19/30/6 18/4/6 23/31/6
+
diff --git a/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_f.obj b/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_f.obj
new file mode 100644
index 00000000..9dadd567
--- /dev/null
+++ b/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_f.obj
@@ -0,0 +1,118 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T19:37:30Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [32]
+
+v -0.125 -0.4375 0.125
+v -0.125 -0.0625 0.125
+v -0.125 -0.0625 -0.125
+v -0.125 -0.4375 -0.125
+v 0.125 -0.0625 -0.125
+v 0.125 -0.4375 -0.125
+v 0.125 -0.0625 0.125
+v 0.125 -0.4375 0.125
+v -0.15625 -0.5 0.15625
+v -0.15625 -0.4375 0.15625
+v -0.15625 -0.4375 -0.15625
+v -0.15625 -0.5 -0.15625
+v 0.15625 -0.4375 -0.15625
+v 0.15625 -0.5 -0.15625
+v 0.15625 -0.4375 0.15625
+v 0.15625 -0.5 0.15625
+v -0.15625 -0.0625 0.15625
+v -0.15625 0 0.15625
+v -0.15625 0 -0.15625
+v -0.15625 -0.0625 -0.15625
+v 0.15625 0 -0.15625
+v 0.15625 -0.0625 -0.15625
+v 0.15625 0 0.15625
+v 0.15625 -0.0625 0.15625
+v -0.0625 0 0.0625
+v -0.0625 0.0625 0.0625
+v -0.0625 0.0625 -0.0625
+v -0.0625 0 -0.0625
+v 0.0625 0.0625 -0.0625
+v 0.0625 0 -0.0625
+v 0.0625 0.0625 0.0625
+v 0.0625 0 0.0625
+
+# normals [6]
+
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 0 1
+vn 0 -1 0
+vn 0 1 0
+
+# uvs [35]
+
+vt 0.75 0.5
+vt 0.75 0.875
+vt 0.5 0.875
+vt 0.5 0.5
+vt 0.25 0.875
+vt 0.25 0.5
+vt 0 0.875
+vt 0 0.5
+vt 1 0.5
+vt 1 0.875
+vt 0.5 0.1875
+vt 0.5 0.125
+vt 0.8125 0.125
+vt 0.8125 0.1875
+vt 0.1875 0.1875
+vt 0.1875 0.125
+vt 0.1875 0.5
+vt 0.5 0.8125
+vt 0.8125 0.8125
+vt 0.8125 0.875
+vt 0.1875 0.875
+vt 0.1875 0.8125
+vt 1 0.375
+vt 1 0.4375
+vt 0.875 0.4375
+vt 0.875 0.375
+vt 0.75 0.4375
+vt 0.75 0.375
+vt 0.625 0.4375
+vt 0.625 0.375
+vt 0.5 0.4375
+vt 0.5 0.375
+vt 0.625 0.625
+vt 0.5 0.625
+vt 0.625 0.5
+
+# objects [2]
+
+g LanternGlobe
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 4/4/2 3/3/2 5/5/2 6/6/2
+f 6/6/3 5/5/3 7/7/3 8/8/3
+f 8/9/4 7/10/4 2/2/4 1/1/4
+
+g LanternMetal
+
+s 1
+f 9/11/1 10/12/1 11/13/1 12/14/1
+f 12/13/2 11/14/2 13/11/2 14/12/2
+f 14/12/3 13/11/3 15/15/3 16/16/3
+f 16/15/4 15/16/4 10/12/4 9/11/4
+f 12/4/5 14/17/5 16/15/5 9/11/5
+f 13/11/6 11/4/6 10/17/6 15/15/6
+f 17/3/1 18/18/1 19/19/1 20/20/1
+f 20/19/2 19/20/2 21/3/2 22/18/2
+f 22/18/3 21/3/3 23/21/3 24/22/3
+f 24/21/4 23/22/4 18/18/4 17/3/4
+f 20/18/5 22/22/5 24/17/5 17/4/5
+f 21/4/6 19/18/6 18/22/6 23/17/6
+f 25/23/1 26/24/1 27/25/1 28/26/1
+f 28/26/2 27/25/2 29/27/2 30/28/2
+f 30/28/3 29/27/3 31/29/3 32/30/3
+f 32/30/4 31/29/4 26/31/4 25/32/4
+f 29/33/6 27/34/6 26/4/6 31/35/6
+
diff --git a/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_w.obj b/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_w.obj
new file mode 100644
index 00000000..5195b98b
--- /dev/null
+++ b/mods/morelights/morelights_vintage/models/morelights_vintage_lantern_w.obj
@@ -0,0 +1,136 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T19:39:42Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [36]
+
+v -0.125 -0.1875 -0.3125
+v -0.125 -0.1875 0.0625
+v -0.125 0.0625 0.0625
+v -0.125 0.0625 -0.3125
+v 0.125 0.0625 0.0625
+v 0.125 0.0625 -0.3125
+v 0.125 -0.1875 0.0625
+v 0.125 -0.1875 -0.3125
+v 0 -0.4375 0.3125
+v 0 -0.4375 0.0625
+v 0 0.0625 0.0625
+v 0 0.0625 0.3125
+v -0.15625 -0.21875 0.0625
+v -0.15625 -0.21875 0.125
+v -0.15625 0.09375 0.125
+v -0.15625 0.09375 0.0625
+v 0.15625 0.09375 0.125
+v 0.15625 0.09375 0.0625
+v 0.15625 -0.21875 0.125
+v 0.15625 -0.21875 0.0625
+v -0.0625 -0.125 0.125
+v -0.0625 -0.125 0.1875
+v -0.0625 0 0.1875
+v -0.0625 0 0.125
+v 0.0625 0 0.1875
+v 0.0625 0 0.125
+v 0.0625 -0.125 0.1875
+v 0.0625 -0.125 0.125
+v 0.125 -0.5 -0.125
+v 0.125 -0.5 0.375
+v -0.125 -0.5 0.375
+v -0.125 -0.5 -0.125
+v -0.125 -0.4375 0.375
+v -0.125 -0.4375 -0.125
+v 0.125 -0.4375 0.375
+v 0.125 -0.4375 -0.125
+
+# normals [6]
+
+vn -1 0 0
+vn 0 1 0
+vn 1 0 0
+vn 0 -1 0
+vn 0 0 -1
+vn 0 0 1
+
+# uvs [47]
+
+vt 0.75 0.5
+vt 0.75 0.875
+vt 0.5 0.875
+vt 0.5 0.5
+vt 0.25 0.875
+vt 0.25 0.5
+vt 0 0.875
+vt 0 0.5
+vt 1 0.5
+vt 1 0.875
+vt 0 0.25
+vt 0.5 0.25
+vt 0.75 0.25
+vt 1 0.25
+vt 0.5 0.8125
+vt 0.8125 0.8125
+vt 0.8125 0.875
+vt 0.1875 0.875
+vt 0.1875 0.8125
+vt 0.1875 0.5
+vt 1 0.375
+vt 1 0.4375
+vt 0.875 0.4375
+vt 0.875 0.375
+vt 0.75 0.4375
+vt 0.75 0.375
+vt 0.625 0.4375
+vt 0.625 0.375
+vt 0.5 0.4375
+vt 0.5 0.375
+vt 0.625 0.625
+vt 0.5 0.625
+vt 0.625 0.5
+vt 0.9375 0.25
+vt 0.9375 0.75
+vt 0.6875 0.75
+vt 0.6875 0.25
+vt 0.625 0.75
+vt 0.625 0.25
+vt 0.375 0.75
+vt 0.375 0.25
+vt 0.3125 0.75
+vt 0.3125 0.25
+vt 0.6875 0.1875
+vt 0.9375 0.1875
+vt 0.6875 0.8125
+vt 0.9375 0.8125
+
+# objects [2]
+
+g Lantern
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 4/4/2 3/3/2 5/5/2 6/6/2
+f 6/6/3 5/5/3 7/7/3 8/8/3
+f 8/9/4 7/10/4 2/2/4 1/1/4
+f 9/8/3 10/11/3 11/12/3 12/4/3
+f 4/13/5 6/14/5 8/9/5 1/1/5
+
+g LanternMetal
+
+s 1
+f 13/3/1 14/15/1 15/16/1 16/17/1
+f 16/16/2 15/17/2 17/3/2 18/15/2
+f 18/15/3 17/3/3 19/18/3 20/19/3
+f 20/18/4 19/19/4 14/15/4 13/3/4
+f 16/15/5 18/19/5 20/20/5 13/4/5
+f 17/4/6 15/15/6 14/19/6 19/20/6
+f 21/21/1 22/22/1 23/23/1 24/24/1
+f 24/24/2 23/23/2 25/25/2 26/26/2
+f 26/26/3 25/25/3 27/27/3 28/28/3
+f 28/28/4 27/27/4 22/29/4 21/30/4
+f 25/31/6 23/32/6 22/4/6 27/33/6
+f 29/34/4 30/35/4 31/36/4 32/37/4
+f 32/37/1 31/36/1 33/38/1 34/39/1
+f 34/39/2 33/38/2 35/40/2 36/41/2
+f 36/41/3 35/40/3 30/42/3 29/43/3
+f 32/37/5 34/44/5 36/45/5 29/34/5
+f 33/46/6 31/36/6 30/35/6 35/47/6
+
diff --git a/mods/morelights/morelights_vintage/models/morelights_vintage_oillamp.obj b/mods/morelights/morelights_vintage/models/morelights_vintage_oillamp.obj
new file mode 100644
index 00000000..c5a32370
--- /dev/null
+++ b/mods/morelights/morelights_vintage/models/morelights_vintage_oillamp.obj
@@ -0,0 +1,156 @@
+# Processed with obj-simplify v1.1 (a2f5cd9) | 2021-08-06T20:28:11Z | https://github.com/jonnenauha/obj-simplify
+
+# Blender v2.93.0 OBJ File: ''
+# www.blender.org
+
+# vertices [40]
+
+v -0.03125 -0.46875 -0.03125
+v 0.03125 -0.46875 0.03125
+v 0.03125 0.03125 0.03125
+v -0.03125 0.03125 -0.03125
+v 0.03125 -0.46875 -0.03125
+v -0.03125 -0.46875 0.03125
+v -0.03125 0.03125 0.03125
+v 0.03125 0.03125 -0.03125
+v -0.125 -0.499 0.125
+v -0.125 -0.34375 0.125
+v -0.125 -0.34375 -0.125
+v -0.125 -0.499 -0.125
+v 0.125 -0.34375 -0.125
+v 0.125 -0.499 -0.125
+v 0.125 -0.34375 0.125
+v 0.125 -0.499 0.125
+v -0.125 -0.21875 0.125
+v -0.125 0.25 0.125
+v -0.125 0.25 -0.125
+v -0.125 -0.21875 -0.125
+v 0.125 0.25 -0.125
+v 0.125 -0.21875 -0.125
+v 0.125 0.25 0.125
+v 0.125 -0.21875 0.125
+v 0.0625 -0.3125 -0.0625
+v 0.0625 -0.25 -0.0625
+v 0.0625 -0.25 0.0625
+v 0.0625 -0.3125 0.0625
+v -0.0625 -0.25 0.0625
+v -0.0625 -0.3125 0.0625
+v -0.0625 -0.25 -0.0625
+v -0.0625 -0.3125 -0.0625
+v -0.125 -0.3125 0.125
+v -0.125 -0.3125 -0.125
+v 0.125 -0.3125 -0.125
+v 0.125 -0.3125 0.125
+v -0.125 -0.25 0.125
+v -0.125 -0.25 -0.125
+v 0.125 -0.25 -0.125
+v 0.125 -0.25 0.125
+
+# normals [8]
+
+vn -0.7071 0 0.7071
+vn -0.7071 0 -0.7071
+vn -1 0 0
+vn 0 0 -1
+vn 1 0 0
+vn 0 0 1
+vn 0 -1 0
+vn 0 1 0
+
+# uvs [54]
+
+vt 0 0
+vt 1 0
+vt 1 1
+vt 0 1
+vt 0.75 0
+vt 0.75 0.15625
+vt 0.5 0.15625
+vt 0.5 0
+vt 0.25 0.15625
+vt 0.25 0
+vt 0 0.15625
+vt 1 0.15625
+vt 0.25 0.40625
+vt 0 0.40625
+vt 0.75 0.40625
+vt 0.75 0.875
+vt 0.5 0.875
+vt 0.5 0.40625
+vt 0.25 0.875
+vt 0 0.875
+vt 1 0.40625
+vt 1 0.875
+vt 0.625 0.53125
+vt 0.625 0.46875
+vt 0.75 0.46875
+vt 0.75 0.53125
+vt 0.25 0.53125
+vt 0.25 0.46875
+vt 0.375 0.46875
+vt 0.375 0.53125
+vt 0.5 0.46875
+vt 0.5 0.53125
+vt 0.25 0.78125
+vt 0.25 0.75
+vt 0.5 0.75
+vt 0.5 0.78125
+vt 0.75 0.75
+vt 0.75 0.78125
+vt 1 0.75
+vt 1 0.78125
+vt 0 0.78125
+vt 0 0.75
+vt 0 0.5
+vt 0.25 0.5
+vt 0.25 0.25
+vt 0.25 0.21875
+vt 0.5 0.21875
+vt 0.5 0.25
+vt 0.75 0.21875
+vt 0.75 0.25
+vt 1 0.21875
+vt 1 0.25
+vt 0 0.25
+vt 0 0.21875
+
+# objects [3]
+
+g Wick
+
+s 1
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 5/1/2 6/2/2 7/3/2 8/4/2
+
+g Globe
+
+s 1
+f 9/5/3 10/6/3 11/7/3 12/8/3
+f 12/8/4 11/7/4 13/9/4 14/10/4
+f 14/10/5 13/9/5 15/11/5 16/1/5
+f 16/2/6 15/12/6 10/6/6 9/5/6
+f 12/13/7 14/14/7 16/11/7 9/9/7
+f 17/15/3 18/16/3 19/17/3 20/18/3
+f 20/18/4 19/17/4 21/19/4 22/13/4
+f 22/13/5 21/19/5 23/20/5 24/14/5
+f 24/21/6 23/22/6 18/16/6 17/15/6
+f 18/18/8 23/13/8 21/9/8 19/7/8
+
+g Metal
+
+s 1
+f 25/23/5 26/24/5 27/25/5 28/26/5
+f 28/27/6 27/28/6 29/29/6 30/30/6
+f 30/30/3 29/29/3 31/31/3 32/32/3
+f 26/24/4 25/23/4 32/32/4 31/31/4
+f 10/33/3 33/34/3 34/35/3 11/36/3
+f 11/36/4 34/35/4 35/37/4 13/38/4
+f 13/38/5 35/37/5 36/39/5 15/40/5
+f 15/41/6 36/42/6 33/34/6 10/33/6
+f 33/34/8 36/42/8 35/43/8 34/44/8
+f 37/45/3 17/46/3 20/47/3 38/48/3
+f 38/48/4 20/47/4 22/49/4 39/50/4
+f 39/50/5 22/49/5 24/51/5 40/52/5
+f 40/53/6 24/54/6 17/46/6 37/45/6
+f 38/44/7 39/43/7 40/53/7 37/45/7
+
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_block_frame.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_block_frame.png
new file mode 100644
index 00000000..12bd8325
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_block_frame.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_block_frame.xcf b/mods/morelights/morelights_vintage/textures/morelights_vintage_block_frame.xcf
new file mode 100644
index 00000000..b2c66fa5
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_block_frame.xcf differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_block_glass.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_block_glass.png
new file mode 100644
index 00000000..bab82f6c
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_block_glass.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_brass.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_brass.png
new file mode 100644
index 00000000..ff20bb86
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_brass.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_brass_32.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_brass_32.png
new file mode 100644
index 00000000..a6df9f4c
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_brass_32.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_chain_b_inv.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_chain_b_inv.png
new file mode 100644
index 00000000..c6f96792
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_chain_b_inv.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_chandelier.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_chandelier.png
new file mode 100644
index 00000000..c1e5816a
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_chandelier.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_hangingbulb.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_hangingbulb.png
new file mode 100644
index 00000000..90610ab7
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_hangingbulb.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_hangingbulb_inv.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_hangingbulb_inv.png
new file mode 100644
index 00000000..1c7c628d
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_hangingbulb_inv.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_lantern_frame.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_lantern_frame.png
new file mode 100644
index 00000000..5c876871
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_lantern_frame.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_lantern_glass.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_lantern_glass.png
new file mode 100644
index 00000000..204f309d
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_lantern_glass.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_oil_flame.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_oil_flame.png
new file mode 100644
index 00000000..cfab23d0
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_oil_flame.png differ
diff --git a/mods/morelights/morelights_vintage/textures/morelights_vintage_oillamp.png b/mods/morelights/morelights_vintage/textures/morelights_vintage_oillamp.png
new file mode 100644
index 00000000..a2d7ab8d
Binary files /dev/null and b/mods/morelights/morelights_vintage/textures/morelights_vintage_oillamp.png differ
diff --git a/mods/morelights/screenshot.png b/mods/morelights/screenshot.png
new file mode 100644
index 00000000..972d45b9
Binary files /dev/null and b/mods/morelights/screenshot.png differ
diff --git a/mods/potted_farming/LICENSE b/mods/potted_farming/LICENSE
new file mode 100644
index 00000000..8000a6fa
--- /dev/null
+++ b/mods/potted_farming/LICENSE
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random
+ Hacker.
+
+ , 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/mods/potted_farming/README.md b/mods/potted_farming/README.md
new file mode 100644
index 00000000..5e458f77
--- /dev/null
+++ b/mods/potted_farming/README.md
@@ -0,0 +1,81 @@
+Hello there! Thank you for checking out my mod!
+
+# Potted Farming
+
+This is a new farming mechanic for MineTest, not on soil, but on nice pots, even inside your house or castle!
+I inteded this mod also to provide some nice plants to decorate your rooms.
+Have you ever wanted to grow plants in your nice house, and maybe get some nice fruit in the meantime?
+Well now you can! Make sure you give it enough light and water to sustain the little tree, and get your fresh apples now!
+
+## Things that you will need
+
+#### Pot with dirt
+
+The essential item, the whole point of the mod. You will need bricks and dirt, and some dye to make it somewhat stylish.
+
+#### Watering Can
+
+Some plants require your care, with you having to craft a watering can to give them water from time to time.
+After all, you are planting in a pot, not in the soil near water, so you are its only source of hydration.
+It can hold very little water, afterall the plants do not require much, therefor filling the watering can doesn't clear a whole water node. Speaking of which, you can fill it also with homedecor items like the sink and well.
+
+## Plants and Herbs
+
+You can find some new plants in your world, wild herbs in different biomes, break them to obtain a *stem* from them.
+Planting it in a pot starts your journey as a pot farmer!
+If there is enough light, it will grow and after a while you can harvest some leaves, without removing the whole plant!
+If you don't harvest it, it will become thristy and you'll have to use the watering can. You will not loose the plant.
+
+#### Plants currently present:
+
+- Basil
+- Rosemary
+- Sage
+- Parsley
+- Mint
+- Oregano
+
+Rosemary could be preexisting from [cucina_vegana](https://content.minetest.net/packages/Clyde/cucina_vegana/),
+while Parsley and Mint from [farming](https://content.minetest.net/packages/TenPlus1/farming/).
+If you change the settings you can have the potted plant give you that mod item instead of a new one defined by this mod.
+
+## Mushrooms
+
+Are you tired of building a dark room to grow mushrooms and wait for them ages to grow only to harvest few of them?
+Well ... you still need the same dark room (mushrooms require darkness after all!) but at least you can harvest more than one
+ and they will regrow from the same pot!
+
+#### Mushrooms currently present:
+
+- Brown Mushroom from [default]
+- Chanterelle from [herbs](https://content.minetest.net/packages/Clyde/herbs/)
+- Boletus also from [herbs]
+
+## Fruit trees
+
+Yeah you read that right. I present you a way to grow a potted little tree that can regrow fruits!
+Plant a regular sapling and wait, it will eventually grow into a 2 node tall tree.
+The leaves after some time will either grow 2 to 3 fruits that you can harvest, without hurting the tree (again no
+need to replant over and over) or they will be tinted in yellow, meaning the tree is now thristy, in that case
+you need to pour water into the pot.
+
+#### Fruit trees currently present:
+
+- Apple from [default]
+- Orange from [ethereal](https://content.minetest.net/packages/TenPlus1/ethereal/)
+- Lemon from [ethereal]
+- Banana from [ethereal]
+- Cherry from [cool_trees modpack](https://content.minetest.net/packages/runs/cool_trees/)
+- Plum from [cool_trees]
+- Pomegranate from [cool_trees]
+
+Furthermore, the settings allow some trees to be changed, for example the Apple small tree could use the [moretrees](https://content.minetest.net/packages/mt-mods/moretrees/) texture instead, and the Orange and Lemon small tree could
+instead use the [cool_trees] sapling and textures.
+
+### License
+
+Code and Textures by Annalysa.
+Sounds have their own license file in the sounds folder.
+A special thanks to *clyde*, that helped me a lot with the code.
+
+##### Italian, Dutch, French, Spanish traslations available
diff --git a/mods/potted_farming/fruit_trees.png b/mods/potted_farming/fruit_trees.png
new file mode 100644
index 00000000..0b4291f7
Binary files /dev/null and b/mods/potted_farming/fruit_trees.png differ
diff --git a/mods/potted_farming/init.lua b/mods/potted_farming/init.lua
new file mode 100644
index 00000000..4e721717
--- /dev/null
+++ b/mods/potted_farming/init.lua
@@ -0,0 +1,187 @@
+potted_farming = {}
+
+local pf = potted_farming
+pf.version = 4.2
+pf.modname = minetest.get_current_modname()
+pf.path = minetest.get_modpath(pf.modname)
+pf.plant_settings = {}
+pf.watering_can_max_uses = 5
+
+local S
+
+if(minetest.get_translator) then
+ S = minetest.get_translator(pf.modname)
+
+else
+ S = function ( s ) return s end
+
+end
+
+pf.S = S
+
+dofile(pf.path .. "/settings.lua")
+dofile(pf.path .. "/lib.lua")
+dofile(pf.path .. "/nodes.lua")
+dofile(pf.path .. "/items.lua")
+dofile(pf.path .. "/tools.lua")
+dofile(pf.path .. "/recipes.lua")
+
+pf.plant_list = {
+-- name, wild parameters: place_on (found on)
+ ["basil"] = { { "default:dirt_with_grass",
+ "default:dirt",
+ "ethereal:praire_dirt",
+ "ethereal:grove_dirt",
+ },},
+ ["rosemary"] = { { "default:dirt_with_coniferous_litter",
+ "default:dirt_with_snow",
+ "ethereal:gray_dirt",
+ "ethereal:cold_dirt",
+ },},
+ ["sage"] = { { "default:dirt_with_grass",
+ "default:permafrost_with_moss",
+ "default:permafrost_with_stones",
+ "ethereal:cold_dirt",
+ },},
+ ["parsley"] = { { "default:dirt_with_grass",
+ "default:dirt_with_dry_grass",
+ "default:dry_dirt_with_dry_grass",
+ "ethereal:praire_dirt",
+ },},
+ ["mint"] = { { "default:dirt_with_grass",
+ "default:dirt_with_snow",
+ "ethereal:praire_dirt",
+ "ethereal:jungle_dirt",
+ },},
+ ["oregano"] = { { "default:dirt_with_grass",
+ "default:dirt_with_dry_grass",
+ "ethereal:praire_dirt",
+ "ethereal:jungle_dirt",
+ },},
+}
+
+for k, v in pairs(pf.plant_list) do
+ local ps = pf.plant_settings[k]
+ if ps ~= nil and ps.definable then
+ pf.register_plant(k)
+ pf.register_wild_variant(k, v[1], ps.scale, ps.min_hight, ps.max_hight )
+ end
+end
+
+pf.mushroom_list = {
+ --name full_mushroom_name
+ ["brown"] = {"flowers:mushroom_brown",},
+ ["cantharellus"] = {"herbs:mushroom_cantharellus",},
+ ["boletus"] = {"herbs:mushroom_boletus",},
+}
+
+for k, v in pairs(pf.mushroom_list) do
+ local ps = pf.plant_settings[k]
+ if ps ~= nil and ps.definable and minetest.registered_items[v[1]] then
+ pf.register_mushroom(k, v[1])
+ end
+end
+
+local shrub = pf.modname ..":pot_with_shrub"
+local b_shrub = pf.modname ..":pot_with_plantain"
+
+pf.fruit_tree_list = {
+ --name sapling_name, fruit_name, leaves_name,
+ ["lemon"] = {"ethereal:lemon_tree_sapling",
+ "ethereal:lemon",
+ "ethereal_lemon_leaves.png",
+ shrub,
+ },
+ ["orange"] = {"ethereal:orange_tree_sapling",
+ "ethereal:orange",
+ "ethereal_orange_leaves.png",
+ shrub,
+ },
+ ["apple"] = {"default:sapling",
+ "default:apple",
+ "default_leaves.png",
+ shrub,
+ },
+ ["cherry"] = {"cherrytree:sapling",
+ "cherrytree:cherries",
+ "cherrytree_leaves.png",
+ shrub,
+ },
+ ["plum"] = {"plumtree:sapling",
+ "plumtree:plum",
+ "plumtree_leaves.png",
+ shrub,
+ },
+ ["pomegranate"] = {"pomegranate:sapling",
+ "pomegranate:pomegranate",
+ "pomegranate_leaves.png",
+ shrub,
+ },
+ ["banana"] = {"ethereal:banana_tree_sapling",
+ "ethereal:banana",
+ "ethereal_banana_leaf.png",
+ b_shrub,
+ },
+}
+
+local fruit_tree_alternatives = {
+ --name sapling_name, fruit_name, leaves_name,
+ ["lemon"] = { "lemontree",
+ { "lemontree:sapling",
+ "lemontree:lemon",
+ "lemontree_leaves.png",
+ shrub,
+ },
+ },
+ ["orange"] = { "clementinetree",
+ { "clementinetree:sapling",
+ "clementinetree:clementine",
+ "clementinetree_leaves.png",
+ shrub,
+ },
+ },
+ ["apple"] = { "moretrees",
+ { "moretrees:apple_tree_sapling",
+ "default:apple",
+ "moretrees_apple_tree_leaves.png^nature_blossom.png",
+ shrub,
+ },
+ },
+ ["cherry"] = { "cherrytree",
+ { "cherrytree:sapling",
+ "cherrytree:cherries",
+ "cherrytree_blossom_leaves.png",
+ shrub,
+ },
+ },
+}
+
+-- can items be swapped? need to check and modify fruit_tree_list before register
+for k,v in pairs(pf.fruit_tree_list) do
+ local modname = v[1]:split(":")[1]
+
+ if minetest.get_modpath(modname) then
+
+ local s = pf.plant_settings.support
+ if s[k] ~= nil and s[k].can_swap == true then
+
+ local f = fruit_tree_alternatives[k]
+ if minetest.get_modpath(f[1]) then
+ pf.fruit_tree_list[k] = f[2]
+ end -- if the alternative mod is present
+
+ end -- if fruit is in the main list, and can swap
+
+ end-- if mod with fruit is present
+
+end -- for fruit_tree_list
+
+for k, v in pairs(pf.fruit_tree_list) do
+ local ps = pf.plant_settings[k]
+ -- i still need to check if the item exists, just in case.
+ if ps ~= nil and ps.definable == true and minetest.registered_items[v[2]] then
+ pf.register_fruit_tree(k, v[1], v[2], v[3], v[4])
+ end
+end
+
+minetest.log("ACTION", "[MOD] " .. pf.modname .. " successfully loaded.")
diff --git a/mods/potted_farming/items.lua b/mods/potted_farming/items.lua
new file mode 100644
index 00000000..4ec4a822
--- /dev/null
+++ b/mods/potted_farming/items.lua
@@ -0,0 +1,44 @@
+local pf = potted_farming
+local S = pf.S
+
+local h_k = minetest.get_modpath("homedecor_kitchen") ~= nil
+local h_b = minetest.get_modpath("homedecor_bathroom") ~= nil
+local h_e = minetest.get_modpath("homedecor_exterior") ~= nil
+
+local water_list = {
+ ["default:water_source"] = true,
+ ["default:water_flowing"] = true,
+ ["default:river_water_source"] = true,
+ ["default:river_water_flowing"] = true,
+ ["homedecor:kitchen_cabinet_colorable_with_sink"] = h_k,
+ ["homedecor:kitchen_cabinet_colorable_with_sink_locked"] = h_k,
+ ["homedecor:kitchen_faucet"] = h_b,
+ ["homedecor:sink"] = h_b,
+ ["homedecor:taps"] = h_b,
+ ["homedecor:taps_brass"] = h_b,
+ ["homedecor:well"] = h_e,
+}
+
+minetest.register_craftitem(pf.modname .. ":empty_watering_can", {
+ description = S("Empty Watering Can"),
+ inventory_image = pf.modname .. "_empty_watering_can.png",
+ groups = {watering_can = 1},
+ liquids_pointable = true,
+ stack_max = 1,
+ on_use = function(itemstack, player, pointed_thing)
+ local pos = minetest.get_pointed_thing_position(pointed_thing, above)
+ if not pos then return itemstack end
+ local name = minetest.get_node(pos).name
+ -- minetest.get_item_group(minetest.get_node(pos).name , "water") >= 1
+ if player:is_player() and pointed_thing.type == "node" and water_list[name] then
+ itemstack:replace(pf.modname .. ":watering_can") -- so that it isnt given on another
+ -- inv slot and can be
+ -- immediatelly used
+ local n = math.random(1, 2)
+ minetest.sound_play("water-splash-0".. n, {pos=pos, gain=1.2})
+ end
+
+ return itemstack
+
+ end,
+})
diff --git a/mods/potted_farming/lib.lua b/mods/potted_farming/lib.lua
new file mode 100644
index 00000000..2aed1edb
--- /dev/null
+++ b/mods/potted_farming/lib.lua
@@ -0,0 +1,956 @@
+local lib = potted_farming
+local S = lib.S
+local max_uses = lib.watering_can_max_uses
+local watering_can_item = lib.modname .. ":watering_can"
+
+local pot_def = {
+ groups = {flammable = 2, crumbly = 2, cracky = 3, attached_node = 1, not_in_creative_inventory = 1},
+ tiles = {
+ "pot_with_soil_top.png",
+ "pot_with_soil_bottom.png",
+ "pot_with_soil_side.png",
+ "pot_with_soil_side.png",
+ "pot_with_soil_side.png",
+ "pot_with_soil_side.png"
+
+ }, -- tiles
+ drawtype = "nodebox",
+ paramtype = "light",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.1875, 0.1875}, -- base_center
+ {-0.25, -0.375, -0.125, -0.1875, -0.1875, 0.125}, -- base1
+ {-0.125, -0.375, 0.1875, 0.125, -0.1875, 0.25}, -- base2
+ {0.1875, -0.375, -0.125, 0.25, -0.1875, 0.125}, -- base3
+ {-0.125, -0.375, -0.25, 0.125, -0.1875, -0.1875}, -- base4
+ {-0.5, -0.5, 0, 0.5, 0.5, 0}, -- plant1X
+ {0, -0.5, -0.5, 0, 0.5, 0.5}, -- plant2Z
+
+ } -- fixed
+
+ }, -- node_box
+ selection_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, -0.25, 0.25, -0.1875, 0.25}, -- selection
+
+ }
+
+ }, -- selection_box
+
+ collision_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, -0.25, 0.25, -0.1875, 0.25}, -- selection
+
+ }
+
+ }, -- collsion_box
+
+ on_rotate = function(pos, node)
+ return false
+ end,
+}
+
+
+--[[
+ **************************************************
+ ** **
+ ** is_acceptable_source **
+ ** **
+ **************************************************
+ need modname:itemname
+--]]
+
+function lib.is_acceptable_source (itemname)
+ local name = itemname:split(":")[2]
+ local plant = name:split("_")[1]
+
+ if plant == nil then plant = "" end
+
+ local s = lib.plant_settings
+ -- with these variables i can check if the potted plant had been actually registered
+ local pot = lib.modname .. ":pot_with_".. plant .."_1"
+ local chkpot = minetest.registered_nodes[pot]
+
+ -- if plant is present in table AND the item in hand is the stem of that plant
+ if lib.plant_list[plant] and string.find(name , "_stem") and s[plant].definable and chkpot then
+ return true, "plant", plant
+ end
+ for k,v in pairs(lib.mushroom_list) do
+ pot = lib.modname .. ":pot_with_".. k .."_1"
+ chkpot = minetest.registered_nodes[pot]
+ if v[1] == itemname and chkpot then
+ if s[k] ~= nil and s[k].definable then
+ return true, "mushroom", k
+ end
+ end
+ end
+ for k,v in pairs(lib.fruit_tree_list) do
+ pot = lib.modname .. ":pot_with_".. k .."_sapling"
+ chkpot = minetest.registered_nodes[pot]
+ if v[1] == itemname and chkpot then
+ if s[k] ~= nil and s[k].definable then
+ return true, "fruit_tree", k
+ end
+ end
+ end
+
+ return false, "no", nil
+
+end -- lib.is_acceptable_source
+
+
+--[[
+ **************************************************
+ ** **
+ ** check_light **
+ ** **
+ **************************************************
+--]]
+
+function lib.check_light(pos, min_light, max_light)
+ local checkpos = pos
+ if minetest.get_node(checkpos).name == "ignore" then
+ return false
+ end
+
+ local pot_light = minetest.get_node_light(checkpos)
+ --minetest.chat_send_all("pot light".. pot_light)
+ --"if you need to get the light value ontop of target node you need to get the light value of position above the target."
+ --this is a potted plant, can stay inside a house, not on soil, therefore there is no need to check the light from the node above.
+ local min = min_light
+ local max = max_light
+
+ if min < 1 then min = 1 end
+ if max > 14 then max = 14 end
+
+ if min > max then
+ local t = max
+ max = min
+ min = t
+ end
+
+ if min <= pot_light and pot_light <= max then
+ return true
+ end
+
+ return false
+
+end -- function lib.check_light
+
+
+--[[
+ **************************************************
+ ** **
+ ** register_plant_abm **
+ ** growing_potted_plant **
+ ** **
+ **************************************************
+--]]
+
+function lib.register_plant_abm(nodename, next_step_nodename, delay, percentage)
+ -- if the register_plant_abm function is called, then surely the plant is defined and registerable
+ -- mod_name:pot_with_PLANT_N
+ local plant_name = nodename:split(":")[2]:split("_")[3]
+ -- as per naming convention, [3] is the given plant_name
+ local potted_plant = lib.plant_settings[plant_name]
+ minetest.register_abm({
+ label = "growing_potted_".. plant_name .."_abm",
+ nodenames = {nodename},
+ --neighbors = {"default:air"}, --can be omitted
+ interval = delay,
+ chance = percentage,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local nodepos = pos
+ if(lib.check_light(nodepos, potted_plant.min_light, potted_plant.max_light ) ) then
+ minetest.swap_node(nodepos, {name = next_step_nodename})
+
+ end -- if(lib.check
+
+ end, -- action =
+
+ }) -- register_plant_abm
+
+end -- lib.register_plant_abm
+
+
+--[[
+ **************************************************
+ ** **
+ ** add_watering_can_wear **
+ ** **
+ **************************************************
+--]]
+
+function lib.add_watering_can_wear(itemstack)
+ if itemstack:get_name() == watering_can_item then
+ itemstack:add_wear(65535 / (max_uses))
+ local wear = itemstack:get_wear()
+ if wear >= 65535 then
+ itemstack:replace(lib.modname .. ":empty_watering_can")
+
+ end -- if wear
+ end -- if itemstack is watering can
+
+ return itemstack
+
+end -- add_watering_can_wear
+
+
+--[[
+ **************************************************
+ ** **
+ ** get_leaf_item_name **
+ ** **
+ **************************************************
+--]]
+
+function lib.get_leaf_item_name(plant_name)
+ local item_name = lib.modname .. ":" .. plant_name
+ local p = lib.plant_settings.support[plant_name]
+ -- p = possible_support_for_existing_item
+ if p ~= nil and p.can_grow then
+ -- the plant is present in the support AND can swap is set to true
+ local existing_item = p.itemname
+ local mod_name = existing_item:split(":")[1]
+
+ -- the itemname could be written incorrectly taken from the settings
+ if minetest.registered_items[existing_item] and minetest.get_modpath(mod_name) then
+
+ item_name = existing_item
+
+ end -- if mod is installed, and itemname exists
+
+ end -- if value is set to true
+
+ return item_name
+
+end -- get_leaf_item_name
+
+
+--[[
+ **************************************************
+ ** **
+ ** register_plant **
+ ** **
+ **************************************************
+ plant_name = "basil" or "rosemary" etc
+--]]
+
+function lib.register_plant(plant_name)
+ local plant_desc = plant_name:gsub("_", " "):gsub("(%a)(%a+)",
+ function(a, b)
+ return string.upper(a) .. string.lower(b)
+
+ end)
+
+ local leaf_item = lib.get_leaf_item_name(plant_name)
+ if leaf_item:split(":")[1] == lib.modname then
+
+ -- HERB/LEAVES DEFINITION --
+ local craftitem_def = {
+ description = S(plant_desc),
+ inventory_image = lib.modname .. "_".. plant_name ..".png",
+ groups = {},
+ }
+
+ craftitem_def.groups["food_".. plant_name] = 1
+ minetest.register_craftitem(lib.modname .. ":" .. plant_name, table.copy(craftitem_def) )
+
+ end -- if leaf item do not exists already (due to other mods)
+
+ -- STEM DEFINITION --
+ minetest.register_craftitem(lib.modname .. ":" .. plant_name .."_stem", {
+ description = S(plant_desc) .. " " .. S("Stem"),
+ inventory_image = lib.modname .. "_".. plant_name .."_stem.png",
+ groups = {stem = 1, flammable = 2,},
+ --the planting mechanism is in the pot on_rightclick
+
+ }) -- register_craftitem(
+
+
+ local plant_def = table.copy(pot_def)
+ plant_def.description = S("Pot with ") .. S(plant_desc)
+
+ -- POTTED plant_name STAGE 1 : just planted, gives stem back in case of accidental planting -----------------------
+ plant_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_1.png"
+ plant_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_1.png"
+ plant_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_1.png"
+ plant_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_1.png"
+ plant_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {lib.modname .. ":" .. plant_name .."_stem"} },
+
+ },
+
+ } -- plant_def.drop
+
+ minetest.register_node(lib.modname .. ":pot_with_".. plant_name .."_1", table.copy(plant_def) )
+
+ lib.register_plant_abm(lib.modname .. ":pot_with_".. plant_name .."_1",
+ lib.modname .. ":pot_with_".. plant_name .."_2", 30, 10)
+
+ -- POTTED PLANT STAGE 2 : growing, gives nothing yet --------------------------------------------------------------
+ plant_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_2.png"
+ plant_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_2.png"
+ plant_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_2.png"
+ plant_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_2.png"
+ plant_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ },
+
+ } -- plant_def.drop
+
+ minetest.register_node(lib.modname .. ":pot_with_".. plant_name .."_2", table.copy(plant_def) )
+
+ lib.register_plant_abm(lib.modname .. ":pot_with_".. plant_name .."_2",
+ "potted_farming:pot_with_".. plant_name .."_3", 40, 15)
+
+ -- POTTED PLANT STAGE 3 : fully grown, chance of giving stem, gives most leaves -----------------------------------
+ plant_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_3.png"
+ plant_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_3.png"
+ plant_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_" ..plant_name .."_3.png"
+ plant_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_3.png"
+ plant_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {leaf_item .." 2"}, rarity = 1},
+ {items = {leaf_item }, rarity = 2},
+ {items = {leaf_item }, rarity = 5},
+ {items = {lib.modname .. ":" .. plant_name .."_stem" }, rarity = 5},
+ }, -- items
+
+ } -- plant_def.drop
+
+ plant_def.on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ if player:is_player() then
+ local itemname = itemstack:get_name()
+ if itemname ~= watering_can_item then
+
+ local nodepos = pos
+ local q = math.random(2, 4) -- number of leaves taken
+ local stem = math.random(1, 5) -- chance of getting a stem
+ local leftover
+
+ local item = ItemStack(leaf_item .." ".. q)
+
+ local inv = player:get_inventory()
+ if inv:room_for_item("main", item) then
+ leftover = inv:add_item("main", item)
+ if not leftover:is_empty() then
+ minetest.add_item(player:get_pos(), leftover)
+ end
+ else
+
+ minetest.add_item(player:get_pos(), item)
+
+ end
+ local stem_item = lib.modname .. ":"..plant_name .."_stem"
+
+ if stem == 1 then
+ if inv:room_for_item("main", stem_item) then
+ leftover = inv:add_item("main", stem_item)
+ if not leftover:is_empty() then
+ minetest.add_item(player:get_pos(), leftover)
+ end
+ else
+
+ minetest.add_item(player:get_pos(), stem_item)
+
+ end -- if inv.room
+
+ end -- if stem
+
+ local n = math.random(1, 3)
+ minetest.sound_play("foliage-0".. n, {pos=nodepos, gain=1.2})
+
+ minetest.swap_node(nodepos, {name = lib.modname .. ":pot_with_".. plant_name .."_2"})
+
+ end -- itemstack empty
+
+ end-- player is a player
+
+ return itemstack
+
+ end -- plant_def.on_rightclick
+
+ minetest.register_node(lib.modname.. ":pot_with_".. plant_name .."_3", table.copy(plant_def) )
+
+ lib.register_plant_abm( lib.modname .. ":pot_with_".. plant_name .."_3",
+ lib.modname .. ":pot_with_".. plant_name .."_4", 40, 30)
+
+ -- POTTED PLANT STAGE 4 : needs water, no stem, few leaves --------------------------------------------------------
+ plant_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_4.png"
+ plant_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_4.png"
+ plant_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_4.png"
+ plant_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. plant_name .."_4.png"
+ plant_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {leaf_item .." 2"}},
+ {items = {leaf_item }, rarity = 3},
+
+ }, -- items
+
+ } -- plant_def.drop
+
+ plant_def.on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ if player:is_player() then
+ local itemname = itemstack:get_name()
+ if itemstack:is_empty() == false and itemname == watering_can_item then
+ local nodepos = pos
+
+ itemstack = lib.add_watering_can_wear(itemstack)
+
+ local n = math.random(3, 4)
+ minetest.sound_play("water-splash-0".. n, {pos=nodepos, gain=1.2})
+ minetest.swap_node(nodepos, {name = lib.modname .. ":pot_with_".. plant_name .."_3"})
+
+ end -- itemstack is watering_can
+
+ end -- player is a player
+ return itemstack
+
+ end -- plant_def.on_rightclick
+
+ minetest.register_node(lib.modname .. ":pot_with_" .. plant_name .."_4", table.copy(plant_def) )
+
+end -- register_plant
+
+
+--[[
+ **************************************************
+ ** **
+ ** register_wild_variant **
+ ** **
+ **************************************************
+
+--]]
+
+function lib.register_wild_variant(plant_name, nodes, s, min, max)
+ local leaf_item = lib.get_leaf_item_name(plant_name)
+
+ minetest.register_node(lib.modname .. ":wild_".. plant_name , {
+ description = S("Wild ").. S(plant_name),
+ paramtype = "light",
+ paramtype2 = "facedir",
+ walkable = false,
+ drawtype = "plantlike",
+ tiles = {lib.modname .. "_wild_".. plant_name ..".png"},
+ inventory_image = lib.modname .. "_wild_".. plant_name ..".png",
+ wield_image = lib.modname .. "_wild_".. plant_name ..".png",
+ groups = { snappy = 3, dig_immediate = 1, flammable = 2, plant = 1,
+ flora = 1, attached_node = 1, not_in_creative_inventory = 1 },
+ sounds = default.node_sound_leaves_defaults(),
+ selection_box = {
+ type = "fixed",
+ fixed = { {-4 / 16, -0.5, -4 / 16, 4 / 16, 5 / 16, 4 / 16}, },
+
+ }, -- selection_box
+
+ drop = {
+ items = {
+ {items = {leaf_item .." 2"}, rarity = 1},
+ {items = {leaf_item}, rarity = 2},
+ {items = {lib.modname .. ":" .. plant_name .."_stem"}, rarity = 1},
+
+ } -- items
+
+ }, -- drop
+
+ }) -- register_node
+
+ minetest.register_decoration({
+ deco_type = "simple",
+ place_on = nodes,
+ sidelen = 16,
+ noise_params = {
+ offset = 0,
+ scale = s,
+ spread = {x = 70, y = 70, z = 70},
+ seed = 2570,
+ octaves = 3,
+ persist = 0.6
+
+ }, -- noise_params
+ y_min = min,
+ y_max = max,
+ decoration = lib.modname .. ":wild_".. plant_name,
+
+ }) -- register_decoration
+
+end -- function lib.register_wild_variant
+
+
+--[[
+ **************************************************
+ ** **
+ ** register_mushroom **
+ ** **
+ **************************************************
+ mushroom_name = "brown" or "chanterelle" etc
+--]]
+
+
+function lib.register_mushroom(mushroom_name, full_mushroom_name)
+ -- the check if the mushroom item exists is done when calling the function, not inside
+
+ local mushroom_desc = mushroom_name:gsub("_", " "):gsub("(%a)(%a+)",
+ function(a, b)
+ return string.upper(a) .. string.lower(b)
+
+ end)
+
+ local mushroom_def = table.copy(pot_def)
+ mushroom_def.description = S("Pot with ") .. S(mushroom_desc)
+
+ --POTTED MUSHROOM STAGE 1 : just planted, gives back one mushroom
+ mushroom_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_1.png"
+ mushroom_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_1.png"
+ mushroom_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_1.png"
+ mushroom_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_1.png"
+ mushroom_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {full_mushroom_name} },
+
+ },
+
+ } -- mushroom_def.drop
+
+ minetest.register_node(lib.modname .. ":pot_with_".. mushroom_name .."_1", table.copy(mushroom_def) )
+
+ lib.register_plant_abm(lib.modname .. ":pot_with_".. mushroom_name .."_1",
+ lib.modname .. ":pot_with_".. mushroom_name .."_2", 30, 15)
+
+
+ --POTTED MUSHROOM STAGE 2 : growing, if you click you will get one mushroom, and goes back to 1
+ mushroom_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_2.png"
+ mushroom_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_2.png"
+ mushroom_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_" ..mushroom_name .."_2.png"
+ mushroom_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_2.png"
+ mushroom_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {full_mushroom_name } },
+ {items = {full_mushroom_name }, rarity = 2},
+
+ }, -- items
+
+ } -- mushroom_def.drop
+
+ mushroom_def.on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ if player:is_player() then
+ local itemname = itemstack:get_name()
+ if itemname ~= watering_can_item then
+
+ local nodepos = pos
+ local q = math.random(1, 2)
+ local mush_item = full_mushroom_name .." ".. q
+ local leftover
+
+ local inv = player:get_inventory()
+ if inv:room_for_item("main", mush_item) then
+ leftover = inv:add_item("main", mush_item)
+ if not leftover:is_empty() then
+ minetest.add_item(player:get_pos(), leftover)
+ end
+ else
+ minetest.add_item(player:get_pos(), mush_item)
+
+ end
+
+ local n = math.random(1, 3)
+ minetest.sound_play("foliage-0".. n, {pos=nodepos, gain=1.2})
+
+ minetest.swap_node(nodepos, {name = lib.modname .. ":pot_with_".. mushroom_name .."_1"})
+
+ end -- itemstack empty
+
+ end-- player is a player
+
+ return itemstack
+
+ end -- mushroom_def.on_rightclick
+
+ minetest.register_node(lib.modname.. ":pot_with_".. mushroom_name .."_2", table.copy(mushroom_def) )
+
+ lib.register_plant_abm(lib.modname .. ":pot_with_".. mushroom_name .."_2",
+ lib.modname .. ":pot_with_".. mushroom_name .."_3", 30, 20)
+
+
+ --POTTED MUSHROOM STAGE 3 : fully grown, click to get 2-4 mushrooms, goes back to 2, FINAL STAGE
+
+ mushroom_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_3.png"
+ mushroom_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_3.png"
+ mushroom_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_" ..mushroom_name .."_3.png"
+ mushroom_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_3.png"
+ mushroom_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {full_mushroom_name .." 2"}, rarity = 1},
+ {items = {full_mushroom_name }, rarity = 2},
+ {items = {full_mushroom_name }, rarity = 4},
+
+ }, -- items
+
+ } -- mushroom_def.drop
+
+ mushroom_def.on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ if player:is_player() then
+ local itemname = itemstack:get_name()
+ if itemname ~= watering_can_item then
+
+ local nodepos = pos
+ local q = math.random(2, 4) -- number of mushrooms taken
+ local mush_item = full_mushroom_name .." ".. q
+ local leftover
+
+ local inv = player:get_inventory()
+ if inv:room_for_item("main", mush_item) then
+ leftover = inv:add_item("main", mush_item)
+ if not leftover:is_empty() then
+ minetest.add_item(player:get_pos(), leftover)
+ end
+ else
+ minetest.add_item(nodepos, mush_item)
+
+ end
+
+ local n = math.random(1, 3)
+ minetest.sound_play("foliage-0".. n, {pos=nodepos, gain=1.2})
+
+ minetest.swap_node(nodepos, {name = lib.modname .. ":pot_with_".. mushroom_name .."_1"})
+
+ end -- itemstack empty
+
+ end-- player is a player
+
+ return itemstack
+
+ end -- mushroom_def.on_rightclick
+
+ minetest.register_node(lib.modname.. ":pot_with_".. mushroom_name .."_3", table.copy(mushroom_def) )
+
+
+ --POTTED MUSHROOM STAGE 4 : too much sun exposure, dryed up, ALL STAGES can get to this one if the lightlevel is too high
+ mushroom_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_4.png"
+ mushroom_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_4.png"
+ mushroom_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_" ..mushroom_name .."_4.png"
+ mushroom_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. mushroom_name .."_4.png"
+ mushroom_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {full_mushroom_name .." 2"}, rarity = 1},
+ {items = {full_mushroom_name }, rarity = 2},
+
+ }, -- items
+
+ } -- mushroom_def.drop
+
+ mushroom_def.on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ if player:is_player() then
+ local itemname = itemstack:get_name()
+ if itemname ~= watering_can_item then
+
+ local nodepos = pos
+ local q = math.random(1, 3) -- number of mushrooms taken
+ local mush_item = full_mushroom_name .." ".. q
+ local leftover
+
+ local inv = player:get_inventory()
+ if inv:room_for_item("main", mush_item) then
+ leftover = inv:add_item("main", mush_item)
+ if not leftover:is_empty() then
+ minetest.add_item(player:get_pos(), leftover)
+ end
+ else
+ minetest.add_item(nodepos, mush_item)
+
+ end
+
+ local n = math.random(1, 3)
+ minetest.sound_play("foliage-0".. n, {pos=nodepos, gain=1.2})
+
+ minetest.swap_node(nodepos, {name = lib.modname .. ":pot_with_soil"})
+
+ end -- itemstack empty
+
+ end-- player is a player
+
+ return itemstack
+
+ end -- mushroom_def.on_rightclick
+
+ minetest.register_node(lib.modname.. ":pot_with_".. mushroom_name .."_4", table.copy(mushroom_def) )
+
+ -- DRYING UP ABM
+
+ minetest.register_abm({
+ label = "drying_potted_mushroom_abm",
+ nodenames = {
+ lib.modname.. ":pot_with_".. mushroom_name .."_1",
+ lib.modname.. ":pot_with_".. mushroom_name .."_2",
+ lib.modname.. ":pot_with_".. mushroom_name .."_3",
+ },
+ --neighbors = {"default:air"},
+ interval = 30,
+ chance = 40,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local potted_plant = lib.plant_settings[mushroom_name]
+ local nodepos = pos
+ local can_grow = lib.check_light(nodepos, potted_plant.min_light, potted_plant.max_light)
+ if can_grow == false then
+ minetest.swap_node(nodepos, {name = lib.modname.. ":pot_with_".. mushroom_name .."_4"})
+
+ end -- if(lib.check
+
+ end, -- action =
+
+ }) -- register_abm
+
+end -- function lib.register_mushroom
+
+
+--[[
+ **************************************************
+ ** **
+ ** check_free_space_above **
+ ** **
+ **************************************************
+
+--]]
+
+function lib.check_free_space_above(pot_pos)
+ local above_pos = vector.add(pot_pos, vector.new(0, 1, 0))
+ local above_node = minetest.get_node(above_pos)
+
+ if above_node.name == "air" then
+ return true
+ end
+ return false
+
+end -- function lib.check_free_space_above
+
+
+--[[
+ **************************************************
+ ** **
+ ** register_sapling_abm **
+ ** **
+ **************************************************
+
+--]]
+
+function lib.register_sapling_abm(nodename, shrub_nodename, leaves_nodename, fruit , delay, percentage)
+ local potted_plant = lib.plant_settings[fruit]
+
+ minetest.register_abm({
+ label = "growing_potted_".. fruit .."_sapling_abm",
+ nodenames = {nodename},
+ --neighbors = {"default:air"}, --can be omitted
+ interval = delay,
+ chance = percentage,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local nodepos = pos
+ local above_nodepos = vector.add(nodepos, vector.new(0, 1, 0))
+ local enough_light = lib.check_light(above_nodepos, potted_plant.min_light, potted_plant.max_light)
+
+ if lib.check_free_space_above(nodepos) and enough_light then
+ minetest.swap_node(nodepos, {name = shrub_nodename})
+ minetest.swap_node(above_nodepos, {name = leaves_nodename})
+
+ end -- if(lib.check
+
+ end, -- action =
+
+ })
+
+end -- function lib.register_sapling_abm
+
+
+ --[[
+ **************************************************
+ ** **
+ ** register_leaves_abm **
+ ** **
+ **************************************************
+
+ --]]
+
+function lib.register_leaves_abm(nodename, fruit_leaves_nodename, thirsty_leaves_nodename, fruit, delay, percentage)
+ local potted_plant = lib.plant_settings[fruit]
+
+ minetest.register_abm({
+ label = "growing_potted_".. fruit .."_leaves_abm",
+ nodenames = {nodename},
+ --neighbors = {"default:air"}, --can be omitted
+ interval = delay,
+ chance = percentage,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local nodepos = pos
+ local leaves_nodename = thirsty_leaves_nodename
+ local enough_light = lib.check_light(nodepos, potted_plant.min_light, potted_plant.max_light )
+
+ local meta = minetest.get_meta(nodepos)
+ local pot_light = meta:get_int("lightlevel")
+
+ --minetest.chat_send_all("leaves abm ".. fruit .. " enough_light ".. tostring(enough_light) .." light at pos ".. pot_light)
+ if enough_light then
+ local growth = math.random(1, 3)
+
+ if growth == 1 then
+ leaves_nodename = fruit_leaves_nodename
+ end
+
+ end -- if(lib.check
+ minetest.swap_node(nodepos, {name = leaves_nodename, param2 = 2} )
+
+ end, -- action =
+
+ })
+
+end -- function lib.register_leaves_abm
+
+
+ --[[
+ **************************************************
+ ** **
+ ** register_fruit_tree **
+ ** **
+ **************************************************
+ sapling_name = "ethereal:lemon_tree_sapling", full_fruit_name = "ethereal:lemon"
+ --]]
+
+function lib.register_fruit_tree (k, sapling_name, full_fruit_name, original_leaves_png, shrub_to_use)
+ -- from sapling a litte potted tree
+ local fruit_mod_name = full_fruit_name:split(":")[1]
+ local fruit_name = k
+
+ local leaves_png = original_leaves_png
+ if leaves_png == nil then
+ leaves_png = "default_leaves.png"
+ end
+ if k == "banana" then
+ leaves_png = "(".. leaves_png .. "^".. lib.modname .."_banana_leaf_mask.png^[makealpha:255,0,255)"
+ end
+
+ local shrub_def = table.copy(pot_def)
+ shrub_def.description = S("Pot with ") .. S(fruit_name)
+
+ -- STAGE 1 pot with sapling : grows into pot with shrub after quite a while, will not grow unless there is space above
+ shrub_def.tiles[3] = "pot_with_soil_side.png^" .. lib.modname .. "_".. fruit_name .."_sapling.png"
+ shrub_def.tiles[4] = "pot_with_soil_side.png^" .. lib.modname .. "_".. fruit_name .."_sapling.png"
+ shrub_def.tiles[5] = "pot_with_soil_side.png^" .. lib.modname .. "_" ..fruit_name .."_sapling.png"
+ shrub_def.tiles[6] = "pot_with_soil_side.png^" .. lib.modname .. "_".. fruit_name .."_sapling.png"
+ shrub_def.drop = {
+ items = {
+ {items = {lib.modname .. ":pot_with_soil"} },
+ {items = {sapling_name} },
+
+ }, -- items
+
+ } -- shrub_def.drop
+
+ minetest.register_node(lib.modname.. ":pot_with_".. fruit_name .."_sapling", table.copy(shrub_def) )
+
+ lib.register_sapling_abm(lib.modname.. ":pot_with_".. fruit_name .."_sapling",
+ shrub_to_use,
+ lib.modname.. ":".. fruit_name .."_leaves_1", fruit_name, 40, 30)
+
+ -- STAGE 2 pot with shrub : does not grow but is the base for the leaf node above
+
+ -- MOVED to nodes.lua , registering one and only one shrub, im keeping the register_sapling_abm as it is
+ -- because i could have plants with a different shrub, like banana
+
+ -- ABOVE shrub, leaves : if the leaves are destroyed, the pot below is destroyed too, and viceversa
+ local fruit_desc = fruit_name:gsub("(%a)(%a+)",
+ function(a, b)
+ return string.upper(a) .. string.lower(b)
+
+ end)
+
+ local leaves_def = {
+ description = S(fruit_desc) .. " " .. S("(Leaves)") .. " 1",
+ drawtype = "plantlike", -- allfaces_optional
+ visual_scale = 1.3,
+ walkable = false,
+ paramtype = "light",
+ paramtype2 = "meshoptions",
+ place_param2 = 2,
+ tiles = { leaves_png },
+ sunlight_propagates = true,
+ groups = {snappy = 3, attached_node = 1, leaves = 1, flammable = 2, not_in_creative_inventory = 1 },
+ drop = {
+ items = {
+ {items = {sapling_name}, rarity = 5},
+ }
+ },
+ sounds = default.node_sound_leaves_defaults(),
+
+ } -- leaves_def
+
+ -- STAGE 1 : lemon leaves 1 : grows into leaves with lemon, OR leaves that needs water
+
+ minetest.register_node(lib.modname.. ":".. fruit_name .."_leaves_1", table.copy(leaves_def) )
+
+ -- special abm, with some randomness to determine wheather leaves should go to fruit leaves or thristy leaves
+ lib.register_leaves_abm(lib.modname.. ":".. fruit_name .."_leaves_1",
+ lib.modname.. ":".. fruit_name .."_leaves_2",
+ lib.modname.. ":".. fruit_name .."_leaves_3", fruit_name, 40, 15)
+
+ -- STAGE 2 : lemon leaves 2 : gives 2-3 lemons, does not grow more, goes back to leaves 1 when harvested
+
+ leaves_def.description = S(fruit_desc) .. " " .. S("(Leaves)") .. " 2"
+ leaves_def.tiles = {lib.modname .."_".. fruit_name .."_on_leaves.png^".. leaves_png }
+ leaves_def.drop = {
+ items = {
+ {items = {full_fruit_name .." 2"}},
+ }
+ }
+ leaves_def.on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ if player:is_player() then
+ local itemname = itemstack:get_name()
+
+ local nodepos = pos
+ local q = math.random(2, 3) -- number of fruit taken
+ local leftover
+ local item = ItemStack(full_fruit_name .." ".. q)
+
+ local inv = player:get_inventory()
+ if inv:room_for_item("main", item) then
+ leftover = inv:add_item("main", item)
+ if not leftover:is_empty() then
+ minetest.add_item(player:get_pos(), leftover)
+ end
+ else
+ minetest.add_item(player:get_pos(), item)
+
+ end
+
+ local n = math.random(1, 3)
+ minetest.sound_play("foliage-0".. n, {pos=nodepos, gain=1.2})
+
+ minetest.swap_node(nodepos, {name = lib.modname.. ":".. fruit_name .."_leaves_1", param2 = 2})
+
+ end-- player is a player
+
+ return itemstack
+
+ end -- leaves_def.on_rightclick
+
+ minetest.register_node(lib.modname.. ":".. fruit_name .."_leaves_2", table.copy(leaves_def) )
+
+ -- lemon leaves 3 : needs water, does not grow, goes back to leaves 1 when the POT is watered
+
+ leaves_def.description = S(fruit_desc) .. " " .. S("(Leaves)") .. " 3"
+ leaves_def.tiles = { leaves_png .. "^[colorize:yellow:35" }
+ leaves_def.drop = {}
+
+ minetest.register_node(lib.modname.. ":".. fruit_name .."_leaves_3", table.copy(leaves_def) )
+
+end -- function lib.register_fruit_tree
diff --git a/mods/potted_farming/locale/potted_farming.de.tr b/mods/potted_farming/locale/potted_farming.de.tr
new file mode 100644
index 00000000..3cbba0ef
--- /dev/null
+++ b/mods/potted_farming/locale/potted_farming.de.tr
@@ -0,0 +1,41 @@
+# textdomain: potted_farming
+
+
+### items.lua ###
+
+Empty Watering Can=Leere Gießkanne
+
+### lib.lua ###
+
+Pot with =Vase mit
+
+Basil=Basilikum
+Rosemary=Rosmarin
+Sage=Salbei
+Parsley=Petersilie
+Mint=Minze
+Oregano=Orig
+Stem=Stiel
+Wild =Wilde(r)
+
+Brown=Braun
+Cantharellus=Pfifferling
+Boletus=Steinpilz
+
+Lemon=Zitrone
+Orange=Orange
+Apple=Apfel
+(Leaves)=(Blätter)
+
+### nodes.lua ###
+
+Pot with Soil=Vase mit Erde
+Pot with Shrub=Vase mit Strauch
+Pot with Plantain=Vase mit Wegerich
+
+### tools.lua ###
+
+Watering Can=Gießkanne
+
+
+##### not used anymore #####
diff --git a/mods/potted_farming/locale/potted_farming.es.tr b/mods/potted_farming/locale/potted_farming.es.tr
new file mode 100644
index 00000000..99c06d7b
--- /dev/null
+++ b/mods/potted_farming/locale/potted_farming.es.tr
@@ -0,0 +1,40 @@
+# textdomain: potted_farming
+
+
+### items.lua ###
+
+Empty Watering Can=Regadera Vacia
+
+### lib.lua ###
+
+Pot with =Florero con
+
+Basil=Albahaca
+Rosemary=Romero
+Sage=Sabio
+Parsley=Perejil
+Mint=Menta
+Oregano=Origan
+Stem=con Raíz
+Wild =Salvaje
+
+Brown=Marrón
+Cantharellus=Gallinaccio
+Boletus=Boleto
+
+Lemon=Limón
+Orange=Naranja
+Apple=Manzana
+(Leaves)=(Hojas)
+
+### nodes.lua ###
+
+Pot with Soil=Florero con Tierra
+Pot with Shrub=Florero con Arbusto
+Pot with Plantain=Florero con Platano
+
+### tools.lua ###
+
+Watering Can=Regadera
+
+##### not used anymore ####
diff --git a/mods/potted_farming/locale/potted_farming.fr.tr b/mods/potted_farming/locale/potted_farming.fr.tr
new file mode 100644
index 00000000..3c1d8693
--- /dev/null
+++ b/mods/potted_farming/locale/potted_farming.fr.tr
@@ -0,0 +1,42 @@
+# textdomain: potted_farming
+# textdomain: potted_farming
+
+
+### items.lua ###
+
+Empty Watering Can=Arrosoir Vide
+
+### lib.lua ###
+
+Pot with =Vase avec
+
+Basil=Basilic
+Rosemary=Romarin
+Sage=Sauge
+Parsley=Persil
+Mint=Menthe
+Oregano=Origan
+# Stem= avec Tige
+Wild =Sauvage
+
+Brown=Brun
+Cantharellus=Chanterelle
+Boletus=Bolet
+
+Lemon=Citron
+Orange=Orange
+Apple=Pomme
+# (Leaves)= (Feuilles)
+
+### nodes.lua ###
+
+Pot with Soil=Vase avec de la Terre
+Pot with Shrub=Vase avec Arbuste
+Pot with Plantain=Vase avec Plantain
+
+### tools.lua ###
+
+Watering Can=Arrosoir
+
+
+##### not used anymore #####
diff --git a/mods/potted_farming/locale/potted_farming.it.tr b/mods/potted_farming/locale/potted_farming.it.tr
new file mode 100644
index 00000000..f19b23f6
--- /dev/null
+++ b/mods/potted_farming/locale/potted_farming.it.tr
@@ -0,0 +1,42 @@
+# textdomain: potted_farming
+# textdomain: potted_farming
+
+
+### items.lua ###
+
+Empty Watering Can=Innaffiatoio Vuoto
+
+### lib.lua ###
+
+Pot with =Vaso con
+
+Basil=Basilico
+Rosemary=Rosmarino
+Sage=Salvia
+Parsley=Prezzemolo
+Mint=Menta
+Oregano=Origano
+Stem=con Radice
+Wild =Selvatico
+
+Brown=Marrone
+Cantharellus=Gallinaccio
+Boletus=Boleto
+
+Lemon=Limone
+Orange=Arancia
+Apple=Mela
+(Leaves)=(Foglie)
+
+### nodes.lua ###
+
+Pot with Soil=Vaso con Terra
+Pot with Shrub=Vaso con Arbusto
+Pot with Plantain=Vaso con Piantaggine
+
+### tools.lua ###
+
+Watering Can=Innaffiatoio
+
+
+##### not used anymore #####
diff --git a/mods/potted_farming/locale/template.txt b/mods/potted_farming/locale/template.txt
new file mode 100644
index 00000000..56a016fe
--- /dev/null
+++ b/mods/potted_farming/locale/template.txt
@@ -0,0 +1,42 @@
+# textdomain: potted_farming
+Plantaintextdomain: potted_farming
+
+
+### items.lua ###
+
+Empty Watering Can=
+
+### lib.lua ###
+
+Pot with =
+
+Basil=
+Rosemary=
+Sage=
+Parsley=
+Mint=
+Oregano=
+Stem=
+Wild =
+
+Brown=
+Cantharellus=
+Boletus=
+
+Lemon=
+Orange=
+Apple=
+(Leaves)=
+
+### nodes.lua ###
+
+Pot with Soil=
+Pot with Shrub=
+Pot with Plantain=
+
+### tools.lua ###
+
+Watering Can=
+
+
+##### not used anymore #####
diff --git a/mods/potted_farming/mod.conf b/mods/potted_farming/mod.conf
new file mode 100644
index 00000000..ff23e7e1
--- /dev/null
+++ b/mods/potted_farming/mod.conf
@@ -0,0 +1,8 @@
+name = potted_farming
+depends = default, dye
+optional_depends = farming, flowers, herbs, cucina_vegana, ethereal, homedecor, cool_trees, moretrees
+author = Annalysa
+version = 4.2
+release = 18072
+description = In Arbeit. A new way to farm in pots herbs, mushrooms and regrow fruits on small trees.
+title = Potted Farming
diff --git a/mods/potted_farming/nodes.lua b/mods/potted_farming/nodes.lua
new file mode 100644
index 00000000..003e1e3e
--- /dev/null
+++ b/mods/potted_farming/nodes.lua
@@ -0,0 +1,179 @@
+local pf = potted_farming
+local S = pf.S
+local max_uses = pf.watering_can_max_uses
+
+-- Pot to plant
+minetest.register_node(pf.modname .. ":pot_with_soil", {
+ description = S("Pot with Soil"),
+
+ tiles = {
+ "pot_with_soil_top.png",
+ "pot_with_soil_bottom.png",
+ "pot_with_soil_side.png",
+ "pot_with_soil_side.png",
+ "pot_with_soil_side.png",
+ "pot_with_soil_side.png"
+ },
+
+ drawtype = "nodebox",
+ paramtype = "light",
+
+ groups = {oddly_breakable_by_hand = 4, crumbly = 1, cracky = 1, attached_node = 1},
+
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.1875, 0.1875}, -- base_center
+ {-0.25, -0.375, -0.125, -0.1875, -0.1875, 0.125}, -- base1
+ {-0.125, -0.375, 0.1875, 0.125, -0.1875, 0.25}, -- base2
+ {0.1875, -0.375, -0.125, 0.25, -0.1875, 0.125}, -- base3
+ {-0.125, -0.375, -0.25, 0.125, -0.1875, -0.1875}, -- base4
+ }
+ },
+
+ selection_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, -0.25, 0.25, -0.1875, 0.25}, -- selection
+ }
+ },
+
+ collision_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, -0.25, 0.25, -0.1875, 0.25}, -- selection
+ }
+ },
+
+ on_rightclick = function(pos, node, player, itemstack, pointed_thing)
+ if player:is_player() and itemstack:is_empty() == false then
+ local itemname = itemstack:get_name()
+ local acceptable, type, plant = pf.is_acceptable_source(itemname)
+ local step = "1"
+ if type == "fruit_tree" then step = "sapling" end
+ if acceptable then
+ local n = math.random(1, 3)
+ minetest.sound_play("dirt-0".. n, {pos=pos, gain=1.2})
+ minetest.set_node(pos, {name = pf.modname .. ":pot_with_".. plant .. "_" .. step})
+ itemstack:take_item()
+ end
+ end
+ end,
+
+ on_rotate = function(pos, node)
+ return false
+ end,
+
+}) -- minetest.register_node(pot
+
+local pot_with_shrub = {
+ description = S("Pot with Shrub"),
+ groups = {flammable = 2, crumbly = 1, cracky = 1, attached_node = 1, not_in_creative_inventory = 1},
+ tiles = {
+ "pot_with_soil_top.png",
+ "pot_with_soil_bottom.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_shrub.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_shrub.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_shrub.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_shrub.png"
+
+ }, -- tiles
+ drawtype = "nodebox",
+ paramtype = "light",
+ walkable = true,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.1875, 0.1875}, -- base_center
+ {-0.25, -0.375, -0.125, -0.1875, -0.1875, 0.125}, -- base1
+ {-0.125, -0.375, 0.1875, 0.125, -0.1875, 0.25}, -- base2
+ {0.1875, -0.375, -0.125, 0.25, -0.1875, 0.125}, -- base3
+ {-0.125, -0.375, -0.25, 0.125, -0.1875, -0.1875}, -- base4
+ {-0.5, -0.5, 0, 0.5, 0.5, 0}, -- plant1X
+ {0, -0.5, -0.5, 0, 0.5, 0.5}, -- plant2Z
+
+ } -- fixed
+
+ }, -- node_box
+ selection_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, -0.25, 0.25, 0.5, 0.25}, -- selection
+
+ }
+
+ }, -- selection_box
+
+ collision_box = {
+ type = "fixed",
+ fixed = {
+ {-0.25, -0.5, -0.25, 0.25, 0.5, 0.25}, -- selection
+
+ }
+
+ }, -- collsion_box
+
+ drop = {
+ items = {
+ {items = {pf.modname .. ":pot_with_soil"} },
+ {items = {"default:stick"} },
+
+ }, -- items
+
+ }, -- drop
+
+ on_rightclick = function (pos, node, player, itemstack, pointed_thing)
+ if player:is_player() then
+ local itemname = itemstack:get_name()
+ if itemstack:is_empty() == false and itemname == pf.modname .. ":watering_can" then
+ local nodepos = pos
+ local possible_leaf_pos = vector.add(nodepos, vector.new(0, 1, 0))
+ local possible_leaf = minetest.get_node(possible_leaf_pos)
+
+ local fruit_name = nil
+ for k, v in pairs(pf.fruit_tree_list) do
+ if possible_leaf.name == pf.modname ..":".. k .."_leaves_3" then
+ fruit_name = k
+ end
+ end -- fruit_tree_list iteration
+
+ if fruit_name ~= nil then
+ itemstack = pf.add_watering_can_wear(itemstack)
+
+ local n = math.random(3, 4)
+ minetest.sound_play("water-splash-0".. n, {pos=nodepos, gain=1.2})
+ minetest.swap_node(possible_leaf_pos, {name = pf.modname .. ":".. fruit_name .."_leaves_1", param2 = 2})
+
+ end -- if node above is ANY thirsty leaves
+
+ end -- itemstack is watering_can
+
+ end -- player is a player
+ return itemstack
+
+ end, -- on_rightclick
+ -- no further action is required, the leaves should fall on their own, as they too are an attached_node
+
+ on_rotate = function(pos, node)
+ return false
+ end,
+
+} -- pot_with_shrub table
+
+minetest.register_node(pf.modname ..":pot_with_shrub", table.copy(pot_with_shrub) )
+
+-- different shrub for a different plant, that differes only in texture
+-- unfortunately i have to make another node just for this difference
+
+pot_with_shrub.description = S("Pot with Plantain")
+pot_with_shrub.tiles = {
+ "pot_with_soil_top.png",
+ "pot_with_soil_bottom.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_plantain.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_plantain.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_plantain.png",
+ "pot_with_soil_side.png^" .. pf.modname .. "_plantain.png"
+
+} -- plantain tiles
+
+minetest.register_node(pf.modname ..":pot_with_plantain", table.copy(pot_with_shrub) )
diff --git a/mods/potted_farming/potted_mushrooms.png b/mods/potted_farming/potted_mushrooms.png
new file mode 100644
index 00000000..e8e46151
Binary files /dev/null and b/mods/potted_farming/potted_mushrooms.png differ
diff --git a/mods/potted_farming/potted_plants.png b/mods/potted_farming/potted_plants.png
new file mode 100644
index 00000000..d5d0d146
Binary files /dev/null and b/mods/potted_farming/potted_plants.png differ
diff --git a/mods/potted_farming/recipes.lua b/mods/potted_farming/recipes.lua
new file mode 100644
index 00000000..f8c58cfc
--- /dev/null
+++ b/mods/potted_farming/recipes.lua
@@ -0,0 +1,20 @@
+local pf = potted_farming
+
+--if minetest.registered_craftitems["flowerpot:empty"] then
+minetest.register_craft({
+ output = pf.modname .. ":pot_with_soil",
+ recipe = {
+ {"", "", ""},
+ {"default:clay_brick", "default:dirt", "default:clay_brick"},
+ {"dye:orange", "default:clay_brick", "dye:brown"},
+ }
+})
+
+minetest.register_craft({
+ output = pf.modname .. ":empty_watering_can",
+ recipe = {
+ {"default:tin_ingot", "", "dye:green"},
+ {"default:tin_ingot", "", "default:tin_ingot"},
+ {"dye:green", "default:tin_ingot", "dye:green"},
+ }
+})
diff --git a/mods/potted_farming/screenshot.png b/mods/potted_farming/screenshot.png
new file mode 100644
index 00000000..924d1a91
Binary files /dev/null and b/mods/potted_farming/screenshot.png differ
diff --git a/mods/potted_farming/settings.lua b/mods/potted_farming/settings.lua
new file mode 100644
index 00000000..283e7599
--- /dev/null
+++ b/mods/potted_farming/settings.lua
@@ -0,0 +1,144 @@
+local pf = potted_farming
+-- the or is to assure a value, there is a bug in mtv5 as per dev.mt which gives nil
+-- the names are fine and there are no overlapping
+
+-- Basil
+pf.plant_settings.basil = {}
+pf.plant_settings.basil.definable = minetest.settings:get_bool("basil", true)
+pf.plant_settings.basil.scale = minetest.settings:get("basil_scale") or 0.0003
+pf.plant_settings.basil.min_hight = minetest.settings:get("basil_min_hight") or 0
+pf.plant_settings.basil.max_hight = minetest.settings:get("basil_max_hight") or 250
+pf.plant_settings.basil.min_light = minetest.settings:get("basil_min_light") or 11
+pf.plant_settings.basil.max_light = minetest.settings:get("basil_max_light") or 14
+
+-- Rosemary
+pf.plant_settings.rosemary = {}
+pf.plant_settings.rosemary.definable = minetest.settings:get_bool("rosemary", true)
+pf.plant_settings.rosemary.scale = minetest.settings:get("rosemary_scale") or 0.0005
+pf.plant_settings.rosemary.min_hight = minetest.settings:get("rosemary_min_hight") or 10
+pf.plant_settings.rosemary.max_hight = minetest.settings:get("rosemary_max_hight") or 300
+pf.plant_settings.rosemary.min_light = minetest.settings:get("rosemary_min_light") or 11
+pf.plant_settings.rosemary.max_light = minetest.settings:get("rosemary_max_light") or 14
+
+-- Sage
+pf.plant_settings.sage = {}
+pf.plant_settings.sage.definable = minetest.settings:get_bool("sage", true)
+pf.plant_settings.sage.scale = minetest.settings:get("sage_scale") or 0.0004
+pf.plant_settings.sage.min_hight = minetest.settings:get("sage_min_hight") or 30
+pf.plant_settings.sage.max_hight = minetest.settings:get("sage_max_hight") or 400
+pf.plant_settings.sage.min_light = minetest.settings:get("sage_min_light") or 12
+pf.plant_settings.sage.max_light = minetest.settings:get("sage_max_light") or 14
+
+-- Parsley
+pf.plant_settings.parsley = {}
+pf.plant_settings.parsley.definable = minetest.settings:get_bool("parsley", true)
+pf.plant_settings.parsley.scale = minetest.settings:get("parsley_scale") or 0.0003
+pf.plant_settings.parsley.min_hight = minetest.settings:get("parsley_min_hight") or 0
+pf.plant_settings.parsley.max_hight = minetest.settings:get("parsley_max_hight") or 300
+pf.plant_settings.parsley.min_light = minetest.settings:get("parsley_min_light") or 11
+pf.plant_settings.parsley.max_light = minetest.settings:get("parsley_max_light") or 14
+
+-- Mint
+pf.plant_settings.mint = {}
+pf.plant_settings.mint.definable = minetest.settings:get_bool("mint", true)
+pf.plant_settings.mint.scale = minetest.settings:get("mint_scale") or 0.0007
+pf.plant_settings.mint.min_hight = minetest.settings:get("mint_min_hight") or 20
+pf.plant_settings.mint.max_hight = minetest.settings:get("mint_max_hight") or 250
+pf.plant_settings.mint.min_light = minetest.settings:get("mint_min_light") or 12
+pf.plant_settings.mint.max_light = minetest.settings:get("mint_max_light") or 14
+
+-- Oregano
+pf.plant_settings.oregano = {}
+pf.plant_settings.oregano.definable = minetest.settings:get_bool("oregano", true)
+pf.plant_settings.oregano.scale = minetest.settings:get("oregano_scale") or 0.0007
+pf.plant_settings.oregano.min_hight = minetest.settings:get("oregano_min_hight") or 20
+pf.plant_settings.oregano.max_hight = minetest.settings:get("oregano_max_hight") or 250
+pf.plant_settings.oregano.min_light = minetest.settings:get("oregano_min_light") or 11
+pf.plant_settings.oregano.max_light = minetest.settings:get("oregano_max_light") or 14
+
+-- Brown Mushroom
+pf.plant_settings.brown = {}
+pf.plant_settings.brown.definable = minetest.settings:get_bool("brown", true)
+pf.plant_settings.brown.min_light = minetest.settings:get("brown_min_light") or 1
+pf.plant_settings.brown.max_light = minetest.settings:get("brown_max_light") or 5
+
+-- Cantharellus Mushroom
+pf.plant_settings.cantharellus = {}
+pf.plant_settings.cantharellus.definable = minetest.settings:get_bool("cantharellus", true)
+pf.plant_settings.cantharellus.min_light = minetest.settings:get("cantharellus_min_light") or 1
+pf.plant_settings.cantharellus.max_light = minetest.settings:get("cantharellus_max_light") or 7
+
+-- Boletus Mushroom
+pf.plant_settings.boletus = {}
+pf.plant_settings.boletus.definable = minetest.settings:get_bool("boletus", true)
+pf.plant_settings.boletus.min_light = minetest.settings:get("boletus_min_light") or 0
+pf.plant_settings.boletus.max_light = minetest.settings:get("boletus_max_light") or 5
+
+-- Lemon Fruit Tree
+pf.plant_settings.lemon = {}
+pf.plant_settings.lemon.definable = minetest.settings:get_bool("lemon", true)
+pf.plant_settings.lemon.min_light = minetest.settings:get("lemon_min_light") or 13
+pf.plant_settings.lemon.max_light = minetest.settings:get("lemon_max_light") or 14
+
+-- Orange Fruit Tree
+pf.plant_settings.orange = {}
+pf.plant_settings.orange.definable = minetest.settings:get_bool("orange", true)
+pf.plant_settings.orange.min_light = minetest.settings:get("orange_min_light") or 13
+pf.plant_settings.orange.max_light = minetest.settings:get("orange_max_light") or 14
+
+-- Apple Fruit Tree
+pf.plant_settings.apple = {}
+pf.plant_settings.apple.definable = minetest.settings:get_bool("apple", true)
+pf.plant_settings.apple.min_light = minetest.settings:get("apple_min_light") or 12
+pf.plant_settings.apple.max_light = minetest.settings:get("apple_max_light") or 14
+
+-- Cherry Fruit Tree
+pf.plant_settings.cherry = {}
+pf.plant_settings.cherry.definable = minetest.settings:get_bool("cherry", true)
+pf.plant_settings.cherry.min_light = minetest.settings:get("cherry_min_light") or 12
+pf.plant_settings.cherry.max_light = minetest.settings:get("cherry_max_light") or 14
+
+-- Plum Fruit Tree
+pf.plant_settings.plum = {}
+pf.plant_settings.plum.definable = minetest.settings:get_bool("plum", true)
+pf.plant_settings.plum.min_light = minetest.settings:get("plum_min_light") or 12
+pf.plant_settings.plum.max_light = minetest.settings:get("plum_max_light") or 14
+
+-- Pomegranate Fruit Tree
+pf.plant_settings.pomegranate = {}
+pf.plant_settings.pomegranate.definable = minetest.settings:get_bool("pomegranate", true)
+pf.plant_settings.pomegranate.min_light = minetest.settings:get("pomegranate_min_light") or 13
+pf.plant_settings.pomegranate.max_light = minetest.settings:get("pomegranate_max_light") or 14
+
+-- Banana Fruit Tree
+pf.plant_settings.banana = {}
+pf.plant_settings.banana.definable = minetest.settings:get_bool("banana", true)
+pf.plant_settings.banana.min_light = minetest.settings:get("banana_min_light") or 13
+pf.plant_settings.banana.max_light = minetest.settings:get("banana_max_light") or 14
+
+-- Replace with existing item
+pf.plant_settings.support = {}
+-- Rosemary
+pf.plant_settings.support.rosemary = {}
+pf.plant_settings.support.rosemary.can_swap = minetest.settings:get_bool("support_rosemary", false)
+pf.plant_settings.support.rosemary.itemname = minetest.settings:get("support_rosemary_itemname") or "cucina_vegana:rosemary"
+-- Parsley
+pf.plant_settings.support.parsley = {}
+pf.plant_settings.support.parsley.can_swap = minetest.settings:get_bool("support_parsley", false)
+pf.plant_settings.support.parsley.itemname = minetest.settings:get("support_parsley_itemname") or "farming:parsley"
+-- Mint
+pf.plant_settings.support.mint = {}
+pf.plant_settings.support.mint.can_swap = minetest.settings:get_bool("support_mint", false)
+pf.plant_settings.support.mint.itemname = minetest.settings:get("support_mint_itemname") or "farming:mint_leaf"
+-- Lemon
+pf.plant_settings.support.lemon = {}
+pf.plant_settings.support.lemon.can_swap = minetest.settings:get_bool("support_lemon", false)
+-- Orange
+pf.plant_settings.support.orange = {}
+pf.plant_settings.support.orange.can_swap = minetest.settings:get_bool("support_orange", false)
+-- Apple
+pf.plant_settings.support.apple = {}
+pf.plant_settings.support.apple.can_swap = minetest.settings:get_bool("support_apple", false)
+-- Cherry
+pf.plant_settings.support.cherry = {}
+pf.plant_settings.support.cherry.can_swap = minetest.settings:get_bool("support_cherry", true)
diff --git a/mods/potted_farming/settingtypes.txt b/mods/potted_farming/settingtypes.txt
new file mode 100644
index 00000000..2c56afba
--- /dev/null
+++ b/mods/potted_farming/settingtypes.txt
@@ -0,0 +1,125 @@
+#Basil
+basil (Enable Basil) bool true
+basil_scale (Spreadvalue of wild variant) float 0.0003
+basil_min_hight (Min. Hight can be found on) int 0
+basil_max_hight (Max. Hight can be found on) int 250
+basil_min_light (Min. Light can grow) int 11
+basil_max_light (Max. Light can grow) int 14
+
+# Rosemary
+rosemary (Enable Rosemary) bool true
+rosemary_scale (Spreadvalue of wild variant) float 0.0005
+rosemary_min_hight (Min. Hight can be found on) int 10
+rosemary_max_hight (Max. Hight can be found on) int 300
+rosemary_min_light (Min. Light can grow) int 11
+rosemary_max_light (Max. Light can grow) int 14
+
+# Sage
+sage (Enable Sage) bool true
+sage_scale (Spreadvalue of wild variant) float 0.0004
+sage_min_hight (Min. Hight can be found on) int 30
+sage_max_hight (Max. Hight can be found on) int 400
+sage_min_light (Min. Light can grow) int 12
+sage_max_light (Max. Light can grow) int 14
+
+# Parsley
+parsley (Enable Parsley) bool true
+parsley_scale (Spreadvalue of wild variant) float 0.0003
+parsley_min_hight (Min. Hight can be found on) int 0
+parsley_max_hight (Max. Hight can be found on) int 300
+parsley_min_light (Min. Light can grow) int 11
+parsley_max_light (Max. Light can grow) int 14
+
+# Mint
+mint (Enable Mint) bool true
+mint_scale (Spreadvalue of wild variant) float 0.0007
+mint_min_hight (Min. Hight can be found on) int 0
+mint_max_hight (Max. Hight can be found on) int 250
+mint_min_light (Min. Light can grow) int 12
+mint_max_light (Max. Light can grow) int 14
+
+# Oregano
+oregano (Enable Oregano) bool true
+oregano_scale (Spreadvalue of wild variant) float 0.0004
+oregano_min_hight (Min. Hight can be found on) int 0
+oregano_max_hight (Max. Hight can be found on) int 300
+oregano_min_light (Min. Light can grow) int 12
+oregano_max_light (Max. Light can grow) int 14
+
+# Brown Mushroom
+brown (Enable Brown Mushroom) bool true
+brown_min_light (Min. Light can grow) int 1
+brown_max_light (Max. Light can grow) int 5
+
+# Cantharellus Mushroom
+cantharellus (Enable Chanterelle Mushroom) bool true
+cantharellus_min_light (Min. Light can grow) int 1
+cantharellus_max_light (Max. Light can grow) int 7
+
+# Boletus Mushroom
+boletus (Enable Boletus Mushroom) bool true
+boletus_min_light (Min. Light can grow) int 0
+boletus_max_light (Max. Light can grow) int 5
+
+# Lemon Fruit Tree
+lemon (Enable Lemon Fruit Tree) bool true
+lemon_min_light (Min. Light can grow) int 13
+lemon_max_light (Max. Light can grow) int 14
+
+# Orange Fruit Tree
+orange (Enable Orange Fruit Tree) bool true
+orange_min_light (Min. Light can grow) int 13
+orange_max_light (Max. Light can grow) int 14
+
+# Apple Fruit Tree
+apple (Enable Apple Fruit Tree) bool true
+apple_min_light (Min. Light can grow) int 12
+apple_max_light (Max. Light can grow) int 14
+
+# Cherry Fruit Tree
+cherry (Enable Cherry Fruit Tree) bool true
+cherry_min_light (Min. Light can grow) int 12
+cherry_max_light (Max. Light can grow) int 14
+
+# Plum Fruit Tree
+plum (Enable Plum Fruit Tree) bool true
+plum_min_light (Min. Light can grow) int 12
+plum_max_light (Max. Light can grow) int 14
+
+# Pomegranate Fruit Tree
+pomegranate (Enable Pomegranate Fruit Tree) bool true
+pomegranate_min_light (Min. Light can grow) int 12
+pomegranate_max_light (Max. Light can grow) int 14
+
+# Banana Fruit Tree
+banana (Enable Banana Fruit Tree) bool true
+banana_min_light (Min. Light can grow) int 13
+banana_max_light (Max. Light can grow) int 14
+
+
+# Replace with existing herb
+
+# Rosemary
+support_rosemary (Enable Rosemary to be swapped with existing item) bool false
+support_rosemary_itemname (Itemname that will replace upon harvesting) string "cucina_vegana:rosemary"
+
+# Parsley
+support_parsley (Enable Parsley to be swapped with existing item) bool false
+support_parsley_itemname (Itemname that will replace upon harvesting) string "farming:parsley"
+# possible parsley items (that i know of): farming:parsley , cucina_vegana:parsley
+
+# Mint
+support_mint (Enable Mint to be swapped with existing item) bool false
+support_mint_itemname (Itemname that will replace upon harvesting) string "farming:mint_leaf"
+
+# Lemon
+support_lemon (Enable Lemon to be from CoolTrees mod instead of Ethereal mod) bool false
+
+# Orange
+support_orange (Enable Orange to be from CoolTrees mod instead of Ethereal mod, using Clementine) bool false
+
+# Apple
+support_apple (Enable Apple Fruit Tree sapling and leaf texture to be taken from Moretrees mod) bool true
+
+# Cherry
+support_cherry (Enable Cherry Fruit Tree leaf texture to be the blossom variant) bool true
diff --git a/mods/potted_farming/sounds/dirt-01.ogg b/mods/potted_farming/sounds/dirt-01.ogg
new file mode 100644
index 00000000..508dfc45
Binary files /dev/null and b/mods/potted_farming/sounds/dirt-01.ogg differ
diff --git a/mods/potted_farming/sounds/dirt-02.ogg b/mods/potted_farming/sounds/dirt-02.ogg
new file mode 100644
index 00000000..5319f645
Binary files /dev/null and b/mods/potted_farming/sounds/dirt-02.ogg differ
diff --git a/mods/potted_farming/sounds/dirt-03.ogg b/mods/potted_farming/sounds/dirt-03.ogg
new file mode 100644
index 00000000..0f7c362b
Binary files /dev/null and b/mods/potted_farming/sounds/dirt-03.ogg differ
diff --git a/mods/potted_farming/sounds/dirt_license.txt b/mods/potted_farming/sounds/dirt_license.txt
new file mode 100644
index 00000000..75889fc2
--- /dev/null
+++ b/mods/potted_farming/sounds/dirt_license.txt
@@ -0,0 +1,4 @@
+Derived from Pack: Gravel Footsteps by Ali_6868
+https://freesound.org/people/Ali_6868/packs/21608/
+
+https://creativecommons.org/publicdomain/zero/1.0/
\ No newline at end of file
diff --git a/mods/potted_farming/sounds/foliage-01.ogg b/mods/potted_farming/sounds/foliage-01.ogg
new file mode 100644
index 00000000..d9772a8a
Binary files /dev/null and b/mods/potted_farming/sounds/foliage-01.ogg differ
diff --git a/mods/potted_farming/sounds/foliage-02.ogg b/mods/potted_farming/sounds/foliage-02.ogg
new file mode 100644
index 00000000..e27497a8
Binary files /dev/null and b/mods/potted_farming/sounds/foliage-02.ogg differ
diff --git a/mods/potted_farming/sounds/foliage-03.ogg b/mods/potted_farming/sounds/foliage-03.ogg
new file mode 100644
index 00000000..5bd8b4ed
Binary files /dev/null and b/mods/potted_farming/sounds/foliage-03.ogg differ
diff --git a/mods/potted_farming/sounds/foliage_license.txt b/mods/potted_farming/sounds/foliage_license.txt
new file mode 100644
index 00000000..d5d40381
--- /dev/null
+++ b/mods/potted_farming/sounds/foliage_license.txt
@@ -0,0 +1,7 @@
+Copyright/Attribution Notice:
+Share — copy and redistribute the material in any medium or format Adapt — remix,
+ transform, and build upon the material for any purpose, even commercially.
+ It would be nice if you can credit.
+
+Hi, my name is Antoine. I'm a French Sound Designer.
+https://opengameart.org/content/nature-sounds-pack
diff --git a/mods/potted_farming/sounds/water-splash-01.ogg b/mods/potted_farming/sounds/water-splash-01.ogg
new file mode 100644
index 00000000..ea09ace5
Binary files /dev/null and b/mods/potted_farming/sounds/water-splash-01.ogg differ
diff --git a/mods/potted_farming/sounds/water-splash-02.ogg b/mods/potted_farming/sounds/water-splash-02.ogg
new file mode 100644
index 00000000..230969e2
Binary files /dev/null and b/mods/potted_farming/sounds/water-splash-02.ogg differ
diff --git a/mods/potted_farming/sounds/water-splash-03.ogg b/mods/potted_farming/sounds/water-splash-03.ogg
new file mode 100644
index 00000000..a542ba20
Binary files /dev/null and b/mods/potted_farming/sounds/water-splash-03.ogg differ
diff --git a/mods/potted_farming/sounds/water-splash-04.ogg b/mods/potted_farming/sounds/water-splash-04.ogg
new file mode 100644
index 00000000..fc556aad
Binary files /dev/null and b/mods/potted_farming/sounds/water-splash-04.ogg differ
diff --git a/mods/potted_farming/sounds/water_splash_licence.txt b/mods/potted_farming/sounds/water_splash_licence.txt
new file mode 100644
index 00000000..2612a1ba
--- /dev/null
+++ b/mods/potted_farming/sounds/water_splash_licence.txt
@@ -0,0 +1,9 @@
+Sounds used from http://www.pdsounds.org/sounds/emptying_syringe_in_water_slow and http://www.pdsounds.org/sounds/emptying_syringe_in_water_fast
+
+No license aka public domain/cc0/wtfpl/do-what-you-want :)
+
+Cut by qubodup
+
+Hosted by opengameart.org
+
+converted from .flac to .ogg by Annalysa
diff --git a/mods/potted_farming/textures/pot_with_soil_bottom.png b/mods/potted_farming/textures/pot_with_soil_bottom.png
new file mode 100644
index 00000000..f95d68e4
Binary files /dev/null and b/mods/potted_farming/textures/pot_with_soil_bottom.png differ
diff --git a/mods/potted_farming/textures/pot_with_soil_side.png b/mods/potted_farming/textures/pot_with_soil_side.png
new file mode 100644
index 00000000..645e2264
Binary files /dev/null and b/mods/potted_farming/textures/pot_with_soil_side.png differ
diff --git a/mods/potted_farming/textures/pot_with_soil_top.png b/mods/potted_farming/textures/pot_with_soil_top.png
new file mode 100644
index 00000000..c1b48f04
Binary files /dev/null and b/mods/potted_farming/textures/pot_with_soil_top.png differ
diff --git a/mods/potted_farming/textures/potted_farming_apple_on_leaves.png b/mods/potted_farming/textures/potted_farming_apple_on_leaves.png
new file mode 100644
index 00000000..ad2d8bf8
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_apple_on_leaves.png differ
diff --git a/mods/potted_farming/textures/potted_farming_apple_sapling.png b/mods/potted_farming/textures/potted_farming_apple_sapling.png
new file mode 100644
index 00000000..ec9d477c
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_apple_sapling.png differ
diff --git a/mods/potted_farming/textures/potted_farming_banana_leaf_mask.png b/mods/potted_farming/textures/potted_farming_banana_leaf_mask.png
new file mode 100644
index 00000000..9ecd7e66
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_banana_leaf_mask.png differ
diff --git a/mods/potted_farming/textures/potted_farming_banana_on_leaves.png b/mods/potted_farming/textures/potted_farming_banana_on_leaves.png
new file mode 100644
index 00000000..ccfeae20
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_banana_on_leaves.png differ
diff --git a/mods/potted_farming/textures/potted_farming_banana_sapling.png b/mods/potted_farming/textures/potted_farming_banana_sapling.png
new file mode 100644
index 00000000..66cc0047
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_banana_sapling.png differ
diff --git a/mods/potted_farming/textures/potted_farming_basil.png b/mods/potted_farming/textures/potted_farming_basil.png
new file mode 100644
index 00000000..ccc936e0
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_basil.png differ
diff --git a/mods/potted_farming/textures/potted_farming_basil_1.png b/mods/potted_farming/textures/potted_farming_basil_1.png
new file mode 100644
index 00000000..4b9b8ef4
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_basil_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_basil_2.png b/mods/potted_farming/textures/potted_farming_basil_2.png
new file mode 100644
index 00000000..400636a4
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_basil_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_basil_3.png b/mods/potted_farming/textures/potted_farming_basil_3.png
new file mode 100644
index 00000000..bff64f6d
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_basil_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_basil_4.png b/mods/potted_farming/textures/potted_farming_basil_4.png
new file mode 100644
index 00000000..50bb8923
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_basil_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_basil_stem.png b/mods/potted_farming/textures/potted_farming_basil_stem.png
new file mode 100644
index 00000000..e44cf073
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_basil_stem.png differ
diff --git a/mods/potted_farming/textures/potted_farming_boletus_1.png b/mods/potted_farming/textures/potted_farming_boletus_1.png
new file mode 100644
index 00000000..50aba451
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_boletus_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_boletus_2.png b/mods/potted_farming/textures/potted_farming_boletus_2.png
new file mode 100644
index 00000000..0091efb9
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_boletus_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_boletus_3.png b/mods/potted_farming/textures/potted_farming_boletus_3.png
new file mode 100644
index 00000000..a9ff05c1
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_boletus_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_boletus_4.png b/mods/potted_farming/textures/potted_farming_boletus_4.png
new file mode 100644
index 00000000..05676ab8
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_boletus_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_brown_1.png b/mods/potted_farming/textures/potted_farming_brown_1.png
new file mode 100644
index 00000000..f6f5b843
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_brown_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_brown_2.png b/mods/potted_farming/textures/potted_farming_brown_2.png
new file mode 100644
index 00000000..49c7c279
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_brown_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_brown_3.png b/mods/potted_farming/textures/potted_farming_brown_3.png
new file mode 100644
index 00000000..8a5683cf
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_brown_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_brown_4.png b/mods/potted_farming/textures/potted_farming_brown_4.png
new file mode 100644
index 00000000..b18ff476
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_brown_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_brown_spores.png b/mods/potted_farming/textures/potted_farming_brown_spores.png
new file mode 100644
index 00000000..279c0c85
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_brown_spores.png differ
diff --git a/mods/potted_farming/textures/potted_farming_cantharellus_1.png b/mods/potted_farming/textures/potted_farming_cantharellus_1.png
new file mode 100644
index 00000000..d3c097ac
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_cantharellus_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_cantharellus_2.png b/mods/potted_farming/textures/potted_farming_cantharellus_2.png
new file mode 100644
index 00000000..93ea4789
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_cantharellus_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_cantharellus_3.png b/mods/potted_farming/textures/potted_farming_cantharellus_3.png
new file mode 100644
index 00000000..8686a1ff
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_cantharellus_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_cantharellus_4.png b/mods/potted_farming/textures/potted_farming_cantharellus_4.png
new file mode 100644
index 00000000..a45cbd8e
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_cantharellus_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_cantharellus_spores.png b/mods/potted_farming/textures/potted_farming_cantharellus_spores.png
new file mode 100644
index 00000000..2d705ac5
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_cantharellus_spores.png differ
diff --git a/mods/potted_farming/textures/potted_farming_cherry_on_leaves.png b/mods/potted_farming/textures/potted_farming_cherry_on_leaves.png
new file mode 100644
index 00000000..61dbec4b
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_cherry_on_leaves.png differ
diff --git a/mods/potted_farming/textures/potted_farming_cherry_sapling.png b/mods/potted_farming/textures/potted_farming_cherry_sapling.png
new file mode 100644
index 00000000..9c3e840f
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_cherry_sapling.png differ
diff --git a/mods/potted_farming/textures/potted_farming_empty_watering_can.png b/mods/potted_farming/textures/potted_farming_empty_watering_can.png
new file mode 100644
index 00000000..caf4e8fc
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_empty_watering_can.png differ
diff --git a/mods/potted_farming/textures/potted_farming_lemon_on_leaves.png b/mods/potted_farming/textures/potted_farming_lemon_on_leaves.png
new file mode 100644
index 00000000..1566e972
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_lemon_on_leaves.png differ
diff --git a/mods/potted_farming/textures/potted_farming_lemon_sapling.png b/mods/potted_farming/textures/potted_farming_lemon_sapling.png
new file mode 100644
index 00000000..89a49d06
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_lemon_sapling.png differ
diff --git a/mods/potted_farming/textures/potted_farming_mint.png b/mods/potted_farming/textures/potted_farming_mint.png
new file mode 100644
index 00000000..219bdc9c
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_mint.png differ
diff --git a/mods/potted_farming/textures/potted_farming_mint_1.png b/mods/potted_farming/textures/potted_farming_mint_1.png
new file mode 100644
index 00000000..f1274dc2
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_mint_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_mint_2.png b/mods/potted_farming/textures/potted_farming_mint_2.png
new file mode 100644
index 00000000..c15f1224
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_mint_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_mint_3.png b/mods/potted_farming/textures/potted_farming_mint_3.png
new file mode 100644
index 00000000..19e5cf3c
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_mint_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_mint_4.png b/mods/potted_farming/textures/potted_farming_mint_4.png
new file mode 100644
index 00000000..ac4651dd
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_mint_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_mint_stem.png b/mods/potted_farming/textures/potted_farming_mint_stem.png
new file mode 100644
index 00000000..3264adc4
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_mint_stem.png differ
diff --git a/mods/potted_farming/textures/potted_farming_orange_on_leaves.png b/mods/potted_farming/textures/potted_farming_orange_on_leaves.png
new file mode 100644
index 00000000..ffbd5a5c
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_orange_on_leaves.png differ
diff --git a/mods/potted_farming/textures/potted_farming_orange_sapling.png b/mods/potted_farming/textures/potted_farming_orange_sapling.png
new file mode 100644
index 00000000..9e850e5c
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_orange_sapling.png differ
diff --git a/mods/potted_farming/textures/potted_farming_oregano.png b/mods/potted_farming/textures/potted_farming_oregano.png
new file mode 100644
index 00000000..c5fc50dc
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_oregano.png differ
diff --git a/mods/potted_farming/textures/potted_farming_oregano_1.png b/mods/potted_farming/textures/potted_farming_oregano_1.png
new file mode 100644
index 00000000..d09ad4ad
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_oregano_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_oregano_2.png b/mods/potted_farming/textures/potted_farming_oregano_2.png
new file mode 100644
index 00000000..0a81612a
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_oregano_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_oregano_3.png b/mods/potted_farming/textures/potted_farming_oregano_3.png
new file mode 100644
index 00000000..eda1076b
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_oregano_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_oregano_4.png b/mods/potted_farming/textures/potted_farming_oregano_4.png
new file mode 100644
index 00000000..5250ba9f
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_oregano_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_oregano_stem.png b/mods/potted_farming/textures/potted_farming_oregano_stem.png
new file mode 100644
index 00000000..3004f09f
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_oregano_stem.png differ
diff --git a/mods/potted_farming/textures/potted_farming_parsley.png b/mods/potted_farming/textures/potted_farming_parsley.png
new file mode 100644
index 00000000..b89a350d
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_parsley.png differ
diff --git a/mods/potted_farming/textures/potted_farming_parsley_1.png b/mods/potted_farming/textures/potted_farming_parsley_1.png
new file mode 100644
index 00000000..91a4ab8c
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_parsley_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_parsley_2.png b/mods/potted_farming/textures/potted_farming_parsley_2.png
new file mode 100644
index 00000000..cafef6fb
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_parsley_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_parsley_3.png b/mods/potted_farming/textures/potted_farming_parsley_3.png
new file mode 100644
index 00000000..2c2bd643
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_parsley_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_parsley_4.png b/mods/potted_farming/textures/potted_farming_parsley_4.png
new file mode 100644
index 00000000..6a91d49f
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_parsley_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_parsley_stem.png b/mods/potted_farming/textures/potted_farming_parsley_stem.png
new file mode 100644
index 00000000..c41fa84e
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_parsley_stem.png differ
diff --git a/mods/potted_farming/textures/potted_farming_plantain.png b/mods/potted_farming/textures/potted_farming_plantain.png
new file mode 100644
index 00000000..c90446c6
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_plantain.png differ
diff --git a/mods/potted_farming/textures/potted_farming_plum_on_leaves.png b/mods/potted_farming/textures/potted_farming_plum_on_leaves.png
new file mode 100644
index 00000000..6cf88dc6
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_plum_on_leaves.png differ
diff --git a/mods/potted_farming/textures/potted_farming_plum_sapling.png b/mods/potted_farming/textures/potted_farming_plum_sapling.png
new file mode 100644
index 00000000..9d8dd0bf
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_plum_sapling.png differ
diff --git a/mods/potted_farming/textures/potted_farming_pomegranate_on_leaves.png b/mods/potted_farming/textures/potted_farming_pomegranate_on_leaves.png
new file mode 100644
index 00000000..ef9f8bec
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_pomegranate_on_leaves.png differ
diff --git a/mods/potted_farming/textures/potted_farming_pomegranate_sapling.png b/mods/potted_farming/textures/potted_farming_pomegranate_sapling.png
new file mode 100644
index 00000000..87346938
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_pomegranate_sapling.png differ
diff --git a/mods/potted_farming/textures/potted_farming_rosemary.png b/mods/potted_farming/textures/potted_farming_rosemary.png
new file mode 100644
index 00000000..f08b630f
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_rosemary.png differ
diff --git a/mods/potted_farming/textures/potted_farming_rosemary_1.png b/mods/potted_farming/textures/potted_farming_rosemary_1.png
new file mode 100644
index 00000000..f9f8abd8
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_rosemary_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_rosemary_2.png b/mods/potted_farming/textures/potted_farming_rosemary_2.png
new file mode 100644
index 00000000..f2dfea8a
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_rosemary_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_rosemary_3.png b/mods/potted_farming/textures/potted_farming_rosemary_3.png
new file mode 100644
index 00000000..c6c7cdd2
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_rosemary_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_rosemary_4.png b/mods/potted_farming/textures/potted_farming_rosemary_4.png
new file mode 100644
index 00000000..fedb94ee
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_rosemary_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_rosemary_stem.png b/mods/potted_farming/textures/potted_farming_rosemary_stem.png
new file mode 100644
index 00000000..0291d17b
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_rosemary_stem.png differ
diff --git a/mods/potted_farming/textures/potted_farming_sage.png b/mods/potted_farming/textures/potted_farming_sage.png
new file mode 100644
index 00000000..699a1a5b
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_sage.png differ
diff --git a/mods/potted_farming/textures/potted_farming_sage_1.png b/mods/potted_farming/textures/potted_farming_sage_1.png
new file mode 100644
index 00000000..d61c0939
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_sage_1.png differ
diff --git a/mods/potted_farming/textures/potted_farming_sage_2.png b/mods/potted_farming/textures/potted_farming_sage_2.png
new file mode 100644
index 00000000..8758c03b
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_sage_2.png differ
diff --git a/mods/potted_farming/textures/potted_farming_sage_3.png b/mods/potted_farming/textures/potted_farming_sage_3.png
new file mode 100644
index 00000000..2fc4fbfc
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_sage_3.png differ
diff --git a/mods/potted_farming/textures/potted_farming_sage_4.png b/mods/potted_farming/textures/potted_farming_sage_4.png
new file mode 100644
index 00000000..45f4c2b7
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_sage_4.png differ
diff --git a/mods/potted_farming/textures/potted_farming_sage_stem.png b/mods/potted_farming/textures/potted_farming_sage_stem.png
new file mode 100644
index 00000000..06635977
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_sage_stem.png differ
diff --git a/mods/potted_farming/textures/potted_farming_shrub.png b/mods/potted_farming/textures/potted_farming_shrub.png
new file mode 100644
index 00000000..9a0344ff
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_shrub.png differ
diff --git a/mods/potted_farming/textures/potted_farming_watering_can.png b/mods/potted_farming/textures/potted_farming_watering_can.png
new file mode 100644
index 00000000..975f0241
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_watering_can.png differ
diff --git a/mods/potted_farming/textures/potted_farming_wild_basil.png b/mods/potted_farming/textures/potted_farming_wild_basil.png
new file mode 100644
index 00000000..f8749c72
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_wild_basil.png differ
diff --git a/mods/potted_farming/textures/potted_farming_wild_mint.png b/mods/potted_farming/textures/potted_farming_wild_mint.png
new file mode 100644
index 00000000..057e686d
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_wild_mint.png differ
diff --git a/mods/potted_farming/textures/potted_farming_wild_oregano.png b/mods/potted_farming/textures/potted_farming_wild_oregano.png
new file mode 100644
index 00000000..db062d2d
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_wild_oregano.png differ
diff --git a/mods/potted_farming/textures/potted_farming_wild_parsley.png b/mods/potted_farming/textures/potted_farming_wild_parsley.png
new file mode 100644
index 00000000..2743e6c8
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_wild_parsley.png differ
diff --git a/mods/potted_farming/textures/potted_farming_wild_rosemary.png b/mods/potted_farming/textures/potted_farming_wild_rosemary.png
new file mode 100644
index 00000000..047f88a4
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_wild_rosemary.png differ
diff --git a/mods/potted_farming/textures/potted_farming_wild_sage.png b/mods/potted_farming/textures/potted_farming_wild_sage.png
new file mode 100644
index 00000000..0c5ae866
Binary files /dev/null and b/mods/potted_farming/textures/potted_farming_wild_sage.png differ
diff --git a/mods/potted_farming/textures/textures_license.txt b/mods/potted_farming/textures/textures_license.txt
new file mode 100644
index 00000000..0410da0d
--- /dev/null
+++ b/mods/potted_farming/textures/textures_license.txt
@@ -0,0 +1,83 @@
+License for Textures
+---------------------------------------
+
+CC-BY-SA 3.0 UNPORTED. Created by Annalysa
+
+---------------------------------------
+
+Creative Commons Attribution-ShareAlike 3.0 Unported
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+
+ "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
+ "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License.
+ "Creative Commons Compatible License" means a license that is listed at https://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License.
+ "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
+ "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
+ "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
+ "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
+ "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
+ "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
+ "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
+ "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
+
+2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
+
+ to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
+ to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
+ to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
+ to Distribute and Publicly Perform Adaptations.
+
+ For the avoidance of doubt:
+ Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
+ Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and,
+ Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License.
+
+The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved.
+
+4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
+
+ You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested.
+ You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License.
+ If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
+ Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+ This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
+ Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+ Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
+ Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
+ If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+ No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
+ This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
+ The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
+
+ Creative Commons Notice
+
+ Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
+
+ Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License.
+
+ Creative Commons may be contacted at https://creativecommons.org/.
+
+
diff --git a/mods/potted_farming/tools.lua b/mods/potted_farming/tools.lua
new file mode 100644
index 00000000..6eccd693
--- /dev/null
+++ b/mods/potted_farming/tools.lua
@@ -0,0 +1,21 @@
+local pf = potted_farming
+local S = pf.S
+
+
+minetest.register_tool(pf.modname .. ":watering_can", {
+ description = S("Watering Can"),
+ inventory_image = pf.modname .. "_watering_can.png",
+ groups = {watering_can = 1, not_in_creative_inventory = 1},
+ liquids_pointable = false,
+ stack_max = 1,
+ wear = pf.watering_can_max_uses,
+ range = 3.5,
+ tool_capabilities = {},
+})
+
+--[[
+ The watering_can usage is defined in the on_rightclick function of each
+ individual plant, therefor its add_wear too.
+ Furthermore, this allows me to set the plant in whatever state i want,
+ and allowing me to change multiple nodes like in the fruit tree situation.
+]]
diff --git a/mods/riesenpilz/init.lua b/mods/riesenpilz/init.lua
index 35304bb1..bdc858d0 100644
--- a/mods/riesenpilz/init.lua
+++ b/mods/riesenpilz/init.lua
@@ -559,6 +559,7 @@ for name, ndata in pairs({
chance = 18,
},
hp = 2,
+ groups = {mushroom = 1, food = 1, eatable = 2, food_mushroom = 1}
},
red = {
description = "red mushroom",
@@ -578,6 +579,7 @@ for name, ndata in pairs({
chance = 30,
},
hp = -2,
+ groups = {mushroom = 1, food = 1, eatable =-2}
},
fly_agaric = {
description = "fly agaric",
@@ -595,6 +597,7 @@ for name, ndata in pairs({
chance = 30,
},
hp = -6,
+ groups = {mushroom = 1, food = 1, eatable =-6}
},
lavashroom = {
description = "Lavashroom",
@@ -613,6 +616,7 @@ for name, ndata in pairs({
chance = 60,
},
hp = -1,
+ groups = {mushroom = 1, food = 1, eatable =-1}
},
glowshroom = {
description = "Glowshroom",
@@ -634,6 +638,7 @@ for name, ndata in pairs({
chance = 320,
},
hp = -2,
+ groups = {mushroom = 1, food = 1, eatable =-2}
},
nether_shroom = {
description = "Nether mushroom",
@@ -648,6 +653,7 @@ for name, ndata in pairs({
},
burntime = 6,
hp = -3,
+ groups = {mushroom = 1, food = 1, eatable =-3}
},
parasol = {
description = "white parasol mushroom",
@@ -667,6 +673,7 @@ for name, ndata in pairs({
chance = 36,
},
hp = 3,
+ groups = {mushroom = 1, food = 1, eatable =3, food_mushroom = 1}
},
red45 = {
description = "45 red mushroom",
@@ -687,6 +694,7 @@ for name, ndata in pairs({
chance = 180,
},
hp = 1,
+ groups = {mushroom = 1, food = 1, eatable =1, food_mushroom = 1}
},
brown45 = {
description = "45 brown mushroom",
@@ -706,6 +714,7 @@ for name, ndata in pairs({
chance = 20,
},
hp = 1,
+ groups = {mushroom = 1, food = 1, eatable =1, food_mushroom = 1}
},
}) do
local burntime = ndata.burntime or 1
@@ -730,7 +739,7 @@ for name, ndata in pairs({
buildable_to = true,
drawtype = "nodebox",
paramtype = "light",
- groups = {snappy=3,flammable=2,attached_node=1},
+ groups = {snappy=3,flammable=2,attached_node=1, eatable = (ndata.hp)},
sounds = default.node_sound_leaves_defaults(),
node_box = box,
selection_box = box,
@@ -1197,6 +1206,7 @@ minetest.register_craftitem("riesenpilz:mush45_meal", {
description = "Mushroom Meal",
inventory_image = "riesenpilz_mush45_meal.png",
on_use = minetest.item_eat(6),
+ groups = {eatable = 6, food = 2}
})
minetest.register_craft({
@@ -1276,3 +1286,6 @@ if time > 0.05 then
else
minetest.log("info", msg)
end
+
+
+
diff --git a/mods/sandwiches/init.lua b/mods/sandwiches/init.lua
index abc2832f..41f10c72 100644
--- a/mods/sandwiches/init.lua
+++ b/mods/sandwiches/init.lua
@@ -373,7 +373,7 @@ local sandwiches_recipes = {
{{"sandwiches:tabasco", "vessels:glass_bottle"},}
},
["italian"] = {{"mushroom", "veggie", "dairy"}, "Italian", 7,
- {"flowers:mushroom_brown", "group:food_tomato", "group:food_cheese"},
+ {"group:food_mushroom", "group:food_tomato", "group:food_cheese"},
},
["cheesy"] = {{"dairy", "dairy", "dairy"}, "Cheesy", 8,
{"group:food_cheese","group:food_cheese", "group:food_cheese"},
diff --git a/mods/sandwiches/luas/agriculture.lua b/mods/sandwiches/luas/agriculture.lua
index 1aa82a41..699f6858 100644
--- a/mods/sandwiches/luas/agriculture.lua
+++ b/mods/sandwiches/luas/agriculture.lua
@@ -36,7 +36,7 @@ minetest.register_craft({
output = "sandwiches:italian_sandwich",
recipe = {
{"", "sandwiches:bread_slice", ""},
- {"flowers:mushroom_brown", "agriculture:tomato", "group:food_cheese"},
+ {"group:food_mushroom", "agriculture:tomato", "group:food_cheese"},
{"", "sandwiches:bread_slice", ""},
},
})
diff --git a/mods/simple_furniture/init.lua b/mods/simple_furniture/init.lua
new file mode 100644
index 00000000..56f26429
--- /dev/null
+++ b/mods/simple_furniture/init.lua
@@ -0,0 +1,25 @@
+-- simple_furniture/init.lua
+
+-- Loads The LUA Files For The Mod
+
+local path = minetest.get_modpath("simple_furniture")
+
+dofile(path .. "/sf_benches.lua")
+dofile(path .. "/sf_chairs.lua")
+
+dofile(path .. "/sf_end_tables.lua")
+dofile(path .. "/sf_dining_tables.lua")
+
+dofile(path .. "/sf_cabinets.lua")
+
+dofile(path .. "/sf_crafting.lua")
+
+-- These Are For When Cropocalypse And/Or Dynamic Trees Are Installed
+-- Checks If Cropocalypse Is Installed
+if minetest.get_modpath("cropocalypse") then
+ dofile(path .. "/sf_cropocalypse.lua")
+end
+-- Checks If Dynamic Trees Is Installed
+if minetest.get_modpath("dynamic_trees") then
+ dofile(path .. "/sf_dynamic_trees.lua")
+end
\ No newline at end of file
diff --git a/mods/simple_furniture/license.txt b/mods/simple_furniture/license.txt
new file mode 100644
index 00000000..bd2743f3
--- /dev/null
+++ b/mods/simple_furniture/license.txt
@@ -0,0 +1,20 @@
+MIT License
+
+Copyright (c) 2023 Tarruvi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the “Software”), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be included in all copies
+or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/mods/simple_furniture/mod.conf b/mods/simple_furniture/mod.conf
new file mode 100644
index 00000000..bc813726
--- /dev/null
+++ b/mods/simple_furniture/mod.conf
@@ -0,0 +1,8 @@
+name = simple_furniture
+description = Minetest Game mod: simple_furniture
+depends = default, farming, stairs
+optional_depends = cropocalypse, dynamic_trees
+
+release = 17095
+author = Tarruvi
+title = Simple Furniture
diff --git a/mods/simple_furniture/sf_benches.lua b/mods/simple_furniture/sf_benches.lua
new file mode 100644
index 00000000..9c73302d
--- /dev/null
+++ b/mods/simple_furniture/sf_benches.lua
@@ -0,0 +1,650 @@
+-- simple_furniture/sf_benches.lua
+
+-- These Are The Nodes For The Benches
+-- Apple Wood Bench
+minetest.register_node("simple_furniture:apple_wood_bench", {
+ description = ("Apple Wood Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_wood.png"
+ },
+ groups = {sf_apple_wood_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:apple_wood_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Aspen Wood Bench
+minetest.register_node("simple_furniture:aspen_wood_bench", {
+ description = ("Aspen Wood Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_aspen_wood.png"
+ },
+ groups = {sf_aspen_wood_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:aspen_wood_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Pine Wood Bench
+minetest.register_node("simple_furniture:pine_wood_bench", {
+ description = ("Pine Wood Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_pine_wood.png"
+ },
+ groups = {sf_pine_wood_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pine_wood_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Jungle Wood Bench
+minetest.register_node("simple_furniture:jungle_wood_bench", {
+ description = ("Jungle Wood Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_junglewood.png"
+ },
+ groups = {sf_jungle_wood_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:jungle_wood_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Acacia Wood Bench
+minetest.register_node("simple_furniture:acacia_wood_bench", {
+ description = ("Acacia Wood Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_acacia_wood.png"
+ },
+ groups = {sf_acacia_wood_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:acacia_wood_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Stone Bench
+minetest.register_node("simple_furniture:stone_bench", {
+ description = ("Stone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_stone.png"
+ },
+ groups = {sf_stone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:stone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Stone Bench
+minetest.register_node("simple_furniture:desert_stone_bench", {
+ description = ("Desert Stone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_desert_stone.png"
+ },
+ groups = {sf_desert_stone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_stone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Sandstone Bench
+minetest.register_node("simple_furniture:sandstone_bench", {
+ description = ("Sandstone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_sandstone.png"
+ },
+ groups = {sf_sandstone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:sandstone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Sandstone Bench
+minetest.register_node("simple_furniture:desert_sandstone_bench", {
+ description = ("Desert Sandstone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_desert_sandstone.png"
+ },
+ groups = {sf_desert_sandstone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_sandstone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Silver Sandstone Bench
+minetest.register_node("simple_furniture:silver_sandstone_bench", {
+ description = ("Silver Sandstone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_silver_sandstone.png"
+ },
+ groups = {sf_silver_sandstone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:silver_sandstone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Cobblestone Bench
+minetest.register_node("simple_furniture:cobblestone_bench", {
+ description = ("Cobblestone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_cobble.png"
+ },
+ groups = {sf_cobblestone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:cobblestone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Mossy Cobblestone Bench
+minetest.register_node("simple_furniture:mossy_cobblestone_bench", {
+ description = ("Mossy Cobblestone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_mossycobble.png"
+ },
+ groups = {sf_mossy_cobblestone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:mossy_cobblestone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Cobblestone Bench
+minetest.register_node("simple_furniture:desert_cobblestone_bench", {
+ description = ("Desert Cobblestone Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_desert_cobble.png"
+ },
+ groups = {sf_desert_cobblestone_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_cobblestone_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Brick Bench
+minetest.register_node("simple_furniture:brick_bench", {
+ description = ("Brick Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_brick.png"
+ },
+ groups = {sf_brick_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:brick_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Obsidian Bench
+minetest.register_node("simple_furniture:obsidian_bench", {
+ description = ("Obsidian Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_obsidian.png"
+ },
+ groups = {sf_obsidian_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:obsidian_bench",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Snow Bench
+minetest.register_node("simple_furniture:snow_bench", {
+ description = ("Snow Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_snow.png"
+ },
+ groups = {sf_snow_bench = 1, bench = 1, crumbly = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:snow_bench",
+ sounds = default.node_sound_snow_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Ice Bench
+minetest.register_node("simple_furniture:ice_bench", {
+ description = ("Ice Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "default_ice.png"
+ },
+ groups = {sf_ice_bench = 1, bench = 1, cracky = 3, oddly_breakable_by_hand = 2, cools_lava = 1, slippery = 3},
+ drop = "simple_furniture:ice_bench",
+ sounds = default.node_sound_ice_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Straw Bench
+minetest.register_node("simple_furniture:straw_bench", {
+ description = ("Straw Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "farming_straw.png"
+ },
+ groups = {sf_straw_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:straw_bench",
+ sounds = default.node_sound_leaves_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
\ No newline at end of file
diff --git a/mods/simple_furniture/sf_cabinets.lua b/mods/simple_furniture/sf_cabinets.lua
new file mode 100644
index 00000000..c4f25b9e
--- /dev/null
+++ b/mods/simple_furniture/sf_cabinets.lua
@@ -0,0 +1,704 @@
+-- simple_furniture/sf_cabinets.lua
+
+-- These Are The Nodes For The Cabinets
+-- Apple Wood Cabinet
+minetest.register_node("simple_furniture:apple_wood_cabinet", {
+ description = ("Apple Wood Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_wood.png",
+ "default_wood.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "default_wood.png"
+ },
+ groups = {sf_apple_wood_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:apple_wood_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Aspen Wood Cabinet
+minetest.register_node("simple_furniture:aspen_wood_cabinet", {
+ description = ("Aspen Wood Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_aspen_wood.png",
+ "default_aspen_wood.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "default_aspen_wood.png"
+ },
+ groups = {sf_aspen_wood_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:aspen_wood_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Pine Wood Cabinet
+minetest.register_node("simple_furniture:pine_wood_cabinet", {
+ description = ("Pine Wood Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_pine_wood.png",
+ "default_pine_wood.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "default_pine_wood.png"
+ },
+ groups = {sf_pine_wood_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pine_wood_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Jungle Wood Cabinet
+minetest.register_node("simple_furniture:jungle_wood_cabinet", {
+ description = ("Jungle Wood Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_junglewood.png",
+ "default_junglewood.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "default_junglewood.png"
+ },
+ groups = {sf_jungle_wood_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:jungle_wood_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Acacia Wood Cabinet
+minetest.register_node("simple_furniture:acacia_wood_cabinet", {
+ description = ("Acacia Wood Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_acacia_wood.png",
+ "default_acacia_wood.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "default_acacia_wood.png"
+ },
+ groups = {sf_acacia_wood_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:acacia_wood_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Stone Cabinet
+minetest.register_node("simple_furniture:stone_cabinet", {
+ description = ("Stone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_stone.png",
+ "default_stone.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_stone.png"
+ },
+ groups = {sf_stone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:stone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Stone Cabinet
+minetest.register_node("simple_furniture:desert_stone_cabinet", {
+ description = ("Desert Stone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_stone.png",
+ "default_desert_stone.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_desert_stone.png"
+ },
+ groups = {sf_desert_stone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_stone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Sandstone Cabinet
+minetest.register_node("simple_furniture:sandstone_cabinet", {
+ description = ("Sandstone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_sandstone.png",
+ "default_sandstone.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_sandstone.png"
+ },
+ groups = {sf_sandstone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:sandstone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Sandstone Cabinet
+minetest.register_node("simple_furniture:desert_sandstone_cabinet", {
+ description = ("Desert Sandstone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_sandstone.png",
+ "default_desert_sandstone.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_desert_sandstone.png"
+ },
+ groups = {sf_desert_sandstone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_sandstone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Silver Sandstone Cabinet
+minetest.register_node("simple_furniture:silver_sandstone_cabinet", {
+ description = ("Silver Sandstone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_silver_sandstone.png",
+ "default_silver_sandstone.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_silver_sandstone.png"
+ },
+ groups = {sf_silver_sandstone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:silver_sandstone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Cobblestone Cabinet
+minetest.register_node("simple_furniture:cobblestone_cabinet", {
+ description = ("Cobblestone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_cobble.png",
+ "default_cobble.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_cobble.png"
+ },
+ groups = {sf_cobblestone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:cobblestone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Mossy Cobblestone Cabinet
+minetest.register_node("simple_furniture:mossy_cobblestone_cabinet", {
+ description = ("Mossy Cobblestone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_mossycobble.png",
+ "default_mossycobble.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_mossycobble.png"
+ },
+ groups = {sf_mossy_cobblestone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:mossy_cobblestone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Cobblestone Cabinet
+minetest.register_node("simple_furniture:desert_cobblestone_cabinet", {
+ description = ("Desert Cobblestone Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_cobble.png",
+ "default_desert_cobble.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_desert_cobble.png"
+ },
+ groups = {sf_desert_cobblestone_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_cobblestone_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Brick Cabinet
+minetest.register_node("simple_furniture:brick_cabinet", {
+ description = ("Brick Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_brick.png",
+ "default_brick.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_brick.png"
+ },
+ groups = {sf_brick_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:brick_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Obsidian Cabinet
+minetest.register_node("simple_furniture:obsidian_cabinet", {
+ description = ("Obsidian Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_obsidian.png",
+ "default_obsidian.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_obsidian.png"
+ },
+ groups = {sf_obsidian_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:obsidian_cabinet",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Snow Cabinet
+minetest.register_node("simple_furniture:snow_cabinet", {
+ description = ("Snow Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_snow.png",
+ "default_snow.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_snow.png"
+ },
+ groups = {sf_snow_cabinet = 1, cabinet = 1, crumbly = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:snow_cabinet",
+ sounds = default.node_sound_snow_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Ice Cabinet
+minetest.register_node("simple_furniture:ice_cabinet", {
+ description = ("Ice Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_ice.png",
+ "default_ice.png",
+ "default_furnace_top.png",
+ "default_furnace_top.png",
+ "default_furnace_side.png",
+ "default_ice.png"
+ },
+ groups = {sf_ice_cabinet = 1, cabinet = 1, cracky = 3, oddly_breakable_by_hand = 2, cools_lava = 1, slippery = 3},
+ drop = "simple_furniture:ice_cabinet",
+ sounds = default.node_sound_ice_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Straw Cabinet
+minetest.register_node("simple_furniture:straw_cabinet", {
+ description = ("Straw Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "farming_straw.png",
+ "farming_straw.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "farming_straw.png"
+ },
+ groups = {sf_straw_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:straw_cabinet",
+ sounds = default.node_sound_leaves_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
\ No newline at end of file
diff --git a/mods/simple_furniture/sf_chairs.lua b/mods/simple_furniture/sf_chairs.lua
new file mode 100644
index 00000000..fe87484e
--- /dev/null
+++ b/mods/simple_furniture/sf_chairs.lua
@@ -0,0 +1,614 @@
+-- simple_furniture/sf_chairs.lua
+
+-- These Are The Nodes For The Chairs
+-- Apple Wood Chair
+minetest.register_node("simple_furniture:apple_wood_chair", {
+ description = ("Apple Wood Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_wood.png"
+ },
+ groups = {sf_apple_wood_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:apple_wood_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Aspen Wood Chair
+minetest.register_node("simple_furniture:aspen_wood_chair", {
+ description = ("Aspen Wood Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_aspen_wood.png"
+ },
+ groups = {sf_aspen_wood_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:aspen_wood_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Pine Wood Chair
+minetest.register_node("simple_furniture:pine_wood_chair", {
+ description = ("Pine Wood Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_pine_wood.png"
+ },
+ groups = {sf_pine_wood_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pine_wood_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Jungle Wood Chair
+minetest.register_node("simple_furniture:jungle_wood_chair", {
+ description = ("Jungle Wood Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_junglewood.png"
+ },
+ groups = {sf_jungle_wood_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:jungle_wood_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Acacia Wood Chair
+minetest.register_node("simple_furniture:acacia_wood_chair", {
+ description = ("Acacia Wood Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_acacia_wood.png"
+ },
+ groups = {sf_acacia_wood_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:acacia_wood_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Stone Chair
+minetest.register_node("simple_furniture:stone_chair", {
+ description = ("Stone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_stone.png"
+ },
+ groups = {sf_stone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:stone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Stone Chair
+minetest.register_node("simple_furniture:desert_stone_chair", {
+ description = ("Desert Stone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_desert_stone.png"
+ },
+ groups = {sf_desert_stone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_stone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Sandstone Chair
+minetest.register_node("simple_furniture:sandstone_chair", {
+ description = ("Sandstone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_sandstone.png"
+ },
+ groups = {sf_sandstone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:sandstone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Sandstone Chair
+minetest.register_node("simple_furniture:desert_sandstone_chair", {
+ description = ("Desert Sandstone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_desert_sandstone.png"
+ },
+ groups = {sf_desert_sandstone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_sandstone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Silver Sandstone Chair
+minetest.register_node("simple_furniture:silver_sandstone_chair", {
+ description = ("Silver Sandstone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_silver_sandstone.png"
+ },
+ groups = {sf_silver_sandstone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:silver_sandstone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Cobblestone Chair
+minetest.register_node("simple_furniture:cobblestone_chair", {
+ description = ("Cobblestone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_cobble.png"
+ },
+ groups = {sf_cobblestone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:cobblestone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Mossy Cobblestone Chair
+minetest.register_node("simple_furniture:mossy_cobblestone_chair", {
+ description = ("Mossy Cobblestone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_mossycobble.png"
+ },
+ groups = {sf_mossy_cobblestone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:mossy_cobblestone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Cobblestone Chair
+minetest.register_node("simple_furniture:desert_cobblestone_chair", {
+ description = ("Desert Cobblestone Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_desert_cobble.png"
+ },
+ groups = {sf_desert_cobblestone_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_cobblestone_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Brick Chair
+minetest.register_node("simple_furniture:brick_chair", {
+ description = ("Brick Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_brick.png"
+ },
+ groups = {sf_brick_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:brick_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Obsidian Chair
+minetest.register_node("simple_furniture:obsidian_chair", {
+ description = ("Obsidian Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_obsidian.png"
+ },
+ groups = {sf_obsidian_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:obsidian_chair",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Snow Chair
+minetest.register_node("simple_furniture:snow_chair", {
+ description = ("Snow Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_snow.png"
+ },
+ groups = {sf_snow_chair = 1, chair = 1, crumbly = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:snow_chair",
+ sounds = default.node_sound_snow_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Ice Chair
+minetest.register_node("simple_furniture:ice_chair", {
+ description = ("Ice Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "default_ice.png"
+ },
+ groups = {sf_ice_chair = 1, chair = 1, cracky = 3, oddly_breakable_by_hand = 2, cools_lava = 1, slippery = 3},
+ drop = "simple_furniture:ice_chair",
+ sounds = default.node_sound_ice_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Straw Chair
+minetest.register_node("simple_furniture:straw_chair", {
+ description = ("Straw Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "farming_straw.png"
+ },
+ groups = {sf_straw_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:straw_chair",
+ sounds = default.node_sound_leaves_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
\ No newline at end of file
diff --git a/mods/simple_furniture/sf_crafting.lua b/mods/simple_furniture/sf_crafting.lua
new file mode 100644
index 00000000..017d333c
--- /dev/null
+++ b/mods/simple_furniture/sf_crafting.lua
@@ -0,0 +1,902 @@
+-- simple_furniture/sf_crafting.lua
+
+-- These Are The Crafting Recipes For The Mod
+-- Apple Wood Bench
+minetest.register_craft({
+ output = "simple_furniture:apple_wood_bench",
+ recipe = {
+ {"", "", "stairs:slab_wood"},
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
+ {"stairs:slab_wood", "", "stairs:slab_wood"},
+ }
+})
+
+-- Apple Wood Cabinet
+minetest.register_craft({
+ output = "simple_furniture:apple_wood_cabinet",
+ recipe = {
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
+ {"doors:trapdoor", "stairs:slab_wood", "doors:trapdoor"},
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
+ }
+})
+
+-- Apple Wood Chair
+minetest.register_craft({
+ output = "simple_furniture:apple_wood_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Apple Wood Dining Table
+minetest.register_craft({
+ output = "simple_furniture:apple_wood_dining_table",
+ recipe = {
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_wood", ""},
+ }
+})
+
+-- Apple Wood End Table
+minetest.register_craft({
+ output = "simple_furniture:apple_wood_end_table",
+ recipe = {
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Aspen Wood Bench
+minetest.register_craft({
+ output = "simple_furniture:aspen_wood_bench",
+ recipe = {
+ {"", "", "stairs:slab_aspen_wood"},
+ {"stairs:slab_aspen_wood", "stairs:slab_aspen_wood", "stairs:slab_aspen_wood"},
+ {"stairs:slab_aspen_wood", "", "stairs:slab_aspen_wood"},
+ }
+})
+
+-- Aspen Wood Cabinet
+minetest.register_craft({
+ output = "simple_furniture:aspen_wood_cabinet",
+ recipe = {
+ {"stairs:slab_aspen_wood", "stairs:slab_aspen_wood", "stairs:slab_aspen_wood"},
+ {"doors:trapdoor", "stairs:slab_aspen_wood", "doors:trapdoor"},
+ {"stairs:slab_aspen_wood", "stairs:slab_aspen_wood", "stairs:slab_aspen_wood"},
+ }
+})
+
+-- Aspen Wood Chair
+minetest.register_craft({
+ output = "simple_furniture:aspen_wood_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_aspen_wood", "stairs:slab_aspen_wood", "stairs:slab_aspen_wood"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Aspen Wood Dining Table
+minetest.register_craft({
+ output = "simple_furniture:aspen_wood_dining_table",
+ recipe = {
+ {"stairs:slab_aspen_wood", "stairs:slab_aspen_wood", "stairs:slab_aspen_wood"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_aspen_wood", ""},
+ }
+})
+
+-- Aspen Wood End Table
+minetest.register_craft({
+ output = "simple_furniture:aspen_wood_end_table",
+ recipe = {
+ {"stairs:slab_aspen_wood", "stairs:slab_aspen_wood", "stairs:slab_aspen_wood"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Pine Wood Bench
+minetest.register_craft({
+ output = "simple_furniture:pine_wood_bench",
+ recipe = {
+ {"", "", "stairs:slab_pine_wood"},
+ {"stairs:slab_pine_wood", "stairs:slab_pine_wood", "stairs:slab_pine_wood"},
+ {"stairs:slab_pine_wood", "", "stairs:slab_pine_wood"},
+ }
+})
+
+-- Pine Wood Cabinet
+minetest.register_craft({
+ output = "simple_furniture:pine_wood_cabinet",
+ recipe = {
+ {"stairs:slab_pine_wood", "stairs:slab_pine_wood", "stairs:slab_pine_wood"},
+ {"doors:trapdoor", "stairs:slab_pine_wood", "doors:trapdoor"},
+ {"stairs:slab_pine_wood", "stairs:slab_pine_wood", "stairs:slab_pine_wood"},
+ }
+})
+
+-- Pine Wood Chair
+minetest.register_craft({
+ output = "simple_furniture:pine_wood_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_pine_wood", "stairs:slab_pine_wood", "stairs:slab_pine_wood"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Pine Wood Dining Table
+minetest.register_craft({
+ output = "simple_furniture:pine_wood_dining_table",
+ recipe = {
+ {"stairs:slab_pine_wood", "stairs:slab_pine_wood", "stairs:slab_pine_wood"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_pine_wood", ""},
+ }
+})
+
+-- Pine Wood End Table
+minetest.register_craft({
+ output = "simple_furniture:pine_wood_end_table",
+ recipe = {
+ {"stairs:slab_pine_wood", "stairs:slab_pine_wood", "stairs:slab_pine_wood"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Jungle Wood Bench
+minetest.register_craft({
+ output = "simple_furniture:jungle_wood_bench",
+ recipe = {
+ {"", "", "stairs:slab_junglewood"},
+ {"stairs:slab_junglewood", "stairs:slab_junglewood", "stairs:slab_junglewood"},
+ {"stairs:slab_junglewood", "", "stairs:slab_junglewood"},
+ }
+})
+
+-- Jungle Wood Cabinet
+minetest.register_craft({
+ output = "simple_furniture:jungle_wood_cabinet",
+ recipe = {
+ {"stairs:slab_junglewood", "stairs:slab_junglewood", "stairs:slab_junglewood"},
+ {"doors:trapdoor", "stairs:slab_junglewood", "doors:trapdoor"},
+ {"stairs:slab_junglewood", "stairs:slab_junglewood", "stairs:slab_junglewood"},
+ }
+})
+
+-- Jungle Wood Chair
+minetest.register_craft({
+ output = "simple_furniture:jungle_wood_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_junglewood", "stairs:slab_junglewood", "stairs:slab_junglewood"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Jungle Wood Dining Table
+minetest.register_craft({
+ output = "simple_furniture:jungle_wood_dining_table",
+ recipe = {
+ {"stairs:slab_junglewood", "stairs:slab_junglewood", "stairs:slab_junglewood"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_junglewood", ""},
+ }
+})
+
+-- Jungle Wood End Table
+minetest.register_craft({
+ output = "simple_furniture:jungle_wood_end_table",
+ recipe = {
+ {"stairs:slab_junglewood", "stairs:slab_junglewood", "stairs:slab_junglewood"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Acacia Wood Bench
+minetest.register_craft({
+ output = "simple_furniture:acacia_wood_bench",
+ recipe = {
+ {"", "", "stairs:slab_acacia_wood"},
+ {"stairs:slab_acacia_wood", "stairs:slab_acacia_wood", "stairs:slab_acacia_wood"},
+ {"stairs:slab_acacia_wood", "", "stairs:slab_acacia_wood"},
+ }
+})
+
+-- Acacia Wood Cabinet
+minetest.register_craft({
+ output = "simple_furniture:acacia_wood_cabinet",
+ recipe = {
+ {"stairs:slab_acacia_wood", "stairs:slab_acacia_wood", "stairs:slab_acacia_wood"},
+ {"doors:trapdoor", "stairs:slab_acacia_wood", "doors:trapdoor"},
+ {"stairs:slab_acacia_wood", "stairs:slab_acacia_wood", "stairs:slab_acacia_wood"},
+ }
+})
+
+-- Acacia Wood Chair
+minetest.register_craft({
+ output = "simple_furniture:acacia_wood_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_acacia_wood", "stairs:slab_acacia_wood", "stairs:slab_acacia_wood"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Acacia Wood Dining Table
+minetest.register_craft({
+ output = "simple_furniture:acacia_wood_dining_table",
+ recipe = {
+ {"stairs:slab_acacia_wood", "stairs:slab_acacia_wood", "stairs:slab_acacia_wood"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_acacia_wood", ""},
+ }
+})
+
+-- Acacia Wood End Table
+minetest.register_craft({
+ output = "simple_furniture:acacia_wood_end_table",
+ recipe = {
+ {"stairs:slab_acacia_wood", "stairs:slab_acacia_wood", "stairs:slab_acacia_wood"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Stone Bench
+minetest.register_craft({
+ output = "simple_furniture:stone_bench",
+ recipe = {
+ {"", "", "stairs:slab_stone"},
+ {"stairs:slab_stone", "stairs:slab_stone", "stairs:slab_stone"},
+ {"stairs:slab_stone", "", "stairs:slab_stone"},
+ }
+})
+
+-- Stone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:stone_cabinet",
+ recipe = {
+ {"stairs:slab_stone", "stairs:slab_stone", "stairs:slab_stone"},
+ {"doors:trapdoor", "stairs:slab_stone", "doors:trapdoor"},
+ {"stairs:slab_stone", "stairs:slab_stone", "stairs:slab_stone"},
+ }
+})
+
+-- Stone Chair
+minetest.register_craft({
+ output = "simple_furniture:stone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_stone", "stairs:slab_stone", "stairs:slab_stone"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Stone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:stone_dining_table",
+ recipe = {
+ {"stairs:slab_stone", "stairs:slab_stone", "stairs:slab_stone"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_stone", ""},
+ }
+})
+
+-- Stone End Table
+minetest.register_craft({
+ output = "simple_furniture:stone_end_table",
+ recipe = {
+ {"stairs:slab_stone", "stairs:slab_stone", "stairs:slab_stone"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Desert Stone Bench
+minetest.register_craft({
+ output = "simple_furniture:desert_stone_bench",
+ recipe = {
+ {"", "", "stairs:slab_desert_stone"},
+ {"stairs:slab_desert_stone", "stairs:slab_desert_stone", "stairs:slab_desert_stone"},
+ {"stairs:slab_desert_stone", "", "stairs:slab_desert_stone"},
+ }
+})
+
+-- Desert Stone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:desert_stone_cabinet",
+ recipe = {
+ {"stairs:slab_desert_stone", "stairs:slab_desert_stone", "stairs:slab_desert_stone"},
+ {"doors:trapdoor", "stairs:slab_desert_stone", "doors:trapdoor"},
+ {"stairs:slab_desert_stone", "stairs:slab_desert_stone", "stairs:slab_desert_stone"},
+ }
+})
+
+-- Desert Stone Chair
+minetest.register_craft({
+ output = "simple_furniture:desert_stone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_desert_stone", "stairs:slab_desert_stone", "stairs:slab_desert_stone"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Desert Stone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:desert_stone_dining_table",
+ recipe = {
+ {"stairs:slab_desert_stone", "stairs:slab_desert_stone", "stairs:slab_desert_stone"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_desert_stone", ""},
+ }
+})
+
+-- Desert Stone End Table
+minetest.register_craft({
+ output = "simple_furniture:desert_stone_end_table",
+ recipe = {
+ {"stairs:slab_desert_stone", "stairs:slab_desert_stone", "stairs:slab_desert_stone"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Sandstone Bench
+minetest.register_craft({
+ output = "simple_furniture:sandstone_bench",
+ recipe = {
+ {"", "", "stairs:slab_sandstone"},
+ {"stairs:slab_sandstone", "stairs:slab_sandstone", "stairs:slab_sandstone"},
+ {"stairs:slab_sandstone", "", "stairs:slab_sandstone"},
+ }
+})
+
+-- Sandstone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:sandstone_cabinet",
+ recipe = {
+ {"stairs:slab_sandstone", "stairs:slab_sandstone", "stairs:slab_sandstone"},
+ {"doors:trapdoor", "stairs:slab_sandstone", "doors:trapdoor"},
+ {"stairs:slab_sandstone", "stairs:slab_sandstone", "stairs:slab_sandstone"},
+ }
+})
+
+-- Sandstone Chair
+minetest.register_craft({
+ output = "simple_furniture:sandstone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_sandstone", "stairs:slab_sandstone", "stairs:slab_sandstone"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Sandstone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:sandstone_dining_table",
+ recipe = {
+ {"stairs:slab_sandstone", "stairs:slab_sandstone", "stairs:slab_sandstone"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_sandstone", ""},
+ }
+})
+
+-- Sandstone End Table
+minetest.register_craft({
+ output = "simple_furniture:sandstone_end_table",
+ recipe = {
+ {"stairs:slab_sandstone", "stairs:slab_sandstone", "stairs:slab_sandstone"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Desert Sandstone Bench
+minetest.register_craft({
+ output = "simple_furniture:desert_sandstone_bench",
+ recipe = {
+ {"", "", "stairs:slab_desert_sandstone"},
+ {"stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone"},
+ {"stairs:slab_desert_sandstone", "", "stairs:slab_desert_sandstone"},
+ }
+})
+
+-- Desert Sandstone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:desert_sandstone_cabinet",
+ recipe = {
+ {"stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone"},
+ {"doors:trapdoor", "stairs:slab_desert_sandstone", "doors:trapdoor"},
+ {"stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone"},
+ }
+})
+
+-- Desert Sandstone Chair
+minetest.register_craft({
+ output = "simple_furniture:desert_sandstone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Desert Sandstone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:desert_sandstone_dining_table",
+ recipe = {
+ {"stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_desert_sandstone", ""},
+ }
+})
+
+-- Desert Sandstone End Table
+minetest.register_craft({
+ output = "simple_furniture:desert_sandstone_end_table",
+ recipe = {
+ {"stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone", "stairs:slab_desert_sandstone"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Silver Sandstone Bench
+minetest.register_craft({
+ output = "simple_furniture:silver_sandstone_bench",
+ recipe = {
+ {"", "", "stairs:slab_silver_sandstone"},
+ {"stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone"},
+ {"stairs:slab_silver_sandstone", "", "stairs:slab_silver_sandstone"},
+ }
+})
+
+-- Silver Sandstone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:silver_sandstone_cabinet",
+ recipe = {
+ {"stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone"},
+ {"doors:trapdoor", "stairs:slab_silver_sandstone", "doors:trapdoor"},
+ {"stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone"},
+ }
+})
+
+-- Silver Sandstone Chair
+minetest.register_craft({
+ output = "simple_furniture:silver_sandstone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Silver Sandstone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:silver_sandstone_dining_table",
+ recipe = {
+ {"stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_silver_sandstone", ""},
+ }
+})
+
+-- Silver Sandstone End Table
+minetest.register_craft({
+ output = "simple_furniture:silver_sandstone_end_table",
+ recipe = {
+ {"stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone", "stairs:slab_silver_sandstone"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Cobblestone Bench
+minetest.register_craft({
+ output = "simple_furniture:cobblestone_bench",
+ recipe = {
+ {"", "", "stairs:slab_cobble"},
+ {"stairs:slab_cobble", "stairs:slab_cobble", "stairs:slab_cobble"},
+ {"stairs:slab_cobble", "", "stairs:slab_cobble"},
+ }
+})
+
+-- Cobblestone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:cobblestone_cabinet",
+ recipe = {
+ {"stairs:slab_cobble", "stairs:slab_cobble", "stairs:slab_cobble"},
+ {"doors:trapdoor", "stairs:slab_cobble", "doors:trapdoor"},
+ {"stairs:slab_cobble", "stairs:slab_cobble", "stairs:slab_cobble"},
+ }
+})
+
+-- Cobblestone Chair
+minetest.register_craft({
+ output = "simple_furniture:cobblestone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_cobble", "stairs:slab_cobble", "stairs:slab_cobble"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Cobblestone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:cobblestone_dining_table",
+ recipe = {
+ {"stairs:slab_cobble", "stairs:slab_cobble", "stairs:slab_cobble"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_cobble", ""},
+ }
+})
+
+-- Cobblestone End Table
+minetest.register_craft({
+ output = "simple_furniture:cobblestone_end_table",
+ recipe = {
+ {"stairs:slab_cobble", "stairs:slab_cobble", "stairs:slab_cobble"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Mossy Cobblestone Bench
+minetest.register_craft({
+ output = "simple_furniture:mossy_cobblestone_bench",
+ recipe = {
+ {"", "", "stairs:slab_mossycobble"},
+ {"stairs:slab_mossycobble", "stairs:slab_mossycobble", "stairs:slab_mossycobble"},
+ {"stairs:slab_mossycobble", "", "stairs:slab_mossycobble"},
+ }
+})
+
+-- Mossy Cobblestone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:mossy_cobblestone_cabinet",
+ recipe = {
+ {"stairs:slab_mossycobble", "stairs:slab_mossycobble", "stairs:slab_mossycobble"},
+ {"doors:trapdoor", "stairs:slab_mossycobble", "doors:trapdoor"},
+ {"stairs:slab_mossycobble", "stairs:slab_mossycobble", "stairs:slab_mossycobble"},
+ }
+})
+
+-- Mossy Cobblestone Chair
+minetest.register_craft({
+ output = "simple_furniture:mossy_cobblestone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_mossycobble", "stairs:slab_mossycobble", "stairs:slab_mossycobble"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Mossy Cobblestone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:mossy_cobblestone_dining_table",
+ recipe = {
+ {"stairs:slab_mossycobble", "stairs:slab_mossycobble", "stairs:slab_mossycobble"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_mossycobble", ""},
+ }
+})
+
+-- Mossy Cobblestone End Table
+minetest.register_craft({
+ output = "simple_furniture:mossy_cobblestone_end_table",
+ recipe = {
+ {"stairs:slab_mossycobble", "stairs:slab_mossycobble", "stairs:slab_mossycobble"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Desert Cobblestone Bench
+minetest.register_craft({
+ output = "simple_furniture:desert_cobblestone_bench",
+ recipe = {
+ {"", "", "stairs:slab_desert_cobble"},
+ {"stairs:slab_desert_cobble", "stairs:slab_desert_cobble", "stairs:slab_desert_cobble"},
+ {"stairs:slab_desert_cobble", "", "stairs:slab_desert_cobble"},
+ }
+})
+
+-- Desert Cobblestone Cabinet
+minetest.register_craft({
+ output = "simple_furniture:desert_cobblestone_cabinet",
+ recipe = {
+ {"stairs:slab_desert_cobble", "stairs:slab_desert_cobble", "stairs:slab_desert_cobble"},
+ {"doors:trapdoor", "stairs:slab_desert_cobble", "doors:trapdoor"},
+ {"stairs:slab_desert_cobble", "stairs:slab_desert_cobble", "stairs:slab_desert_cobble"},
+ }
+})
+
+-- Desert Cobblestone Chair
+minetest.register_craft({
+ output = "simple_furniture:desert_cobblestone_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_desert_cobble", "stairs:slab_desert_cobble", "stairs:slab_desert_cobble"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Desert Cobblestone Dining Table
+minetest.register_craft({
+ output = "simple_furniture:desert_cobblestone_dining_table",
+ recipe = {
+ {"stairs:slab_desert_cobble", "stairs:slab_desert_cobble", "stairs:slab_desert_cobble"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_desert_cobble", ""},
+ }
+})
+
+-- Desert Cobblestone End Table
+minetest.register_craft({
+ output = "simple_furniture:desert_cobblestone_end_table",
+ recipe = {
+ {"stairs:slab_desert_cobble", "stairs:slab_desert_cobble", "stairs:slab_desert_cobble"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Brick Bench
+minetest.register_craft({
+ output = "simple_furniture:brick_bench",
+ recipe = {
+ {"", "", "stairs:slab_brick"},
+ {"stairs:slab_brick", "stairs:slab_brick", "stairs:slab_brick"},
+ {"stairs:slab_brick", "", "stairs:slab_brick"},
+ }
+})
+
+-- Brick Cabinet
+minetest.register_craft({
+ output = "simple_furniture:brick_cabinet",
+ recipe = {
+ {"stairs:slab_brick", "stairs:slab_brick", "stairs:slab_brick"},
+ {"doors:trapdoor", "stairs:slab_brick", "doors:trapdoor"},
+ {"stairs:slab_brick", "stairs:slab_brick", "stairs:slab_brick"},
+ }
+})
+
+-- Brick Chair
+minetest.register_craft({
+ output = "simple_furniture:brick_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_brick", "stairs:slab_brick", "stairs:slab_brick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Brick Dining Table
+minetest.register_craft({
+ output = "simple_furniture:brick_dining_table",
+ recipe = {
+ {"stairs:slab_brick", "stairs:slab_brick", "stairs:slab_brick"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_brick", ""},
+ }
+})
+
+-- Brick End Table
+minetest.register_craft({
+ output = "simple_furniture:brick_end_table",
+ recipe = {
+ {"stairs:slab_brick", "stairs:slab_brick", "stairs:slab_brick"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Obsidian Bench
+minetest.register_craft({
+ output = "simple_furniture:obsidian_bench",
+ recipe = {
+ {"", "", "stairs:slab_obsidian"},
+ {"stairs:slab_obsidian", "stairs:slab_obsidian", "stairs:slab_obsidian"},
+ {"stairs:slab_obsidian", "", "stairs:slab_obsidian"},
+ }
+})
+
+-- Obsidian Cabinet
+minetest.register_craft({
+ output = "simple_furniture:obsidian_cabinet",
+ recipe = {
+ {"stairs:slab_obsidian", "stairs:slab_obsidian", "stairs:slab_obsidian"},
+ {"doors:trapdoor", "stairs:slab_obsidian", "doors:trapdoor"},
+ {"stairs:slab_obsidian", "stairs:slab_obsidian", "stairs:slab_obsidian"},
+ }
+})
+
+-- Obsidian Chair
+minetest.register_craft({
+ output = "simple_furniture:obsidian_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_obsidian", "stairs:slab_obsidian", "stairs:slab_obsidian"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Obsidian Dining Table
+minetest.register_craft({
+ output = "simple_furniture:obsidian_dining_table",
+ recipe = {
+ {"stairs:slab_obsidian", "stairs:slab_obsidian", "stairs:slab_obsidian"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_obsidian", ""},
+ }
+})
+
+-- Obsidian End Table
+minetest.register_craft({
+ output = "simple_furniture:obsidian_end_table",
+ recipe = {
+ {"stairs:slab_obsidian", "stairs:slab_obsidian", "stairs:slab_obsidian"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Snow Bench
+minetest.register_craft({
+ output = "simple_furniture:snow_bench",
+ recipe = {
+ {"", "", "stairs:slab_snowblock"},
+ {"stairs:slab_snowblock", "stairs:slab_snowblock", "stairs:slab_snowblock"},
+ {"stairs:slab_snowblock", "", "stairs:slab_snowblock"},
+ }
+})
+
+-- Snow Cabinet
+minetest.register_craft({
+ output = "simple_furniture:snow_cabinet",
+ recipe = {
+ {"stairs:slab_snowblock", "stairs:slab_snowblock", "stairs:slab_snowblock"},
+ {"doors:trapdoor", "stairs:slab_snowblock", "doors:trapdoor"},
+ {"stairs:slab_snowblock", "stairs:slab_snowblock", "stairs:slab_snowblock"},
+ }
+})
+
+-- Snow Chair
+minetest.register_craft({
+ output = "simple_furniture:snow_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_snowblock", "stairs:slab_snowblock", "stairs:slab_snowblock"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Snow Dining Table
+minetest.register_craft({
+ output = "simple_furniture:snow_dining_table",
+ recipe = {
+ {"stairs:slab_snowblock", "stairs:slab_snowblock", "stairs:slab_snowblock"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_snowblock", ""},
+ }
+})
+
+-- Snow End Table
+minetest.register_craft({
+ output = "simple_furniture:snow_end_table",
+ recipe = {
+ {"stairs:slab_snowblock", "stairs:slab_snowblock", "stairs:slab_snowblock"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Ice Bench
+minetest.register_craft({
+ output = "simple_furniture:ice_bench",
+ recipe = {
+ {"", "", "stairs:slab_ice"},
+ {"stairs:slab_ice", "stairs:slab_ice", "stairs:slab_ice"},
+ {"stairs:slab_ice", "", "stairs:slab_ice"},
+ }
+})
+
+-- Ice Cabinet
+minetest.register_craft({
+ output = "simple_furniture:ice_cabinet",
+ recipe = {
+ {"stairs:slab_ice", "stairs:slab_ice", "stairs:slab_ice"},
+ {"doors:trapdoor", "stairs:slab_ice", "doors:trapdoor"},
+ {"stairs:slab_ice", "stairs:slab_ice", "stairs:slab_ice"},
+ }
+})
+
+-- Ice Chair
+minetest.register_craft({
+ output = "simple_furniture:ice_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_ice", "stairs:slab_ice", "stairs:slab_ice"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Ice Dining Table
+minetest.register_craft({
+ output = "simple_furniture:ice_dining_table",
+ recipe = {
+ {"stairs:slab_ice", "stairs:slab_ice", "stairs:slab_ice"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_ice", ""},
+ }
+})
+
+-- Ice End Table
+minetest.register_craft({
+ output = "simple_furniture:ice_end_table",
+ recipe = {
+ {"stairs:slab_ice", "stairs:slab_ice", "stairs:slab_ice"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Straw Bench
+minetest.register_craft({
+ output = "simple_furniture:straw_bench",
+ recipe = {
+ {"", "", "stairs:slab_straw"},
+ {"stairs:slab_straw", "stairs:slab_straw", "stairs:slab_straw"},
+ {"stairs:slab_straw", "", "stairs:slab_straw"},
+ }
+})
+
+-- Straw Cabinet
+minetest.register_craft({
+ output = "simple_furniture:straw_cabinet",
+ recipe = {
+ {"stairs:slab_straw", "stairs:slab_straw", "stairs:slab_straw"},
+ {"doors:trapdoor", "stairs:slab_straw", "doors:trapdoor"},
+ {"stairs:slab_straw", "stairs:slab_straw", "stairs:slab_straw"},
+ }
+})
+
+-- Straw Chair
+minetest.register_craft({
+ output = "simple_furniture:straw_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_straw", "stairs:slab_straw", "stairs:slab_straw"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Straw Dining Table
+minetest.register_craft({
+ output = "simple_furniture:straw_dining_table",
+ recipe = {
+ {"stairs:slab_straw", "stairs:slab_straw", "stairs:slab_straw"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_straw", ""},
+ }
+})
+
+-- Straw End Table
+minetest.register_craft({
+ output = "simple_furniture:straw_end_table",
+ recipe = {
+ {"stairs:slab_straw", "stairs:slab_straw", "stairs:slab_straw"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
\ No newline at end of file
diff --git a/mods/simple_furniture/sf_cropocalypse.lua b/mods/simple_furniture/sf_cropocalypse.lua
new file mode 100644
index 00000000..1941aa75
--- /dev/null
+++ b/mods/simple_furniture/sf_cropocalypse.lua
@@ -0,0 +1,457 @@
+-- simple_furniture/sf_cropocalypse.lua
+
+-- These Are The Nodes For The Benches
+-- Pumpkin Bench
+minetest.register_node("simple_furniture:pumpkin_bench", {
+ description = ("Pumpkin Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "cropocalypse_pumpkin.png"
+ },
+ groups = {sf_pumpkin_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pumpkin_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Watermelon Bench
+minetest.register_node("simple_furniture:watermelon_bench", {
+ description = ("Watermelon Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "cropocalypse_watermelon.png"
+ },
+ groups = {sf_watermelon_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:watermelon_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The Cabinets
+-- Pumpkin Cabinet
+minetest.register_node("simple_furniture:pumpkin_cabinet", {
+ description = ("Pumpkin Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "cropocalypse_pumpkin.png",
+ "cropocalypse_pumpkin.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "cropocalypse_pumpkin.png"
+ },
+ groups = {sf_pumpkin_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pumpkin_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Watermelon Cabinet
+minetest.register_node("simple_furniture:watermelon_cabinet", {
+ description = ("Watermelon Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "cropocalypse_watermelon.png",
+ "cropocalypse_watermelon.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "cropocalypse_watermelon.png"
+ },
+ groups = {sf_watermelon_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:watermelon_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The Chairs
+-- Pumpkin Chair
+minetest.register_node("simple_furniture:pumpkin_chair", {
+ description = ("Pumpkin Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "cropocalypse_pumpkin.png"
+ },
+ groups = {sf_pumpkin_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pumpkin_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Watermelon Chair
+minetest.register_node("simple_furniture:watermelon_chair", {
+ description = ("Watermelon Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "cropocalypse_watermelon.png"
+ },
+ groups = {sf_watermelon_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:watermelon_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The Dining Tables
+-- Pumpkin Dining Table
+minetest.register_node("simple_furniture:pumpkin_dining_table", {
+ description = ("Pumpkin Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "cropocalypse_pumpkin_top.png",
+ "cropocalypse_pumpkin.png",
+ "cropocalypse_pumpkin.png"
+ },
+ groups = {sf_pumpkin_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pumpkin_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Watermelon Dining Table
+minetest.register_node("simple_furniture:watermelon_dining_table", {
+ description = ("Watermelon Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "cropocalypse_watermelon_top_bottom.png",
+ "cropocalypse_watermelon.png",
+ "cropocalypse_watermelon.png"
+ },
+ groups = {sf_watermelon_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:watermelon_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The End Tables
+-- Pumpkin End Table
+minetest.register_node("simple_furniture:pumpkin_end_table", {
+ description = ("Pumpkin End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "cropocalypse_pumpkin_top.png",
+ "cropocalypse_pumpkin.png",
+ "cropocalypse_pumpkin.png"
+ },
+ groups = {sf_pumpkin_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pumpkin_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Watermelon End Table
+minetest.register_node("simple_furniture:watermelon_end_table", {
+ description = ("Watermelon End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "cropocalypse_watermelon_top_bottom.png",
+ "cropocalypse_watermelon.png",
+ "cropocalypse_watermelon.png"
+ },
+ groups = {sf_watermelon_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:watermelon_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Crafting Recipes For The Cropocalypse Furniture
+-- Pumpkin Bench
+minetest.register_craft({
+ output = "simple_furniture:pumpkin_bench",
+ recipe = {
+ {"", "", "cropocalypse:pumpkin"},
+ {"cropocalypse:pumpkin", "cropocalypse:pumpkin", "cropocalypse:pumpkin"},
+ {"cropocalypse:pumpkin", "", "cropocalypse:pumpkin"},
+ }
+})
+
+-- Pumpkin Cabinet
+minetest.register_craft({
+ output = "simple_furniture:pumpkin_cabinet",
+ recipe = {
+ {"cropocalypse:pumpkin", "cropocalypse:pumpkin", "cropocalypse:pumpkin"},
+ {"doors:trapdoor", "cropocalypse:pumpkin", "doors:trapdoor"},
+ {"cropocalypse:pumpkin", "cropocalypse:pumpkin", "cropocalypse:pumpkin"},
+ }
+})
+
+-- Pumpkin Chair
+minetest.register_craft({
+ output = "simple_furniture:pumpkin_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"cropocalypse:pumpkin", "cropocalypse:pumpkin", "cropocalypse:pumpkin"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Pumpkin Dining Table
+minetest.register_craft({
+ output = "simple_furniture:pumpkin_dining_table",
+ recipe = {
+ {"cropocalypse:pumpkin", "cropocalypse:pumpkin", "cropocalypse:pumpkin"},
+ {"", "default:stick", ""},
+ {"", "cropocalypse:pumpkin", ""},
+ }
+})
+
+-- Pumpkin End Table
+minetest.register_craft({
+ output = "simple_furniture:pumpkin_end_table",
+ recipe = {
+ {"cropocalypse:pumpkin", "cropocalypse:pumpkin", "cropocalypse:pumpkin"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Watermelon Bench
+minetest.register_craft({
+ output = "simple_furniture:watermelon_bench",
+ recipe = {
+ {"", "", "cropocalypse:watermelon"},
+ {"cropocalypse:watermelon", "cropocalypse:watermelon", "cropocalypse:watermelon"},
+ {"cropocalypse:watermelon", "", "cropocalypse:watermelon"},
+ }
+})
+
+-- Watermelon Cabinet
+minetest.register_craft({
+ output = "simple_furniture:watermelon_cabinet",
+ recipe = {
+ {"cropocalypse:watermelon", "cropocalypse:watermelon", "cropocalypse:watermelon"},
+ {"doors:trapdoor", "cropocalypse:watermelon", "doors:trapdoor"},
+ {"cropocalypse:watermelon", "cropocalypse:watermelon", "cropocalypse:watermelon"},
+ }
+})
+
+-- Watermelon Chair
+minetest.register_craft({
+ output = "simple_furniture:watermelon_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"cropocalypse:watermelon", "cropocalypse:watermelon", "cropocalypse:watermelon"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Watermelon Dining Table
+minetest.register_craft({
+ output = "simple_furniture:watermelon_dining_table",
+ recipe = {
+ {"cropocalypse:watermelon", "cropocalypse:watermelon", "cropocalypse:watermelon"},
+ {"", "default:stick", ""},
+ {"", "cropocalypse:watermelon", ""},
+ }
+})
+
+-- Watermelon End Table
+minetest.register_craft({
+ output = "simple_furniture:watermelon_end_table",
+ recipe = {
+ {"cropocalypse:watermelon", "cropocalypse:watermelon", "cropocalypse:watermelon"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
\ No newline at end of file
diff --git a/mods/simple_furniture/sf_dining_tables.lua b/mods/simple_furniture/sf_dining_tables.lua
new file mode 100644
index 00000000..f25f9d71
--- /dev/null
+++ b/mods/simple_furniture/sf_dining_tables.lua
@@ -0,0 +1,524 @@
+-- simple_furniture/sf_dining_tables.lua
+
+-- These Are The Nodes For The Dining Tables
+-- Apple Wood Dining Table
+minetest.register_node("simple_furniture:apple_wood_dining_table", {
+ description = ("Apple Wood Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_wood.png"
+ },
+ groups = {sf_apple_wood_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:apple_wood_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Aspen Wood Dining Table
+minetest.register_node("simple_furniture:aspen_wood_dining_table", {
+ description = ("Aspen Wood Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_aspen_wood.png"
+ },
+ groups = {sf_aspen_wood_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:aspen_wood_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Pine Wood Dining Table
+minetest.register_node("simple_furniture:pine_wood_dining_table", {
+ description = ("Pine Wood Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_pine_wood.png"
+ },
+ groups = {sf_pine_wood_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pine_wood_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Jungle Wood Dining Table
+minetest.register_node("simple_furniture:jungle_wood_dining_table", {
+ description = ("Jungle Wood Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_junglewood.png"
+ },
+ groups = {sf_jungle_wood_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:jungle_wood_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Acacia Wood Dining Table
+minetest.register_node("simple_furniture:acacia_wood_dining_table", {
+ description = ("Acacia Wood Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_acacia_wood.png"
+ },
+ groups = {sf_acacia_wood_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:acacia_wood_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Stone Dining Table
+minetest.register_node("simple_furniture:stone_dining_table", {
+ description = ("Stone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_stone.png"
+ },
+ groups = {sf_stone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:stone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Stone Dining Table
+minetest.register_node("simple_furniture:desert_stone_dining_table", {
+ description = ("Desert Stone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_stone.png"
+ },
+ groups = {sf_desert_stone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_stone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Sandstone Dining Table
+minetest.register_node("simple_furniture:sandstone_dining_table", {
+ description = ("Sandstone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_sandstone.png"
+ },
+ groups = {sf_sandstone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:sandstone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Sandstone Dining Table
+minetest.register_node("simple_furniture:desert_sandstone_dining_table", {
+ description = ("Desert Sandstone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_sandstone.png"
+ },
+ groups = {sf_desert_sandstone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_sandstone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Silver Sandstone Dining Table
+minetest.register_node("simple_furniture:silver_sandstone_dining_table", {
+ description = ("Silver Sandstone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_silver_sandstone.png"
+ },
+ groups = {sf_silver_sandstone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:silver_sandstone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Cobblestone Dining Table
+minetest.register_node("simple_furniture:cobblestone_dining_table", {
+ description = ("Cobblestone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_cobble.png"
+ },
+ groups = {sf_cobblestone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:cobblestone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Mossy Cobblestone Dining Table
+minetest.register_node("simple_furniture:mossy_cobblestone_dining_table", {
+ description = ("Mossy Cobblestone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_mossycobble.png"
+ },
+ groups = {sf_mossy_cobblestone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:mossy_cobblestone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Cobblestone Dining Table
+minetest.register_node("simple_furniture:desert_cobblestone_dining_table", {
+ description = ("Desert Cobblestone Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_cobble.png"
+ },
+ groups = {sf_desert_cobblestone_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_cobblestone_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Brick Dining Table
+minetest.register_node("simple_furniture:brick_dining_table", {
+ description = ("Brick Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_brick.png"
+ },
+ groups = {sf_brick_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:brick_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Obsidian Dining Table
+minetest.register_node("simple_furniture:obsidian_dining_table", {
+ description = ("Obsidian Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_obsidian.png"
+ },
+ groups = {sf_obsidian_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:obsidian_dining_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Snow Dining Table
+minetest.register_node("simple_furniture:snow_dining_table", {
+ description = ("Snow Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_snow.png"
+ },
+ groups = {sf_snow_dining_table = 1, dining_table = 1, crumbly = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:snow_dining_table",
+ sounds = default.node_sound_snow_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Ice Dining Table
+minetest.register_node("simple_furniture:ice_dining_table", {
+ description = ("Ice Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_ice.png"
+ },
+ groups = {sf_ice_dining_table = 1, dining_table = 1, cracky = 3, oddly_breakable_by_hand = 2, cools_lava = 1, slippery = 3},
+ drop = "simple_furniture:ice_dining_table",
+ sounds = default.node_sound_ice_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Straw Dining Table
+minetest.register_node("simple_furniture:straw_dining_table", {
+ description = ("Straw Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "farming_straw.png"
+ },
+ groups = {sf_straw_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:straw_dining_table",
+ sounds = default.node_sound_leaves_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
\ No newline at end of file
diff --git a/mods/simple_furniture/sf_dynamic_trees.lua b/mods/simple_furniture/sf_dynamic_trees.lua
new file mode 100644
index 00000000..ddb15d1d
--- /dev/null
+++ b/mods/simple_furniture/sf_dynamic_trees.lua
@@ -0,0 +1,670 @@
+-- simple_furniture/sf_dynamic_trees.lua
+
+-- These Are The Nodes For The Benches
+-- Bamboo Bundle Bench
+minetest.register_node("simple_furniture:bamboo_bundle_bench", {
+ description = ("Bamboo Bundle Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "dt_bamboo_bundle_side.png"
+ },
+ groups = {sf_bamboo_bundle_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_bundle_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Bamboo Planks Bench
+minetest.register_node("simple_furniture:bamboo_planks_bench", {
+ description = ("Bamboo Planks Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "dt_bamboo_planks.png"
+ },
+ groups = {sf_bamboo_planks_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_planks_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Dry Planks Bench
+minetest.register_node("simple_furniture:dry_planks_bench", {
+ description = ("Dry Planks Bench"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.3125, -0.25, -0.0625, -0.25},
+ {-0.375, -0.5, 0.3125, -0.25, -0.0625, 0.25},
+ {0.375, -0.5, -0.3125, 0.25, -0.0625, -0.25},
+ {0.375, -0.5, 0.3125, 0.25, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.5, -0.0625, -0.375, 0.5, 0, 0.375},
+ {-0.5, -0.125, -0.3125, 0.5, -0.0625, -0.4375},
+ {-0.5, -0.125, 0.3125, 0.5, -0.0625, 0.4375},
+ -- This Is The Backrest Post
+ {0.0625, 0, 0.375, -0.0625, 0.375, 0.3125},
+ -- This Is The Backrest
+ {-0.5, 0.1875, 0.3125, 0.5, 0.4375, 0.25},
+ {-0.5, 0.375, 0.375, 0.5, 0.5, 0.3125},
+ }
+ },
+ tiles = {
+ "dt_dry_planks.png"
+ },
+ groups = {sf_dry_planks_bench = 1, bench = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:dry_planks_bench",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The Cabinets
+-- Bamboo Bundle Cabinet
+minetest.register_node("simple_furniture:bamboo_bundle_cabinet", {
+ description = ("Bamboo Bundle Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_bamboo_bundle_side.png",
+ "dt_bamboo_bundle_side.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "dt_bamboo_bundle_side.png"
+ },
+ groups = {sf_bamboo_bundle_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_bundle_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Bamboo Planks Cabinet
+minetest.register_node("simple_furniture:bamboo_planks_cabinet", {
+ description = ("Bamboo Planks Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_bamboo_planks.png",
+ "dt_bamboo_planks.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "dt_bamboo_planks.png"
+ },
+ groups = {sf_bamboo_planks_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_planks_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Dry Planks Cabinet
+minetest.register_node("simple_furniture:dry_planks_cabinet", {
+ description = ("Dry Planks Cabinet"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Base Of The Cabinet
+ {0.5, -0.5, 0.5, -0.5, -0.4375, -0.375},
+ -- These Are The Sides Of The Cabinet
+ {0.5, -0.4375, 0.5, 0.4375, 0.4375, -0.375},
+ {-0.5, -0.4375, 0.5, -0.4375, 0.4375, -0.375},
+ {0.4375, -0.4375, 0.5, -0.4375, 0.4375, 0.4375},
+ {0.4375, -0.4375, -0.3125, -0.4375, 0.4375, -0.375},
+ -- These Are The Doors To The Cabinet
+ {0.4375, -0.4375, -0.375, 0.0625, 0.375, -0.4375},
+ {-0.4375, -0.4375, -0.375, -0.0625, 0.375, -0.4375},
+ -- This Is The Cabinet Top
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_dry_planks.png",
+ "dt_dry_planks.png",
+ "default_chest_top.png",
+ "default_chest_top.png",
+ "default_chest_side.png",
+ "dt_dry_planks.png"
+ },
+ groups = {sf_dry_planks_cabinet = 1, cabinet = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:dry_planks_cabinet",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The Chairs
+-- Bamboo Bundle Chair
+minetest.register_node("simple_furniture:bamboo_bundle_chair", {
+ description = ("Bamboo Bundle Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "dt_bamboo_bundle_side.png"
+ },
+ groups = {sf_bamboo_bundle_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_bundle_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Bamboo Planks Chair
+minetest.register_node("simple_furniture:bamboo_planks_chair", {
+ description = ("Bamboo Planks Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "dt_bamboo_planks.png"
+ },
+ groups = {sf_bamboo_planks_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_planks_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Dry Planks Chair
+minetest.register_node("simple_furniture:dry_planks_chair", {
+ description = ("Dry Planks Chair"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.3125, -0.0625, -0.25},
+ {-0.375, -0.5, 0.375, -0.3125, -0.0625, 0.25},
+ {0.375, -0.5, -0.375, 0.3125, -0.0625, -0.25},
+ {0.375, -0.5, 0.375, 0.3125, -0.0625, 0.25},
+ -- This Is The Seat
+ {-0.375, -0.0625, -0.375, 0.375, 0, 0.375},
+ -- These Are The Backrest Posts
+ {-0.375, 0, 0.375, -0.3125, 0.5, 0.25},
+ {0.375, 0, 0.375, 0.3125, 0.5, 0.25},
+ -- This Is The Backrest
+ {-0.3125, 0.3125, 0.3125, 0.3125, 0.5, 0.25},
+ }
+ },
+ tiles = {
+ "dt_dry_planks.png"
+ },
+ groups = {sf_dry_planks_chair = 1, chair = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:dry_planks_chair",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The Dining Tables
+-- Bamboo Bundle Dining Table
+minetest.register_node("simple_furniture:bamboo_bundle_dining_table", {
+ description = ("Bamboo Bundle Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_bamboo_bundle_side.png"
+ },
+ groups = {sf_bamboo_bundle_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_bundle_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Bamboo Planks Dining Table
+minetest.register_node("simple_furniture:bamboo_planks_dining_table", {
+ description = ("Bamboo Planks Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_bamboo_planks.png"
+ },
+ groups = {sf_bamboo_planks_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_planks_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Dry Planks Dining Table
+minetest.register_node("simple_furniture:dry_planks_dining_table", {
+ description = ("Dry Planks Dining Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- This Is The Pedestal
+ {-0.0625, -0.375, -0.0625, 0.0625, 0.375, 0.0625},
+ {-0.125, -0.4375, -0.125, 0.125, -0.375, 0.125},
+ {-0.1875, -0.5, -0.1875, 0.1875, -0.4375, 0.1875},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_dry_planks.png"
+ },
+ groups = {sf_dry_planks_dining_table = 1, dining_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:dry_planks_dining_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Nodes For The End Tables
+-- Bamboo Bundle End Table
+minetest.register_node("simple_furniture:bamboo_bundle_end_table", {
+ description = ("Bamboo Bundle End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_bamboo_bundle_side.png"
+ },
+ groups = {sf_bamboo_bundle_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_bundle_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Bamboo Planks End Table
+minetest.register_node("simple_furniture:bamboo_planks_end_table", {
+ description = ("Bamboo Planks End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_bamboo_planks.png"
+ },
+ groups = {sf_bamboo_planks_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:bamboo_planks_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Dry Planks End Table
+minetest.register_node("simple_furniture:dry_planks_end_table", {
+ description = ("Dry Planks End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "dt_dry_planks.png"
+ },
+ groups = {sf_dry_planks_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:dry_planks_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- These Are The Crafting Recipes For The Dynamic Trees Furniture
+-- Bamboo Bundle Bench
+minetest.register_craft({
+ output = "simple_furniture:bamboo_bundle_bench",
+ recipe = {
+ {"", "", "stairs:slab_bamboo_bundle"},
+ {"stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle"},
+ {"stairs:slab_bamboo_bundle", "", "stairs:slab_bamboo_bundle"},
+ }
+})
+
+-- Bamboo Bundle Cabinet
+minetest.register_craft({
+ output = "simple_furniture:bamboo_bundle_cabinet",
+ recipe = {
+ {"stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle"},
+ {"doors:trapdoor", "stairs:slab_bamboo_bundle", "doors:trapdoor"},
+ {"stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle"},
+ }
+})
+
+-- Bamboo Bundle Chair
+minetest.register_craft({
+ output = "simple_furniture:bamboo_bundle_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Bamboo Bundle Dining Table
+minetest.register_craft({
+ output = "simple_furniture:bamboo_bundle_dining_table",
+ recipe = {
+ {"stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_bamboo_bundle", ""},
+ }
+})
+
+-- Bamboo Bundle End Table
+minetest.register_craft({
+ output = "simple_furniture:bamboo_bundle_end_table",
+ recipe = {
+ {"stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle", "stairs:slab_bamboo_bundle"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Bamboo Planks Bench
+minetest.register_craft({
+ output = "simple_furniture:bamboo_planks_bench",
+ recipe = {
+ {"", "", "stairs:slab_bamboo_planks"},
+ {"stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks"},
+ {"stairs:slab_bamboo_planks", "", "stairs:slab_bamboo_planks"},
+ }
+})
+
+-- Bamboo Planks Cabinet
+minetest.register_craft({
+ output = "simple_furniture:bamboo_planks_cabinet",
+ recipe = {
+ {"stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks"},
+ {"doors:trapdoor", "stairs:slab_bamboo_planks", "doors:trapdoor"},
+ {"stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks"},
+ }
+})
+
+-- Bamboo Planks Chair
+minetest.register_craft({
+ output = "simple_furniture:bamboo_planks_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Bamboo Planks Dining Table
+minetest.register_craft({
+ output = "simple_furniture:bamboo_planks_dining_table",
+ recipe = {
+ {"stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_bamboo_planks", ""},
+ }
+})
+
+-- Bamboo Planks End Table
+minetest.register_craft({
+ output = "simple_furniture:bamboo_planks_end_table",
+ recipe = {
+ {"stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks", "stairs:slab_bamboo_planks"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Dry Planks Bench
+minetest.register_craft({
+ output = "simple_furniture:dry_planks_bench",
+ recipe = {
+ {"", "", "stairs:slab_dry_planks"},
+ {"stairs:slab_dry_planks", "stairs:slab_dry_planks", "stairs:slab_dry_planks"},
+ {"stairs:slab_dry_planks", "", "stairs:slab_dry_planks"},
+ }
+})
+
+-- Dry Planks Cabinet
+minetest.register_craft({
+ output = "simple_furniture:dry_planks_cabinet",
+ recipe = {
+ {"stairs:slab_dry_planks", "stairs:slab_dry_planks", "stairs:slab_dry_planks"},
+ {"doors:trapdoor", "stairs:slab_dry_planks", "doors:trapdoor"},
+ {"stairs:slab_dry_planks", "stairs:slab_dry_planks", "stairs:slab_dry_planks"},
+ }
+})
+
+-- Dry Planks Chair
+minetest.register_craft({
+ output = "simple_furniture:dry_planks_chair",
+ recipe = {
+ {"", "", "default:stick"},
+ {"stairs:slab_dry_planks", "stairs:slab_dry_planks", "stairs:slab_dry_planks"},
+ {"default:stick", "", "default:stick"},
+ }
+})
+
+-- Dry Planks Dining Table
+minetest.register_craft({
+ output = "simple_furniture:dry_planks_dining_table",
+ recipe = {
+ {"stairs:slab_dry_planks", "stairs:slab_dry_planks", "stairs:slab_dry_planks"},
+ {"", "default:stick", ""},
+ {"", "stairs:slab_dry_planks", ""},
+ }
+})
+
+-- Dry Planks End Table
+minetest.register_craft({
+ output = "simple_furniture:dry_planks_end_table",
+ recipe = {
+ {"stairs:slab_dry_planks", "stairs:slab_dry_planks", "stairs:slab_dry_planks"},
+ {"default:stick", "", "default:stick"},
+ {"default:stick", "", "default:stick"},
+ }
+})
\ No newline at end of file
diff --git a/mods/simple_furniture/sf_end_tables.lua b/mods/simple_furniture/sf_end_tables.lua
new file mode 100644
index 00000000..e134f9f0
--- /dev/null
+++ b/mods/simple_furniture/sf_end_tables.lua
@@ -0,0 +1,596 @@
+-- simple_furniture/sf_end_tables.lua
+
+-- These Are The Nodes For The End Tables
+-- Apple Wood End Table
+minetest.register_node("simple_furniture:apple_wood_end_table", {
+ description = ("Apple Wood End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_wood.png"
+ },
+ groups = {sf_apple_wood_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:apple_wood_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Aspen Wood End Table
+minetest.register_node("simple_furniture:aspen_wood_end_table", {
+ description = ("Aspen Wood End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_aspen_wood.png"
+ },
+ groups = {sf_aspen_wood_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:aspen_wood_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Pine Wood End Table
+minetest.register_node("simple_furniture:pine_wood_end_table", {
+ description = ("Pine Wood End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_pine_wood.png"
+ },
+ groups = {sf_pine_wood_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:pine_wood_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Jungle Wood End Table
+minetest.register_node("simple_furniture:jungle_wood_end_table", {
+ description = ("Jungle Wood End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_junglewood.png"
+ },
+ groups = {sf_jungle_wood_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:jungle_wood_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Acacia Wood End Table
+minetest.register_node("simple_furniture:acacia_wood_end_table", {
+ description = ("Acacia Wood End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_acacia_wood.png"
+ },
+ groups = {sf_acacia_wood_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:acacia_wood_end_table",
+ sounds = default.node_sound_wood_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Stone End Table
+minetest.register_node("simple_furniture:stone_end_table", {
+ description = ("Stone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_stone.png"
+ },
+ groups = {sf_stone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:stone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Stone End Table
+minetest.register_node("simple_furniture:desert_stone_end_table", {
+ description = ("Desert Stone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_stone.png"
+ },
+ groups = {sf_desert_stone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_stone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Sandstone End Table
+minetest.register_node("simple_furniture:sandstone_end_table", {
+ description = ("Sandstone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_sandstone.png"
+ },
+ groups = {sf_sandstone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:sandstone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Sandstone End Table
+minetest.register_node("simple_furniture:desert_sandstone_end_table", {
+ description = ("Desert Sandstone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_sandstone.png"
+ },
+ groups = {sf_desert_sandstone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_sandstone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Silver Sandstone End Table
+minetest.register_node("simple_furniture:silver_sandstone_end_table", {
+ description = ("Silver Sandstone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_silver_sandstone.png"
+ },
+ groups = {sf_silver_sandstone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:silver_sandstone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Cobblestone End Table
+minetest.register_node("simple_furniture:cobblestone_end_table", {
+ description = ("Cobblestone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_cobble.png"
+ },
+ groups = {sf_cobblestone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:cobblestone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Mossy Cobblestone End Table
+minetest.register_node("simple_furniture:mossy_cobblestone_end_table", {
+ description = ("Mossy Cobblestone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_mossycobble.png"
+ },
+ groups = {sf_mossy_cobblestone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:mossy_cobblestone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Desert Cobblestone End Table
+minetest.register_node("simple_furniture:desert_cobblestone_end_table", {
+ description = ("Desert Cobblestone End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_desert_cobble.png"
+ },
+ groups = {sf_desert_cobblestone_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:desert_cobblestone_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Brick End Table
+minetest.register_node("simple_furniture:brick_end_table", {
+ description = ("Brick End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_brick.png"
+ },
+ groups = {sf_brick_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:brick_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Obsidian End Table
+minetest.register_node("simple_furniture:obsidian_end_table", {
+ description = ("Obsidian End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_obsidian.png"
+ },
+ groups = {sf_obsidian_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:obsidian_end_table",
+ sounds = default.node_sound_stone_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Snow End Table
+minetest.register_node("simple_furniture:snow_end_table", {
+ description = ("Snow End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_snow.png"
+ },
+ groups = {sf_snow_end_table = 1, end_table = 1, crumbly = 3, oddly_breakable_by_hand = 2},
+ drop = "simple_furniture:snow_end_table",
+ sounds = default.node_sound_snow_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Ice End Table
+minetest.register_node("simple_furniture:ice_end_table", {
+ description = ("Ice End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "default_ice.png"
+ },
+ groups = {sf_ice_end_table = 1, end_table = 1, cracky = 3, oddly_breakable_by_hand = 2, cools_lava = 1, slippery = 3},
+ drop = "simple_furniture:ice_end_table",
+ sounds = default.node_sound_ice_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
+
+-- Straw End Table
+minetest.register_node("simple_furniture:straw_end_table", {
+ description = ("Straw End Table"),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ -- These Are The Legs
+ {-0.375, -0.5, -0.375, -0.25, 0.375, -0.3125},
+ {-0.375, -0.5, 0.375, -0.25, 0.375, 0.3125},
+ {0.375, -0.5, -0.375, 0.25, 0.375, -0.3125},
+ {0.375, -0.5, 0.375, 0.25, 0.375, 0.3125},
+ -- These Are The Stretchers
+ {-0.3125, -0.25, 0.3125, -0.25, -0.1875, -0.3125},
+ {0.3125, -0.25, 0.3125, 0.25, -0.1875, -0.3125},
+ -- This Is The Table Top
+ {-0.4375, 0.375, -0.4375, 0.4375, 0.4375, 0.4375},
+ {-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5},
+ }
+ },
+ tiles = {
+ "farming_straw.png"
+ },
+ groups = {sf_straw_end_table = 1, end_table = 1, choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ drop = "simple_furniture:straw_end_table",
+ sounds = default.node_sound_leaves_defaults(),
+ paramtype = "light",
+ sunlight_propagates = true,
+ paramtype2 = "facedir",
+ legacy_facedir_simple = true,
+ is_ground_content = false
+})
\ No newline at end of file
diff --git a/mods/stairs/README.md b/mods/stairs/README.md
new file mode 100644
index 00000000..d8b3782f
--- /dev/null
+++ b/mods/stairs/README.md
@@ -0,0 +1,44 @@
+minetest mod Stairs
+=========================
+
+Featured stairs with transparent and glowing stairs, slopes, fuel feature, etc
+
+Information
+-----------
+
+This mod is named `stairs` and its a featured replacement of default one;
+this mod has been amended to add new features like transparent and glowing
+stairs, slopes, recipes to return stairs / slopes back into blocks and also for
+stairs to be used as fuel for furnaces, also alternative placement functions
+that use on_rotate and sneak key.
+
+
+
+Tech information
+----------------
+
+This mod replaces the default stairs mod and is fully api compatible.
+
+#### Dependencies
+
+* default
+
+### Settings
+
+'stairs.show_sides' True by default it uses separate side textures for
+ glass and obsidian glass stairs to look more solid.
+
+License
+-------
+
+### Authors of source code
+
+Originally by Kahrl (LGPLv2.1+) and
+celeron55, Perttu Ahola (LGPLv2.1+)
+Various Minetest developers and contributors (LGPLv2.1+)
+http://www.gnu.org/licenses/lgpl-2.1.html
+
+### License of media (textures and sounds)
+
+Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
+http://creativecommons.org/licenses/by-sa/3.0/
diff --git a/mods/stairs/api.txt b/mods/stairs/api.txt
new file mode 100644
index 00000000..fe8ac6a3
--- /dev/null
+++ b/mods/stairs/api.txt
@@ -0,0 +1,85 @@
+Stairs API
+----------
+
+The stairs API lets you register stairs and slabs and ensures that they are registered the same way as those delivered with Minetest Game, to keep them compatible with other mods.
+
+`stairs.register_stair(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+ * Registers a stair
+ * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_subname"
+ * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
+ * `groups`: See [Known damage and digging time defining groups]
+ * `images`: See [Tile definition]
+ * `description`: Used for the description field in the stair's definition
+ * `sounds`: See [#Default sounds]
+ * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]
+
+
+`stairs.register_slab(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+ * Registers a slab
+ * `subname`: Basically the material name (e.g. cobble) used for the slab name. Nodename pattern: "stairs:slab_subname"
+ * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble"
+ * `groups`: See [Known damage and digging time defining groups]
+ * `images`: See [Tile definition]
+ * `description`: Used for the description field in the slab's definition
+ * `sounds`: See [#Default sounds]
+ * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]
+
+
+`stairs.register_stair_inner(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+ * Registers an inner corner stair
+ * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_inner_subname"
+ * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
+ * `groups`: See [Known damage and digging time defining groups]
+ * `images`: See [Tile definition]
+ * `description`: Used for the description field in the stair's definition with "Inner" prepended
+ * `sounds`: See [#Default sounds]
+ * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]
+
+
+`stairs.register_stair_outer(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+ * Registers an outer corner stair
+ * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_outer_subname"
+ * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
+ * `groups`: See [Known damage and digging time defining groups]
+ * `images`: See [Tile definition]
+ * `description`: Used for the description field in the stair's definition with "Outer" prepended
+ * `sounds`: See [#Default sounds]
+ * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]
+
+
+`stairs.register_slope(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+ * Registers a slope
+ * `subname`: Basically the material name (e.g. cobble) used for the stair name. Nodename pattern: "stairs:stair_outer_subname"
+ * `recipeitem`: Item used in the craft recipe, e.g. "default:cobble", may be `nil`
+ * `groups`: See [Known damage and digging time defining groups]
+ * `images`: See [Tile definition]
+ * `description`: Used for the description field in the stair's definition with "Outer" prepended
+ * `sounds`: See [#Default sounds]
+ * `worldaligntex`: A bool to set all textures world-aligned. Default false. See [Tile definition]
+
+`stairs.register_slope_outer(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+`stairs.register_slope_inner(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+
+`stairs.register_stair_and_slab(subname, recipeitem, groups, images, desc_stair, desc_slab, sounds, worldaligntex)`
+
+ * A wrapper for stairs.register_stair, stairs.register_slab, stairs.register_stair_inner, stairs.register_stair_outer
+ * Uses almost the same arguments as stairs.register_stair
+ * `desc_stair`: Description for stair nodes. For corner stairs 'Inner' or 'Outer' will be prefixed
+ * `desc_slab`: Description for slab node
+
+
+`stairs.register_all(subname, recipeitem, groups, images, description, sounds, worldaligntex)`
+
+ * A wrapper for stairs.register_stair, stairs.register_slab, stairs.register_stair_inner, stairs.register_stair_outer, stairs.register_slope
+ * Uses almost the same arguments as stairs.register_stair
+ * `description`: Description for stair nodes. 'stair' 'slab' 'stair_inner' 'stair_outer' 'Slope' will be prefixed
+
+
+Note: if sounds set to nil then node sounds will be used. Texture alpha, sunglight propagates and light source will be taken from node for each stair registered.
diff --git a/mods/stairs/init.lua b/mods/stairs/init.lua
new file mode 100644
index 00000000..c3ca2c50
--- /dev/null
+++ b/mods/stairs/init.lua
@@ -0,0 +1,646 @@
+stairs = {mod = "redo"}
+
+
+-- cache creative
+local creative = core.settings:get_bool("creative_mode")
+
+local function is_creative_enabled_for(name)
+
+ if creative or core.check_player_privs(name, {creative = true}) then
+ return true
+ end
+
+ return false
+end
+
+
+-- process textures
+local set_textures = function(images, worldaligntex)
+
+ local stair_images = {}
+
+
+ for i, image in ipairs(images) do
+
+ stair_images[i] = type(image) == "string" and {name = image} or table.copy(image)
+
+ if stair_images[i].backface_culling == nil then
+ stair_images[i].backface_culling = true
+ end
+
+ if worldaligntex and stair_images[i].align_style == nil then
+ stair_images[i].align_style = "world"
+ end
+ end
+
+ return stair_images
+end
+
+
+-- placement helper
+local stair_place = function(itemstack, placer, pointed_thing, stair_node)
+
+ -- if sneak pressed then use param2 in node pointed at when placing
+ if placer:is_player() and placer:get_player_control().sneak then
+
+ local name = placer:get_player_name()
+ local pos_a = pointed_thing.above
+ local node_a = core.get_node(pos_a)
+ local def_a = core.registered_nodes[node_a.name]
+
+ if not def_a.buildable_to or core.is_protected(pos_a, name) then
+ return itemstack
+ end
+
+ local pos_u = pointed_thing.under
+ local node_u = core.get_node(pos_u)
+
+ if core.get_item_group(node_u.name, "stair") > 0
+ or core.get_item_group(node_u.name, "slab") > 0 then
+
+ core.set_node(pos_a, {
+ name = stair_node,
+ param2 = node_u.param2
+ })
+
+ if not is_creative_enabled_for(name) then
+ itemstack:take_item()
+ end
+
+ return itemstack
+ end
+ end
+
+ core.rotate_and_place(itemstack, placer, pointed_thing,
+ is_creative_enabled_for(placer:get_player_name()),
+ {invert_wall = placer:get_player_control().sneak})
+
+ return itemstack
+end
+
+
+-- if recipeitem can be burned then stair can also
+local function set_burn(recipeitem, stair_name, v)
+
+ local burntime = core.get_craft_result({
+ method = "fuel", width = 1, items = {recipeitem} }).time
+
+ if burntime > 0 then
+
+ core.register_craft({
+ type = "fuel",
+ recipe = stair_name,
+ burntime = math.floor(burntime * v)
+ })
+ end
+end
+
+
+-- Node will be called stairs:stair_
+function stairs.register_stair(
+ subname, recipeitem, groups, images, description, snds, wat)
+
+ local stair_images = set_textures(images, wat)
+ local new_groups = table.copy(groups)
+
+ new_groups.stair = 1
+
+ local def = core.registered_nodes[recipeitem] or {}
+
+ core.register_node(":stairs:stair_" .. subname, {
+ description = description,
+ drawtype = "nodebox",
+ tiles = stair_images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ use_texture_alpha = def.use_texture_alpha,
+ light_source = def.light_source,
+ sunlight_propagates = def.sunlight_propagates,
+ groups = new_groups,
+ sounds = snds or def.sounds,
+ 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}
+ }
+ },
+
+ on_place = function(itemstack, placer, pointed_thing)
+ return stair_place(
+ itemstack, placer, pointed_thing, "stairs:stair_" .. subname)
+ end
+ })
+
+ -- if no recipe item provided then skip craft recipes
+ if not recipeitem then
+ return
+ end
+
+ -- stair recipes
+ core.register_craft({
+ output = "stairs:stair_" .. subname .. " 8",
+ recipe = {
+ {recipeitem, "", ""},
+ {recipeitem, recipeitem, ""},
+ {recipeitem, recipeitem, recipeitem}
+ }
+ })
+
+ core.register_craft({
+ output = "stairs:stair_" .. subname .. " 8",
+ recipe = {
+ {"", "", recipeitem},
+ {"", recipeitem, recipeitem},
+ {recipeitem, recipeitem, recipeitem}
+ }
+ })
+
+ -- stair to original material recipe
+ core.register_craft({
+ output = recipeitem .. " 3",
+ recipe = {
+ {"stairs:stair_" .. subname, "stairs:stair_" .. subname},
+ {"stairs:stair_" .. subname, "stairs:stair_" .. subname}
+ }
+ })
+
+ set_burn(recipeitem, "stairs:stair_" .. subname, 0.75)
+end
+
+
+-- Node will be called stairs:slab_
+function stairs.register_slab(
+ subname, recipeitem, groups, images, description, snds, wat)
+
+ local slab_images = set_textures(images, wat)
+ local new_groups = table.copy(groups)
+
+ new_groups.slab = 1
+
+ local def = core.registered_nodes[recipeitem] or {}
+
+ core.register_node(":stairs:slab_" .. subname, {
+ description = description,
+ drawtype = "nodebox",
+ tiles = slab_images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ use_texture_alpha = def.use_texture_alpha,
+ light_source = def.light_source,
+ sunlight_propagates = def.sunlight_propagates,
+ groups = new_groups,
+ sounds = snds or def.sounds,
+ node_box = {
+ type = "fixed",
+ fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5}
+ },
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ return stair_place(
+ itemstack, placer, pointed_thing, "stairs:slab_" .. subname)
+ end
+ })
+
+ -- if no recipe item provided then skip craft recipes
+ if not recipeitem then
+ return
+ end
+
+ -- slab recipe
+ core.register_craft({
+ output = "stairs:slab_" .. subname .. " 6",
+ recipe = {
+ {recipeitem, recipeitem, recipeitem}
+ }
+ })
+
+ -- slab to original material recipe
+ core.register_craft({
+ output = recipeitem,
+ recipe = {
+ {"stairs:slab_" .. subname},
+ {"stairs:slab_" .. subname}
+ }
+ })
+
+ set_burn(recipeitem, "stairs:slab_" .. subname, 0.5)
+end
+
+
+-- Node will be called stairs:stair_outer_
+function stairs.register_stair_outer(
+ subname, recipeitem, groups, images, description, snds, wat, fdesc)
+
+ local stair_images = set_textures(images, wat)
+ local new_groups = table.copy(groups)
+
+ new_groups.stair = 1
+
+ local def = core.registered_nodes[recipeitem] or {}
+
+ core.register_node(":stairs:stair_outer_" .. subname, {
+ description = fdesc or "Outer " .. description,
+ drawtype = "nodebox",
+ tiles = stair_images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ use_texture_alpha = def.use_texture_alpha,
+ light_source = def.light_source,
+ sunlight_propagates = def.sunlight_propagates,
+ groups = new_groups,
+ sounds = snds or def.sounds,
+ 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}
+ },
+ },
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ return stair_place(
+ itemstack, placer, pointed_thing, "stairs:stair_outer_" .. subname)
+ end
+ })
+
+ -- add alias for old stairs redo name
+ core.register_alias("stairs:corner_" .. subname,
+ "stairs:stair_outer_" .. subname)
+
+ -- if no recipe item provided then skip craft recipes
+ if not recipeitem then
+ return
+ end
+
+ -- corner stair recipe
+ core.register_craft({
+ output = "stairs:stair_outer_" .. subname .. " 6",
+ recipe = {
+ {"", "", ""},
+ {"", recipeitem, ""},
+ {recipeitem, recipeitem, recipeitem}
+ },
+ })
+
+ -- corner stair to original material recipe
+ core.register_craft({
+ output = recipeitem .. " 2",
+ recipe = {
+ {"stairs:stair_outer_" .. subname, "stairs:stair_outer_" .. subname},
+ {"stairs:stair_outer_" .. subname, "stairs:stair_outer_" .. subname}
+ }
+ })
+
+ set_burn(recipeitem, "stairs:stair_outer_" .. subname, 0.625)
+end
+
+
+-- Node will be called stairs:stair_inner_
+function stairs.register_stair_inner(
+ subname, recipeitem, groups, images, description, snds, wat, fdesc)
+
+ local stair_images = set_textures(images, wat)
+ local new_groups = table.copy(groups)
+
+ new_groups.stair = 1
+
+ local def = core.registered_nodes[recipeitem] or {}
+
+ core.register_node(":stairs:stair_inner_" .. subname, {
+ description = fdesc or "Inner " .. description,
+ drawtype = "nodebox",
+ tiles = stair_images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ use_texture_alpha = def.use_texture_alpha,
+ light_source = def.light_source,
+ sunlight_propagates = def.sunlight_propagates,
+ groups = new_groups,
+ sounds = snds or def.sounds,
+ 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}
+ }
+ },
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ return stair_place(
+ itemstack, placer, pointed_thing, "stairs:stair_inner_" .. subname)
+ end,
+ })
+
+ -- add alias for old stairs redo name
+ core.register_alias("stairs:invcorner_" .. subname,
+ "stairs:stair_inner_" .. subname)
+
+ -- if no recipe item provided then skip craft recipes
+ if not recipeitem then
+ return
+ end
+
+ -- inside corner stair recipe
+ core.register_craft({
+ output = "stairs:stair_inner_" .. subname .. " 9",
+ recipe = {
+ {recipeitem, recipeitem, ""},
+ {recipeitem, recipeitem, recipeitem},
+ {recipeitem, recipeitem, recipeitem},
+ }
+ })
+
+ -- inside corner stair to original material recipe
+ core.register_craft({
+ output = recipeitem .. " 3",
+ recipe = {
+ {"stairs:stair_inner_" .. subname, "stairs:stair_inner_" .. subname},
+ {"stairs:stair_inner_" .. subname, "stairs:stair_inner_" .. subname}
+ }
+ })
+
+ set_burn(recipeitem, "stairs:stair_inner_" .. subname, 0.875)
+end
+
+
+-- Node will be called stairs:slope_
+function stairs.register_slope(
+ subname, recipeitem, groups, images, description, snds, wat)
+
+ local stair_images = set_textures(images, wat)
+ local new_groups = table.copy(groups)
+
+ new_groups.stair = 1
+
+ local def = core.registered_nodes[recipeitem] or {}
+
+ core.register_node(":stairs:slope_" .. subname, {
+ description = description,
+ drawtype = "mesh",
+ mesh = "stairs_slope.obj",
+ tiles = stair_images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ use_texture_alpha = def.use_texture_alpha,
+ light_source = def.light_source,
+ sunlight_propagates = def.sunlight_propagates,
+ groups = new_groups,
+ sounds = snds or def.sounds,
+ selection_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},
+ },
+ },
+ collision_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}
+ },
+ },
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ return stair_place(
+ itemstack, placer, pointed_thing, "stairs:slope_" .. subname)
+ end
+ })
+
+ -- slope recipe
+ core.register_craft({
+ output = "stairs:slope_" .. subname .. " 6",
+ recipe = {
+ {recipeitem, "", ""},
+ {recipeitem, recipeitem, ""}
+ }
+ })
+
+ -- slope to original material recipe
+ core.register_craft({
+ output = recipeitem,
+ recipe = {
+ {"stairs:slope_" .. subname, "stairs:slope_" .. subname}
+ }
+ })
+
+ set_burn(recipeitem, "stairs:slope_" .. subname, 0.5)
+end
+
+
+-- Node will be called stairs:slope_inner_
+function stairs.register_slope_inner(
+ subname, recipeitem, groups, images, description, snds, wat)
+
+ local stair_images = set_textures(images, wat)
+ local new_groups = table.copy(groups)
+
+ new_groups.stair = 1
+
+ local def = core.registered_nodes[recipeitem] or {}
+
+ core.register_node(":stairs:slope_inner_" .. subname, {
+ description = description,
+ drawtype = "mesh",
+ mesh = "stairs_slope_inner.obj",
+ tiles = stair_images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ use_texture_alpha = def.use_texture_alpha,
+ light_source = def.light_source,
+ sunlight_propagates = def.sunlight_propagates,
+ groups = new_groups,
+ sounds = snds or def.sounds,
+ selection_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}
+ }
+ },
+ collision_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}
+ }
+ },
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ return stair_place(
+ itemstack, placer, pointed_thing, "stairs:slope_inner_" .. subname)
+ end
+ })
+
+ -- slope recipe
+ core.register_craft({
+ output = "stairs:slope_inner_" .. subname .. " 6",
+ recipe = {
+ {"", recipeitem, recipeitem},
+ {recipeitem, recipeitem, recipeitem}
+ }
+ })
+
+ -- slope to original material recipe
+ core.register_craft({
+ output = recipeitem,
+ recipe = {
+ {"stairs:slope_inner_" .. subname, "stairs:slope_inner_" .. subname}
+ }
+ })
+
+ set_burn(recipeitem, "stairs:slope_inner_" .. subname, 0.5)
+end
+
+
+-- Node will be called stairs:slope_outer_
+function stairs.register_slope_outer(
+ subname, recipeitem, groups, images, description, snds, wat)
+
+ local stair_images = set_textures(images, wat)
+ local new_groups = table.copy(groups)
+
+ new_groups.stair = 1
+
+ local def = core.registered_nodes[recipeitem] or {}
+
+ core.register_node(":stairs:slope_outer_" .. subname, {
+ description = description,
+ drawtype = "mesh",
+ mesh = "stairs_slope_outer.obj",
+ tiles = stair_images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = false,
+ use_texture_alpha = def.use_texture_alpha,
+ light_source = def.light_source,
+ sunlight_propagates = def.sunlight_propagates,
+ groups = new_groups,
+ sounds = snds or def.sounds,
+ selection_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+ {-0.5, 0, 0, 0, 0.5, 0.5}
+ },
+ },
+ collision_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+ {-0.5, 0, 0, 0, 0.5, 0.5}
+ },
+ },
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ return stair_place(
+ itemstack, placer, pointed_thing, "stairs:slope_outer_" .. subname)
+ end
+ })
+
+ -- slope recipe
+ core.register_craft({
+ output = "stairs:slope_outer_" .. subname .. " 6",
+ recipe = {
+ {"", "", recipeitem},
+ {"", recipeitem, recipeitem}
+ }
+ })
+
+ -- slope to original material recipe
+ core.register_craft({
+ output = recipeitem,
+ recipe = {
+ {"stairs:slope_outer_" .. subname, "stairs:slope_outer_" .. subname}
+ }
+ })
+
+ set_burn(recipeitem, "stairs:slope_outer_" .. subname, 0.5)
+end
+
+
+-- Nodes will be called stairs:{stair,slab}_
+function stairs.register_stair_and_slab(
+ subname, recipeitem, groups, images, desc_stair, desc_slab, sounds, wat)
+
+ stairs.register_stair(
+ subname, recipeitem, groups, images, desc_stair, sounds, wat)
+
+ stairs.register_stair_inner(
+ subname, recipeitem, groups, images, desc_stair, sounds, wat)
+
+ stairs.register_stair_outer(
+ subname, recipeitem, groups, images, desc_stair, sounds, wat)
+
+ stairs.register_slab(
+ subname, recipeitem, groups, images, desc_slab, sounds, wat)
+end
+
+
+-- Nodes will be called stairs:{stair,slab,corner,invcorner,slope}_
+function stairs.register_all(
+ subname, recipeitem, groups, images, desc, snds, wat)
+
+ stairs.register_stair(
+ subname, recipeitem, groups, images, desc .. " Stair", snds, wat)
+
+ stairs.register_slab(
+ subname, recipeitem, groups, images, desc .. " Slab", snds, wat)
+
+ stairs.register_stair_inner(
+ subname, recipeitem, groups, images, desc .. " Stair", snds, wat)
+
+ stairs.register_stair_outer(
+ subname, recipeitem, groups, images, desc .. " Stair", snds, wat)
+
+ stairs.register_slope(
+ subname, recipeitem, groups, images, desc .. " Slope", snds, wat)
+
+ stairs.register_slope_inner(
+ subname, recipeitem, groups, images, desc .. " Slope", snds, wat)
+
+ stairs.register_slope_outer(
+ subname, recipeitem, groups, images, desc .. " Slope", snds, wat)
+end
+
+
+-- compatibility function for previous stairs:invcorner_
+function stairs.register_invcorner(
+ subname, recipeitem, groups, images, desc, snds, wat)
+
+ stairs.register_stair_inner(
+ subname, recipeitem, groups, images, desc .. " Stair", snds, wat)
+end
+
+
+-- compatibility function for previous stairs:corner_
+function stairs.register_corner(
+ subname, recipeitem, groups, images, desc, snds, wat)
+
+ stairs.register_stair_outer(
+ subname, recipeitem, groups, images, desc .. " Stair", snds, wat)
+end
+
+
+-- Register stairs after mods loaded
+core.register_on_mods_loaded(function()
+ dofile(core.get_modpath("stairs") .. "/stairs.lua")
+end)
+
+print ("[MOD] Stairs Redo loaded")
diff --git a/mods/stairs/license.txt b/mods/stairs/license.txt
new file mode 100644
index 00000000..19ed680b
--- /dev/null
+++ b/mods/stairs/license.txt
@@ -0,0 +1,31 @@
+The MIT License (MIT)
+
+Copyright (c) 2022 TenPlus1
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+Textures
+--------
+
+ Derived from a texture by PilzAdam (CC BY-SA 3.0):
+ stairs_obsidian_glass_quarter.png
+
+ Derived from a texture by celeron55 (CC BY-SA 3.0):
+ stairs_glass_quarter.png
diff --git a/mods/stairs/locale/stairs.de.tr b/mods/stairs/locale/stairs.de.tr
new file mode 100644
index 00000000..beb45798
--- /dev/null
+++ b/mods/stairs/locale/stairs.de.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Glastreppe
+Glass Slab=Glasplatte
+Inner Glass Stair=Innere Glastreppe
+Outer Glass Stair=Äußere Glastreppe
+Obsidian Glass Stair=Obsidianglastreppe
+Obsidian Glass Slab=Obsidianglasplatte
+Inner Obsidian Glass Stair=Innere Obsidianglastreppe
+Outer Obsidian Glass Stair=Äußere Obsidianglastreppe
+Wooden Stair=Holztreppe
+Inner Wooden Stair=Innere Holztreppe
+Outer Wooden Stair=Äußere Holztreppe
+Wooden Slab=Holzplatte
+Jungle Wood Stair=Dschungelholztreppe
+Inner Jungle Wood Stair=Innere Dschungelholztreppe
+Outer Jungle Wood Stair=Äußere Dschungelholztreppe
+Jungle Wood Slab=Dschungelholzplatte
+Pine Wood Stair=Kiefernholztreppe
+Inner Pine Wood Stair=Innere Kiefernholztreppe
+Outer Pine Wood Stair=Äußere Kiefernholztreppe
+Pine Wood Slab=Kiefernholzplatte
+Acacia Wood Stair=Akazienholztreppe
+Inner Acacia Wood Stair=Innere Akazienholztreppe
+Outer Acacia Wood Stair=Äußere Akazienholztreppe
+Acacia Wood Slab=Akazienholzplatte
+Aspen Wood Stair=Espenholztreppe
+Inner Aspen Wood Stair=Innere Espenholztreppe
+Outer Aspen Wood Stair=Äußere Espenholztreppe
+Aspen Wood Slab=Espenholzplatte
+Stone Stair=Steintreppe
+Inner Stone Stair=Innere Steintreppe
+Outer Stone Stair=Äußere Steintreppe
+Stone Slab=Steinplatte
+Cobblestone Stair=Kopfsteinpflastertreppe
+Inner Cobblestone Stair=Innere Kopfsteinpflastertreppe
+Outer Cobblestone Stair=Äußere Kopfsteinpflastertreppe
+Cobblestone Slab=Kopfsteinpflasterplatte
+Mossy Cobblestone Stair=Moosige Kopfsteinpflastertreppe
+Inner Mossy Cobblestone Stair=Innere moosige Kopfsteinpflastertreppe
+Outer Mossy Cobblestone Stair=Äußere moosige Kopfsteinpflastertreppe
+Mossy Cobblestone Slab=Moosige Kopfsteinpflasterplatte
+Stone Brick Stair=Steinziegeltreppe
+Inner Stone Brick Stair=Innere Steinziegeltreppe
+Outer Stone Brick Stair=Äußere Steinziegeltreppe
+Stone Brick Slab=Steinziegelplatte
+Stone Block Stair=Steinblocktreppe
+Inner Stone Block Stair=Innere Steinblocktreppe
+Outer Stone Block Stair=Äußere Steinblocktreppe
+Stone Block Slab=Steinblockplatte
+Desert Stone Stair=Wüstensteintreppe
+Inner Desert Stone Stair=Innere Wüstensteintreppe
+Outer Desert Stone Stair=Äußere Wüstensteintreppe
+Desert Stone Slab=Wüstensteinplatte
+Desert Cobblestone Stair=Wüstenkopfsteinpflastertreppe
+Inner Desert Cobblestone Stair=Innere Wüstenkopfsteinpflastertreppe
+Outer Desert Cobblestone Stair=Äußere Wüstenkopfsteinpflastertreppe
+Desert Cobblestone Slab=Wüstenkopfsteinpflasterplatte
+Desert Stone Brick Stair=Wüstensteinziegeltreppe
+Inner Desert Stone Brick Stair=Innere Wüstensteinziegeltreppe
+Outer Desert Stone Brick Stair=Äußere Wüstensteinziegeltreppe
+Desert Stone Brick Slab=Wüstensteinziegelplatte
+Desert Stone Block Stair=Wüstensteinblocktreppe
+Inner Desert Stone Block Stair=Innere Wüstensteinblocktreppe
+Outer Desert Stone Block Stair=Äußere Wüstensteinblocktreppe
+Desert Stone Block Slab=Wüstensteinblockplatte
+Sandstone Stair=Sandsteintreppe
+Inner Sandstone Stair=Innere Sandsteintreppe
+Outer Sandstone Stair=Äußere Sandsteintreppe
+Sandstone Slab=Sandsteinplatte
+Sandstone Brick Stair=Sandsteinziegeltreppe
+Inner Sandstone Brick Stair=Innere Sandsteinziegeltreppe
+Outer Sandstone Brick Stair=Äußere Sandsteinziegeltreppe
+Sandstone Brick Slab=Sandsteinziegelplatte
+Sandstone Block Stair=Sandsteinblocktreppe
+Inner Sandstone Block Stair=Innere Sandsteinblocktreppe
+Outer Sandstone Block Stair=Äußere Sandsteinblocktreppe
+Sandstone Block Slab=Sandsteinblockplatte
+Desert Sandstone Stair=Wüstensandsteintreppe
+Inner Desert Sandstone Stair=Innere Wüstensandsteintreppe
+Outer Desert Sandstone Stair=Äußere Wüstensandsteintreppe
+Desert Sandstone Slab=Wüstensandsteinplatte
+Desert Sandstone Brick Stair=Wüstensandsteinziegeltreppe
+Inner Desert Sandstone Brick Stair=Innere Wüstensandsteinziegeltreppe
+Outer Desert Sandstone Brick Stair=Äußere Wüstensandsteinziegeltreppe
+Desert Sandstone Brick Slab=Wüstensandsteinziegelplatte
+Desert Sandstone Block Stair=Wüstensandsteinblocktreppe
+Inner Desert Sandstone Block Stair=Innere Wüstensandsteinblocktreppe
+Outer Desert Sandstone Block Stair=Äußere Wüstensandsteinblocktreppe
+Desert Sandstone Block Slab=Wüstensandsteinblockplatte
+Silver Sandstone Stair=Silbersandsteintreppe
+Inner Silver Sandstone Stair=Innere Silbersandsteintreppe
+Outer Silver Sandstone Stair=Äußere Silbersandsteintreppe
+Silver Sandstone Slab=Silbersandsteinplatte
+Silver Sandstone Brick Stair=Silbersandsteinziegeltreppe
+Inner Silver Sandstone Brick Stair=Innere Silbersandsteinziegeltreppe
+Outer Silver Sandstone Brick Stair=Äußere Silbersandsteinziegeltreppe
+Silver Sandstone Brick Slab=Silbersandsteinziegelplatte
+Silver Sandstone Block Stair=Silbersandsteinblocktreppe
+Inner Silver Sandstone Block Stair=Innere Silbersandsteinblocktreppe
+Outer Silver Sandstone Block Stair=Äußere Silbersandsteinblocktreppe
+Silver Sandstone Block Slab=Silbersandsteinblockplatte
+Obsidian Stair=Obsidiantreppe
+Inner Obsidian Stair=Innere Obsidiantreppe
+Outer Obsidian Stair=Äußere Obsidiantreppe
+Obsidian Slab=Obsidianplatte
+Obsidian Brick Stair=Obsidianziegeltreppe
+Inner Obsidian Brick Stair=Innere Obsidianziegeltreppe
+Outer Obsidian Brick Stair=Äußere Obsidianziegeltreppe
+Obsidian Brick Slab=Obsidianziegelplatte
+Obsidian Block Stair=Obsidianblocktreppe
+Inner Obsidian Block Stair=Innere Obsidianblocktreppe
+Outer Obsidian Block Stair=Äußere Obsidianblocktreppe
+Obsidian Block Slab=Obsidianblockplatte
+Brick Stair=Ziegeltreppe
+Inner Brick Stair=Innere Ziegeltreppe
+Outer Brick Stair=Äußere Ziegeltreppe
+Brick Slab=Ziegelplatte
+Steel Block Stair=Stahlblocktreppe
+Inner Steel Block Stair=Innere Stahlblocktreppe
+Outer Steel Block Stair=Äußere Stahlblocktreppe
+Steel Block Slab=Stahlblockplatte
+Tin Block Stair=Zinnblocktreppe
+Inner Tin Block Stair=Innere Zinnblocktreppe
+Outer Tin Block Stair=Äußere Zinnblocktreppe
+Tin Block Slab=Zinnblockplatte
+Copper Block Stair=Kupferblocktreppe
+Inner Copper Block Stair=Innere Kupferblocktreppe
+Outer Copper Block Stair=Äußere Kupferblocktreppe
+Copper Block Slab=Kupferblockplatte
+Bronze Block Stair=Bronzeblocktreppe
+Inner Bronze Block Stair=Innere Bronzeblocktreppe
+Outer Bronze Block Stair=Äußere Bronzeblocktreppe
+Bronze Block Slab=Bronzeblockplatte
+Gold Block Stair=Goldblocktreppe
+Inner Gold Block Stair=Innere Goldblocktreppe
+Outer Gold Block Stair=Äußere Goldblocktreppe
+Gold Block Slab=Goldblockplatte
+Ice Stair=Eistreppe
+Inner Ice Stair=Innere Eistreppe
+Outer Ice Stair=Äußere Eistreppe
+Ice Slab=Eisplatte
+Snow Block Stair=Schneeblocktreppe
+Inner Snow Block Stair=Innere Schneeblocktreppe
+Outer Snow Block Stair=Äußere Schneeblocktreppe
+Snow Block Slab=Schneeblockplatte
diff --git a/mods/stairs/locale/stairs.eo.tr b/mods/stairs/locale/stairs.eo.tr
new file mode 100644
index 00000000..078cc9cc
--- /dev/null
+++ b/mods/stairs/locale/stairs.eo.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Vitra Ŝtupo
+Glass Slab=Vitra Plato
+Inner Glass Stair=Interna Vitra Ŝtupo
+Outer Glass Stair=Ekstera Vitra Ŝtupo
+Obsidian Glass Stair=Obsidiana Vitra Ŝtupo
+Obsidian Glass Slab=Obsidiana Vitra Plato
+Inner Obsidian Glass Stair=Interna Obsidiana Vitra Ŝtupo
+Outer Obsidian Glass Stair=Ekstera Obsidiana Vitra Ŝtupo
+Wooden Stair=Ligna Ŝtupo
+Inner Wooden Stair=Interna Ligna Ŝtupo
+Outer Wooden Stair=Ekstera Ligna Ŝtupo
+Wooden Slab=Ligna Plato
+Jungle Wood Stair=Ĝangala Ligna Ŝtupo
+Inner Jungle Wood Stair=Interna Ĝangala Ligna Ŝtupo
+Outer Jungle Wood Stair=Ekstera Ĝangala Ligna Ŝtupo
+Jungle Wood Slab=Ĝangala Ligna Plato
+Pine Wood Stair=Pina Ligna Ŝtupo
+Inner Pine Wood Stair=Interna Pina Ligna Ŝtupo
+Outer Pine Wood Stair=Ekstera Pina Ligna Ŝtupo
+Pine Wood Slab=Pina Ligna Plato
+Acacia Wood Stair=Akacia Ligna Ŝtupo
+Inner Acacia Wood Stair=Interna Akacia Ligna Ŝtupo
+Outer Acacia Wood Stair=Ekstera Akacia Ligna Ŝtupo
+Acacia Wood Slab=Akacia Ligna Plato
+Aspen Wood Stair=Tremola Ligna Ŝtupo
+Inner Aspen Wood Stair=Interna Tremola Ligna Ŝtupo
+Outer Aspen Wood Stair=Ekstera Tremola Ligna Ŝtupo
+Aspen Wood Slab=Tremola Ligna Plato
+Stone Stair=Ŝtona Ŝtupo
+Inner Stone Stair=Interna Ŝtona Ŝtupo
+Outer Stone Stair=Ekstera Ŝtona Ŝtupo
+Stone Slab=Ŝtona Plato
+Cobblestone Stair=Pavimŝtona Ŝtupo
+Inner Cobblestone Stair=Interna Pavimŝtona Ŝtupo
+Outer Cobblestone Stair=Ekstera Pavimŝtona Ŝtupo
+Cobblestone Slab=Pavimŝtona Plato
+Mossy Cobblestone Stair=Muska Pavimŝtona Ŝtupo
+Inner Mossy Cobblestone Stair=Interna Muska Pavimŝtona Ŝtupo
+Outer Mossy Cobblestone Stair=Ekstera Muska Pavimŝtona Ŝtupo
+Mossy Cobblestone Slab=Muska Pavimŝtona Plato
+Stone Brick Stair=Ŝtona Brika Ŝtupo
+Inner Stone Brick Stair=Interna Ŝtona Brika Ŝtupo
+Outer Stone Brick Stair=Ekstera Ŝtona Brika Ŝtupo
+Stone Brick Slab=Ŝtona Brika Plato
+Stone Block Stair=Ŝtona Ŝtipa Ŝtupo
+Inner Stone Block Stair=Interna Ŝtona Ŝtipa Ŝtupo
+Outer Stone Block Stair=Ekstera Ŝtona Ŝtipa Ŝtupo
+Stone Block Slab=Ŝtona Ŝtipa Plato
+Desert Stone Stair=Dezerta Ŝtona Ŝtupo
+Inner Desert Stone Stair=Interna Dezerta Ŝtona Ŝtupo
+Outer Desert Stone Stair=Ekstera Dezerta Ŝtona Ŝtupo
+Desert Stone Slab=Dezerta Ŝtona Plato
+Desert Cobblestone Stair=Dezerta Pavimŝtona Ŝtupo
+Inner Desert Cobblestone Stair=Interna Dezerta Pavimŝtona Ŝtupo
+Outer Desert Cobblestone Stair=Ekstera Dezerta Pavimŝtona Ŝtupo
+Desert Cobblestone Slab=Dezerta Pavimŝtona Plato
+Desert Stone Brick Stair=Dezerta Ŝtona Brika Ŝtupo
+Inner Desert Stone Brick Stair=Interna Dezerta Ŝtona Brika Ŝtupo
+Outer Desert Stone Brick Stair=Ekstera Dezerta Ŝtona Brika Ŝtupo
+Desert Stone Brick Slab=Dezerta Ŝtona Brika Plato
+Desert Stone Block Stair=Dezerta Ŝtona Bloko Ŝtupo
+Inner Desert Stone Block Stair=Interna Dezerta Ŝtona Bloko Stupo
+Outer Desert Stone Block Stair=Ekstera Dezerta Ŝtona Bloko Ŝtupo
+Desert Stone Block Slab=Dezerta Ŝtona Bloko Plato
+Sandstone Stair=Sablŝtona Ŝtupo
+Inner Sandstone Stair=Interna Ŝablŝtona Ŝtupo
+Outer Sandstone Stair=Ekstera Ŝablŝtona Ŝtupo
+Sandstone Slab=Ŝablŝtona Plato
+Sandstone Brick Stair=Ŝablŝtona Brika Ŝtupo
+Inner Sandstone Brick Stair=Interna Ŝablŝtona Brika Ŝtupo
+Outer Sandstone Brick Stair=Ekstera Ŝablŝtona Brika Ŝtupo
+Sandstone Brick Slab=Ŝablŝtona Brika Plato
+Sandstone Block Stair=Ŝablŝtona Ŝtipa Ŝtupo
+Inner Sandstone Block Stair=Interna Ŝablŝtona Ŝtipa Ŝtupo
+Outer Sandstone Block Stair=Ekstera Ŝablŝtona Ŝtipa Ŝtupo
+Sandstone Block Slab=Ŝablŝtona Ŝtipa Plato
+Desert Sandstone Stair=Dezerta Ŝablŝtona Ŝtupo
+Inner Desert Sandstone Stair=Interna Dezerta Ŝablŝtona Ŝtupo
+Outer Desert Sandstone Stair=Ekstera Dezerta Ŝablŝtona Ŝtupo
+Desert Sandstone Slab=Dezerta Ŝablŝtona Plato
+Desert Sandstone Brick Stair=Dezerta Ŝablŝtona Brika Ŝtupo
+Inner Desert Sandstone Brick Stair=Interna Dezerta Ŝablŝtona Brika Ŝtupo
+Outer Desert Sandstone Brick Stair=Ekstera Dezerta Ŝablŝtona Brika Ŝtupo
+Desert Sandstone Brick Slab=Dezerta Ŝablŝtona Brika Plato
+Desert Sandstone Block Stair=Dezerta Ŝablŝtona Ŝtipa Ŝtupo
+Inner Desert Sandstone Block Stair=Interna Dezerta Ŝablŝtona Brika Ŝtupo
+Outer Desert Sandstone Block Stair=Ekstera Dezerta Ŝablŝtona Brika Ŝtupo
+Desert Sandstone Block Slab=Dezerta Ŝablŝtona Ŝtipa Plato
+Silver Sandstone Stair=Arĝenta Ŝablŝtona Ŝtupo
+Inner Silver Sandstone Stair=Interna Arĝenta Ŝablŝtona Ŝtupo
+Outer Silver Sandstone Stair=Ekstera Arĝenta Ŝablŝtona Ŝtupo
+Silver Sandstone Slab=Arĝenta Ŝablŝtona Plato
+Silver Sandstone Brick Stair=Arĝenta Ŝablŝtona Brika Ŝtupo
+Inner Silver Sandstone Brick Stair=Interna Arĝenta Ŝablŝtona Brika Ŝtupo
+Outer Silver Sandstone Brick Stair=Ekstera Arĝenta Ŝablŝtona Brika Ŝtupo
+Silver Sandstone Brick Slab=Arĝenta Ŝablŝtona Brika Plato
+Silver Sandstone Block Stair=Arĝenta Ŝablŝtona Ŝtipa Ŝtupo
+Inner Silver Sandstone Block Stair=Interna Arĝenta Ŝablŝtona Ŝtipa Ŝtupo
+Outer Silver Sandstone Block Stair=Ekstera Arĝenta Ŝablŝtona Ŝtipa Ŝtupo
+Silver Sandstone Block Slab=Arĝenta Ŝablŝtona Ŝtipa Plato
+Obsidian Stair=Obsidiana Ŝtupo
+Inner Obsidian Stair=Interna Obsidiana Ŝtupo
+Outer Obsidian Stair=Ekstera Obsidiana Ŝtupo
+Obsidian Slab=Obsidiana Plato
+Obsidian Brick Stair=Obsidiana Brika Ŝtupo
+Inner Obsidian Brick Stair=Interna Obsidiana Brika Ŝtupo
+Outer Obsidian Brick Stair=Ekstera Obsidiana Brika Ŝtupo
+Obsidian Brick Slab=Obsidiana Brika Plato
+Obsidian Block Stair=Obsidiana Ŝtipa Ŝtupo
+Inner Obsidian Block Stair=Interna Obsidiana Ŝtipa Ŝtupo
+Outer Obsidian Block Stair=Ekstera Obsidiana Ŝtipa Ŝtupo
+Obsidian Block Slab=Obsidiana Ŝtipa Plato
+Brick Stair=Brika Ŝtupo
+Inner Brick Stair=Interna Brika Ŝtupo
+Outer Brick Stair=Ekstera Brika Ŝtupo
+Brick Slab=Brika Plato
+Steel Block Stair=Ŝtala Ŝtipa Ŝtupo
+Inner Steel Block Stair=Interna Ŝtala Ŝtipa Ŝtupo
+Outer Steel Block Stair=Ekstera Ŝtala Ŝtipa Ŝtupo
+Steel Block Slab=Ŝtala Ŝtipa Plato
+Tin Block Stair=Stana Ŝtipa Ŝtupo
+Inner Tin Block Stair=Interna Stana Ŝtipa Ŝtupo
+Outer Tin Block Stair=Ekstera Stana Ŝtipa Ŝtupo
+Tin Block Slab=Stana Ŝtipa Plato
+Copper Block Stair=Kupra Ŝtipa Ŝtupo
+Inner Copper Block Stair=Interna Kupra Ŝtipa Ŝtupo
+Outer Copper Block Stair=Ekstera Kupra Ŝtipa Ŝtupo
+Copper Block Slab=Kupra Ŝtipa Plato
+Bronze Block Stair=Bronza Ŝtipa Ŝtupo
+Inner Bronze Block Stair=Interna Bronza Ŝtipa Ŝtupo
+Outer Bronze Block Stair=Ekstera Bronza Ŝtipa Ŝtupo
+Bronze Block Slab=Bronza Ŝtipa Plato
+Gold Block Stair=Ora Ŝtipa Ŝtupo
+Inner Gold Block Stair=Interna Ora Ŝtipa Ŝtupo
+Outer Gold Block Stair=Ekstera Ora Ŝtipa Ŝtupo
+Gold Block Slab=Ora Ŝtipa Plato
+Ice Stair=Glacia Ŝtupo
+Inner Ice Stair=Interna Glacia Ŝtupo
+Outer Ice Stair=Ekstera Glacia Ŝtupo
+Ice Slab=Glacia Plato
+Snow Block Stair=Neĝa Ŝtipa Ŝtupo
+Inner Snow Block Stair=Interna Neĝa Ŝtipa Ŝtupo
+Outer Snow Block Stair=Ekstera Neĝa Ŝtipa Ŝtupo
+Snow Block Slab=Neĝa Ŝtipa Plato
diff --git a/mods/stairs/locale/stairs.es.tr b/mods/stairs/locale/stairs.es.tr
new file mode 100644
index 00000000..374540c7
--- /dev/null
+++ b/mods/stairs/locale/stairs.es.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Escalera de vidrio
+Glass Slab=Losa de vidrio
+Inner Glass Stair=Escalera interior de vidrio
+Outer Glass Stair=Escalera exterior de vidrio
+Obsidian Glass Stair=Escalera de vidrio de obsidiana
+Obsidian Glass Slab=Losa de vidrio de obsidiana
+Inner Obsidian Glass Stair=Escalera interior de vidrio de obsidiana
+Outer Obsidian Glass Stair=Escalera exterior de vidrio de obsidiana
+Wooden Stair=Escalera de madera
+Inner Wooden Stair=Escalera interior de madera
+Outer Wooden Stair=Escalera exterior de madera
+Wooden Slab=Losa de madera
+Jungle Wood Stair=Escalera de madera tropical
+Inner Jungle Wood Stair=Escalera interior de madera tropical
+Outer Jungle Wood Stair=Escalera exterior de madera tropical
+Jungle Wood Slab=Losa de madera tropical
+Pine Wood Stair=Escalera de pino
+Inner Pine Wood Stair=Escalera interior de pino
+Outer Pine Wood Stair=Escalera exterior de pino
+Pine Wood Slab=Losa de pino
+Acacia Wood Stair=Escalera de acacia
+Inner Acacia Wood Stair=Escalera interior de acacia
+Outer Acacia Wood Stair=Escalera exterior de acacia
+Acacia Wood Slab=Losa de acacia
+Aspen Wood Stair=Escalera de álamo
+Inner Aspen Wood Stair=Escalera interior de álamo
+Outer Aspen Wood Stair=Escalera exterior de álamo
+Aspen Wood Slab=Losa de álamo
+Stone Stair=Escalera de piedra
+Inner Stone Stair=Escalera interior de piedra
+Outer Stone Stair=Escalera exterior de piedra
+Stone Slab=Losa de piedra
+Cobblestone Stair=Escalera de adoquines
+Inner Cobblestone Stair=Escalera interior de adoquines
+Outer Cobblestone Stair=Escalera exterior de adoquines
+Cobblestone Slab=Losa de adoquines
+Mossy Cobblestone Stair=Escalera de adoquines musgosos
+Inner Mossy Cobblestone Stair=Escalera interior de adoquines musgosos
+Outer Mossy Cobblestone Stair=Escalera exterior de adoquines musgosos
+Mossy Cobblestone Slab=Losa de adoquines musgosos
+Stone Brick Stair=Escalera de ladrillos de piedra
+Inner Stone Brick Stair=Escalera interior de ladrillos de piedra
+Outer Stone Brick Stair=Escalera exterior de ladrillos de piedra
+Stone Brick Slab=Losa de ladrillos de piedra
+Stone Block Stair=Escalera de bloques de piedra
+Inner Stone Block Stair=Escalera interior de bloques de piedra
+Outer Stone Block Stair=Escalera exterior de bloques de piedra
+Stone Block Slab=Losa de bloques de piedra
+Desert Stone Stair=Escalera de piedra desértica
+Inner Desert Stone Stair=Escalera interior de piedra desértica
+Outer Desert Stone Stair=Escalera exterior de piedra desértica
+Desert Stone Slab=Losa de piedra desértica
+Desert Cobblestone Stair=Escalera de adoquines desérticos
+Inner Desert Cobblestone Stair=Escalera interior de adoquines desérticos
+Outer Desert Cobblestone Stair=Escalera exterior de adoquines desérticos
+Desert Cobblestone Slab=Losa de adoquines desérticos
+Desert Stone Brick Stair=Escalera de ladrillos desérticos
+Inner Desert Stone Brick Stair=Escalera interior de ladrillos desérticos
+Outer Desert Stone Brick Stair=Escalera exterior de ladrillos desérticos
+Desert Stone Brick Slab=Losa de ladrillos desérticos
+Desert Stone Block Stair=Escalera de bloques de piedra desértica
+Inner Desert Stone Block Stair=Escalera interior de bloques de piedra desértica
+Outer Desert Stone Block Stair=Escalera exterior de bloques de piedra desértica
+Desert Stone Block Slab=Losa de bloques de piedra desértica
+Sandstone Stair=Escalera de arenisca
+Inner Sandstone Stair=Escalera interior de arenisca
+Outer Sandstone Stair=Escalera exterior de arenisca
+Sandstone Slab=Losa de arenisca
+Sandstone Brick Stair=Escalera de ladrillos de arenisca
+Inner Sandstone Brick Stair=Escalera interior de ladrillos de arenisca
+Outer Sandstone Brick Stair=Escalera exterior de ladrillos de arenisca
+Sandstone Brick Slab=Losa de ladrillos de arenisca
+Sandstone Block Stair=Escalera de bloques de arenisca
+Inner Sandstone Block Stair=Escalera interior de bloques de arenisca
+Outer Sandstone Block Stair=Escalera exterior de bloques de arenisca
+Sandstone Block Slab=Losa de bloques de arenisca
+Desert Sandstone Stair=Escalera de arenisca desértica
+Inner Desert Sandstone Stair=Escalera interior de arenisca desértica
+Outer Desert Sandstone Stair=Escalera exterior de arenisca desértica
+Desert Sandstone Slab=Losa de arenisca desértica
+Desert Sandstone Brick Stair=Escalera de ladrillos de arenisca desértica
+Inner Desert Sandstone Brick Stair=Escalera interior de ladrillos de arenisca desértica
+Outer Desert Sandstone Brick Stair=Escalera exterior de ladrillos de arenisca desértica
+Desert Sandstone Brick Slab=Losa de ladrillos de arenisca desértica
+Desert Sandstone Block Stair=Escalera de bloques de arenisca desértica
+Inner Desert Sandstone Block Stair=Escalera interior de bloques de arenisca desértica
+Outer Desert Sandstone Block Stair=Escalera exterior de bloques de arenisca desértica
+Desert Sandstone Block Slab=Losa de bloques de arenisca desértica
+Silver Sandstone Stair=Escalera de arenisca plateada
+Inner Silver Sandstone Stair=Escalera interior de arenisca plateada
+Outer Silver Sandstone Stair=Escalera exterior de arenisca plateada
+Silver Sandstone Slab=Losa de arenisca plateada
+Silver Sandstone Brick Stair=Escalera de ladrillos de arenisca plateada
+Inner Silver Sandstone Brick Stair=Escalera interior de ladrillos de arenisca plateada
+Outer Silver Sandstone Brick Stair=Escalera exterior de ladrillos de arenisca plateada
+Silver Sandstone Brick Slab=Losa de ladrillos de arenisca plateada
+Silver Sandstone Block Stair=Escalera de bloques de arenisca plateada
+Inner Silver Sandstone Block Stair=Escalera interior de bloques de arenisca plateada
+Outer Silver Sandstone Block Stair=Escalera exterior de bloques de arenisca plateada
+Silver Sandstone Block Slab=Losa de bloques de arenisca plateada
+Obsidian Stair=Escalera de obsidiana
+Inner Obsidian Stair=Escalera interior de obsidiana
+Outer Obsidian Stair=Escalera exterior de obsidiana
+Obsidian Slab=Losa de obsidiana
+Obsidian Brick Stair=Escalera de ladrillos de obsidiana
+Inner Obsidian Brick Stair=Escalera interior de ladrillos de obsidiana
+Outer Obsidian Brick Stair=Escalera exterior de ladrillos de obsidiana
+Obsidian Brick Slab=Losa de ladrillos de obsidiana
+Obsidian Block Stair=Escalera de bloques de obsidiana
+Inner Obsidian Block Stair=Escalera interior de bloques de obsidiana
+Outer Obsidian Block Stair=Escalera exterior de bloques de obsidiana
+Obsidian Block Slab=Losa de bloques de obsidiana
+Brick Stair=Escalera de ladrillos
+Inner Brick Stair=Escalera interior de ladrillos
+Outer Brick Stair=Escalera exterior de ladrillos
+Brick Slab=Losa de ladrillos
+Steel Block Stair=Escalera de acero
+Inner Steel Block Stair=Escalera interior de acero
+Outer Steel Block Stair=Escalera exterior de acero
+Steel Block Slab=Losa de acero
+Tin Block Stair=Escalera de estaño
+Inner Tin Block Stair=Escalera interior de estaño
+Outer Tin Block Stair=Escalera exterior de estaño
+Tin Block Slab=Losa de estaño
+Copper Block Stair=Escalera de cobre
+Inner Copper Block Stair=Escalera interior de cobre
+Outer Copper Block Stair=Escalera exterior de cobre
+Copper Block Slab=Losa de cobre
+Bronze Block Stair=Escalera de bronce
+Inner Bronze Block Stair=Escalera interior de bronce
+Outer Bronze Block Stair=Escalera exterior de bronce
+Bronze Block Slab=Losa de bronce
+Gold Block Stair=Escalera de oro
+Inner Gold Block Stair=Escalera interior de oro
+Outer Gold Block Stair=Escalera exterior de oro
+Gold Block Slab=Losa de oro
+Ice Stair=Escalera de hielo
+Inner Ice Stair=Escalera interior de hielo
+Outer Ice Stair=Escalera exterior de hielo
+Ice Slab=Losa de hielo
+Snow Block Stair=Escalera de nieve
+Inner Snow Block Stair=Escalera interior de nieve
+Outer Snow Block Stair=Escalera exterior de nieve
+Snow Block Slab=Losa de nieve
diff --git a/mods/stairs/locale/stairs.fr.tr b/mods/stairs/locale/stairs.fr.tr
new file mode 100644
index 00000000..81b575e6
--- /dev/null
+++ b/mods/stairs/locale/stairs.fr.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Escalier de verre
+Glass Slab=Dalle de verre
+Inner Glass Stair=Escalier intérieur de verre
+Outer Glass Stair=Escalier extérieur de verre
+Obsidian Glass Stair=Escalier de verre d'obsidienne
+Obsidian Glass Slab=Dalle de verre d'obsidienne
+Inner Obsidian Glass Stair=Escalier intérieur de verre d'obsidienne
+Outer Obsidian Glass Stair=Escalier extérieur de verre d'obsidienne
+Wooden Stair=Escalier en bois
+Inner Wooden Stair=Escalier intérieur en bois
+Outer Wooden Stair=Escalier extérieur en bois
+Wooden Slab=Dalle de bois
+Jungle Wood Stair=Escalier en bois de la jungle
+Inner Jungle Wood Stair=Escalier intérieur en bois de la jungle
+Outer Jungle Wood Stair=Escalier extérieur en bois de la jungle
+Jungle Wood Slab=Dalle en bois de la jungle
+Pine Wood Stair=Escalier en pin
+Inner Pine Wood Stair=Escalier intérieur en pin
+Outer Pine Wood Stair=Escalier extérieur en pin
+Pine Wood Slab=Dalle en pin
+Acacia Wood Stair=Escalier en acacia
+Inner Acacia Wood Stair=Escalier intérieur en acacia
+Outer Acacia Wood Stair=Escalier extérieur en acacia
+Acacia Wood Slab=Dalle en acacia
+Aspen Wood Stair=Escalier en tremble
+Inner Aspen Wood Stair=Escalier intérieur en tremble
+Outer Aspen Wood Stair=Escalier extérieur en tremble
+Aspen Wood Slab=Dalle en tremble
+Stone Stair=Escalier de pierre
+Inner Stone Stair=Escalier intérieur de pierre
+Outer Stone Stair=Escalier extérieur de pierre
+Stone Slab=Dalle de pierre
+Cobblestone Stair=Escalier en pavé
+Inner Cobblestone Stair=Escalier intérieur en pavé
+Outer Cobblestone Stair=Escalier extérieur en pavé
+Cobblestone Slab=Dalle en pavé
+Mossy Cobblestone Stair=Escalier en pavé moussu
+Inner Mossy Cobblestone Stair=Escalier intérieur en pavé moussu
+Outer Mossy Cobblestone Stair=Escalier extérieur en pavé moussu
+Mossy Cobblestone Slab=Dalle en pavé moussu
+Stone Brick Stair=Escalier en brique de pierre
+Inner Stone Brick Stair=Escalier intérieur en brique de pierre
+Outer Stone Brick Stair=Escalier extérieur en brique de pierre
+Stone Brick Slab=Dalle en brique de pierre
+Stone Block Stair=Escalier en bloc de pierre
+Inner Stone Block Stair=Escalier intérieur en bloc de pierre
+Outer Stone Block Stair=Escalier extérieur en bloc de pierre
+Stone Block Slab=Dalle en bloc de pierre
+Desert Stone Stair=Escalier en pierre du désert
+Inner Desert Stone Stair=Escalier intérieur en pierre du désert
+Outer Desert Stone Stair=Escalier extérieur en pierre du désert
+Desert Stone Slab=Dalle en pierre du désert
+Desert Cobblestone Stair=Escalier en pavé du désert
+Inner Desert Cobblestone Stair=Escalier intérieur en pavé du désert
+Outer Desert Cobblestone Stair=Escalier extérieur en pavé du désert
+Desert Cobblestone Slab=Dalle en pavé du désert
+Desert Stone Brick Stair=Escalier en brique de pierre du désert
+Inner Desert Stone Brick Stair=Escalier intérieur en brique de pierre du désert
+Outer Desert Stone Brick Stair=Escalier extérieur en brique de pierre du désert
+Desert Stone Brick Slab=Dalle en brique de pierre du désert
+Desert Stone Block Stair=Escalier en bloc de pierre du désert
+Inner Desert Stone Block Stair=Escalier intérieur en bloc de pierre du désert
+Outer Desert Stone Block Stair=Escalier extérieur en bloc de pierre du désert
+Desert Stone Block Slab=Dalle en bloc de pierre du désert
+Sandstone Stair=Escalier en grès
+Inner Sandstone Stair=Escalier intérieur en grès
+Outer Sandstone Stair=Escalier extérieur en grès
+Sandstone Slab=Dalle en grès
+Sandstone Brick Stair=Escalier en brique de grès
+Inner Sandstone Brick Stair=Escalier intérieur en brique de grès
+Outer Sandstone Brick Stair=Escalier extérieur en brique de grès
+Sandstone Brick Slab=Dalle en brique de grès
+Sandstone Block Stair=Escalier en bloc de grès
+Inner Sandstone Block Stair=Escalier intérieur en bloc de grès
+Outer Sandstone Block Stair=Escalier extérieur en bloc de grès
+Sandstone Block Slab=Dalle en bloc de grès
+Desert Sandstone Stair=Escalier en grès du désert
+Inner Desert Sandstone Stair=Escalier intérieur en grès du désert
+Outer Desert Sandstone Stair=Escalier extérieur en grès du désert
+Desert Sandstone Slab=Dalle en grès du désert
+Desert Sandstone Brick Stair=Escalier en brique de grès du désert
+Inner Desert Sandstone Brick Stair=Escalier intérieur en brique de grès du désert
+Outer Desert Sandstone Brick Stair=Escalier extérieur en brique de grès du désert
+Desert Sandstone Brick Slab=Dalle en brique de grès du désert
+Desert Sandstone Block Stair=Escalier en bloc de grès du désert
+Inner Desert Sandstone Block Stair=Escalier intérieur en bloc de grès du désert
+Outer Desert Sandstone Block Stair=Escalier extérieur en bloc de grès du désert
+Desert Sandstone Block Slab=Dalle en bloc de grès du désert
+Silver Sandstone Stair=Escalier en grès argenté
+Inner Silver Sandstone Stair=Escalier intérieur en grès argenté
+Outer Silver Sandstone Stair=Escalier extérieur en grès argenté
+Silver Sandstone Slab=Dalle en grès argenté
+Silver Sandstone Brick Stair=Escalier en brique de grès argenté
+Inner Silver Sandstone Brick Stair=Escalier intérieur en brique de grès argenté
+Outer Silver Sandstone Brick Stair=Escalier extérieur en brique de grès argenté
+Silver Sandstone Brick Slab=Dalle en brique de grès argenté
+Silver Sandstone Block Stair=Escalier en bloc de grès argenté
+Inner Silver Sandstone Block Stair=Escalier intérieur en bloc de grès argenté
+Outer Silver Sandstone Block Stair=Escalier extérieur en bloc de grès argenté
+Silver Sandstone Block Slab=Dalle en bloc de grès argenté
+Obsidian Stair=Escalier en obsidienne
+Inner Obsidian Stair=Escalier intérieur en obsidienne
+Outer Obsidian Stair=Escalier extérieur en obsidienne
+Obsidian Slab=Dalle en obsidienne
+Obsidian Brick Stair=Escalier en brique d'obsidienne
+Inner Obsidian Brick Stair=Escalier intérieur en brique d'obsidienne
+Outer Obsidian Brick Stair=Escalier extérieur en brique d'obsidienne
+Obsidian Brick Slab=Dalle en brique d'obsidienne
+Obsidian Block Stair=Escalier en bloc d'obsidienne
+Inner Obsidian Block Stair=Escalier intérieur en bloc d'obsidienne
+Outer Obsidian Block Stair=Escalier extérieur en bloc d'obsidienne
+Obsidian Block Slab=Dalle en bloc d'obsidienne
+Brick Stair=Escalier en brique
+Inner Brick Stair=Escalier intérieur en brique
+Outer Brick Stair=Escalier extérieur en brique
+Brick Slab=Dalle en brique
+Steel Block Stair=Escalier en acier
+Inner Steel Block Stair=Escalier intérieur en acier
+Outer Steel Block Stair=Escalier extérieur en acier
+Steel Block Slab=Dalle en acier
+Tin Block Stair=Escalier en bloc d'étain
+Inner Tin Block Stair=Escalier intérieur en bloc d'étain
+Outer Tin Block Stair=Escalier extérieur en bloc d'étain
+Tin Block Slab=Dalle en bloc d'étain
+Copper Block Stair=Escalier en bloc de cuivre
+Inner Copper Block Stair=Escalier intérieur en bloc de cuivre
+Outer Copper Block Stair=Escalier extérieur en bloc de cuivre
+Copper Block Slab=Dalle en bloc de cuivre
+Bronze Block Stair=Escalier en bronze
+Inner Bronze Block Stair=Escalier intérieur en bronze
+Outer Bronze Block Stair=Escalier extérieur en bronze
+Bronze Block Slab=Dalle en bronze
+Gold Block Stair=Escalier en bloc d'or
+Inner Gold Block Stair=Escalier intérieur en bloc d'or
+Outer Gold Block Stair=Escalier extérieur en bloc d'or
+Gold Block Slab=Dalle en bloc d'or
+Ice Stair=Escalier de glace
+Inner Ice Stair=Escalier intérieur de glace
+Outer Ice Stair=Escalier extérieur de glace
+Ice Slab=Dalle de glace
+Snow Block Stair=Escalier en bloc de neige
+Inner Snow Block Stair=Escalier intérieur en bloc de neige
+Outer Snow Block Stair=Escalier extérieur en bloc de neige
+Snow Block Slab=Dalle en bloc de neige
diff --git a/mods/stairs/locale/stairs.id.tr b/mods/stairs/locale/stairs.id.tr
new file mode 100644
index 00000000..dbdfaa0f
--- /dev/null
+++ b/mods/stairs/locale/stairs.id.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Tangga Kaca
+Glass Slab=Lempengan Kaca
+Inner Glass Stair=Tangga Kaca Dalam
+Outer Glass Stair=Tangga Kaca Luar
+Obsidian Glass Stair=Tangga Kaca Obsidian
+Obsidian Glass Slab=Lempengan Kaca Obsidian
+Inner Obsidian Glass Stair=Tangga Kaca Obsidian Dalam
+Outer Obsidian Glass Stair=Tangga Kaca Obsidian Luar
+Wooden Stair=Tangga Kayu
+Inner Wooden Stair=Tangga Kayu Dalam
+Outer Wooden Stair=Tangga Kayu Luar
+Wooden Slab=Lempengan Kayu
+Jungle Wood Stair=Tangga Kayu Rimba
+Inner Jungle Wood Stair=Tangga Kayu Rimba Dalam
+Outer Jungle Wood Stair=Tangga Kayu Rimba Luar
+Jungle Wood Slab=Lempengan Kayu Rimba
+Pine Wood Stair=Tangga Kayu Pinus
+Inner Pine Wood Stair=Tangga Kayu Pinus Dalam
+Outer Pine Wood Stair=Tangga Kayu Pinus Luar
+Pine Wood Slab=Lempengan Kayu Pinus
+Acacia Wood Stair=Tangga Kayu Akasia
+Inner Acacia Wood Stair=Tangga Kayu Akasia Dalam
+Outer Acacia Wood Stair=Tangga Kayu Akasia Luar
+Acacia Wood Slab=Lempengan Kayu Akasia
+Aspen Wood Stair=Tangga Kayu Aspen
+Inner Aspen Wood Stair=Tangga Kayu Aspen Dalam
+Outer Aspen Wood Stair=Tangga Kayu Aspen Luar
+Aspen Wood Slab=Lempengan Kayu Aspen
+Stone Stair=Tangga Batu
+Inner Stone Stair=Tangga Batu Dalam
+Outer Stone Stair=Tangga Batu Luar
+Stone Slab=Lempengan Batu
+Cobblestone Stair=Tangga Bongkahan Batu
+Inner Cobblestone Stair=Tangga Bongkahan Batu Dalam
+Outer Cobblestone Stair=Tangga Bongkahan Batu Luar
+Cobblestone Slab=Lempengan Bongkahan Batu
+Mossy Cobblestone Stair=Tangga Bongkahan Batu Berlumut
+Inner Mossy Cobblestone Stair=Tangga Bongkahan Batu Berlumut Dalam
+Outer Mossy Cobblestone Stair=Tangga Bongkahan Batu Berlumut Luar
+Mossy Cobblestone Slab=Lempengan Bongkahan Batu Berlumut
+Stone Brick Stair=Tangga Tembok Batu
+Inner Stone Brick Stair=Tangga Tembok Batu Dalam
+Outer Stone Brick Stair=Tangga Tembok Batu Luar
+Stone Brick Slab=Lempengan Tembok Batu
+Stone Block Stair=Tangga Balok Batu
+Inner Stone Block Stair=Tangga Balok Batu Dalam
+Outer Stone Block Stair=Tangga Balok Batu Luar
+Stone Block Slab=Lempengan Balok Batu
+Desert Stone Stair=Tangga Batu Gurun
+Inner Desert Stone Stair=Tangga Batu Gurun Dalam
+Outer Desert Stone Stair=Tangga Batu Gurun Luar
+Desert Stone Slab=Lempengan Batu Gurun
+Desert Cobblestone Stair=Tangga Bongkahan Batu Gurun
+Inner Desert Cobblestone Stair=Tangga Bongkahan Batu Gurun Dalam
+Outer Desert Cobblestone Stair=Tangga Bongkahan Batu Gurun Luar
+Desert Cobblestone Slab=Lempengan Bongkahan Batu Gurun
+Desert Stone Brick Stair=Tangga Tembok Batu Gurun
+Inner Desert Stone Brick Stair=Tangga Tembok Batu Gurun Dalam
+Outer Desert Stone Brick Stair=Tangga Tembok Batu Gurun Luar
+Desert Stone Brick Slab=Lempengan Tembok Batu Gurun
+Desert Stone Block Stair=Tangga Balok Batu Gurun
+Inner Desert Stone Block Stair=Tangga Balok Batu Gurun Dalam
+Outer Desert Stone Block Stair=Tangga Balok Batu Gurun Luar
+Desert Stone Block Slab=Lempengan Balok Batu Gurun
+Sandstone Stair=Tangga Batu Pasir
+Inner Sandstone Stair=Tangga Batu Pasir Dalam
+Outer Sandstone Stair=Tangga Batu Pasir Luar
+Sandstone Slab=Lempengan Batu Pasir
+Sandstone Brick Stair=Tangga Tembok Batu Pasir
+Inner Sandstone Brick Stair=Tangga Tembok Batu Pasir Dalam
+Outer Sandstone Brick Stair=Tangga Tembok Batu Pasir Luar
+Sandstone Brick Slab=Lempengan Tembok Batu Pasir
+Sandstone Block Stair=Tangga Balok Batu Pasir
+Inner Sandstone Block Stair=Tangga Balok Batu Pasir Dalam
+Outer Sandstone Block Stair=Tangga Balok Batu Pasir Luar
+Sandstone Block Slab=Lempengan Balok Batu Pasir
+Desert Sandstone Stair=Tangga Batu Pasir Gurun
+Inner Desert Sandstone Stair=Tangga Batu Pasir Gurun Dalam
+Outer Desert Sandstone Stair=Tangga Batu Pasir Gurun Luar
+Desert Sandstone Slab=Lempengan Batu Pasir Gurun
+Desert Sandstone Brick Stair=Tangga Tembok Batu Pasir Gurun
+Inner Desert Sandstone Brick Stair=Tangga Tembok Batu Pasir Gurun Dalam
+Outer Desert Sandstone Brick Stair=Tangga Tembok Batu Pasir Gurun Luar
+Desert Sandstone Brick Slab=Lempengan Tembok Batu Pasir Gurun
+Desert Sandstone Block Stair=Tangga Balok Batu Pasir Gurun
+Inner Desert Sandstone Block Stair=Tangga Balok Batu Pasir Gurun Dalam
+Outer Desert Sandstone Block Stair=Tangga Balok Batu Pasir Gurun Luar
+Desert Sandstone Block Slab=Lempengan Balok Batu Pasir Gurun
+Silver Sandstone Stair=Tangga Batu Pasir Perak
+Inner Silver Sandstone Stair=Tangga Batu Pasir Perak Dalam
+Outer Silver Sandstone Stair=Tangga Batu Pasir Perak Luar
+Silver Sandstone Slab=Lempengan Batu Pasir Perak
+Silver Sandstone Brick Stair=Tangga Tembok Batu Pasir Perak
+Inner Silver Sandstone Brick Stair=Tangga Tembok Batu Pasir Perak Dalam
+Outer Silver Sandstone Brick Stair=Tangga Tembok Batu Pasir Perak Luar
+Silver Sandstone Brick Slab=Lempengan Tembok Batu Pasir Perak
+Silver Sandstone Block Stair=Tangga Balok Batu Pasir Perak
+Inner Silver Sandstone Block Stair=Tangga Balok Batu Pasir Perak Dalam
+Outer Silver Sandstone Block Stair=Tangga Balok Batu Pasir Perak Luar
+Silver Sandstone Block Slab=Lempengan Balok Batu Pasir Perak
+Obsidian Stair=Tangga Obsidian
+Inner Obsidian Stair=Tangga Obsidian Dalam
+Outer Obsidian Stair=Tangga Obsidian Luar
+Obsidian Slab=Lempengan Obsidian
+Obsidian Brick Stair=Tangga Tembok Obsidian
+Inner Obsidian Brick Stair=Tangga Tembok Obsidian Dalam
+Outer Obsidian Brick Stair=Tangga Tembok Obsidian Luar
+Obsidian Brick Slab=Lempengan Tembok Obsidian
+Obsidian Block Stair=Tangga Balok Obsidian
+Inner Obsidian Block Stair=Tangga Balok Obsidian Dalam
+Outer Obsidian Block Stair=Tangga Balok Obsidian Luar
+Obsidian Block Slab=Lempengan Balok Obsidian
+Brick Stair=Tangga Bata
+Inner Brick Stair=Tangga Bata Dalam
+Outer Brick Stair=Tangga Bata Luar
+Brick Slab=Lempengan Bata
+Steel Block Stair=Tangga Balok Baja
+Inner Steel Block Stair=Tangga Balok Baja Dalam
+Outer Steel Block Stair=Tangga Balok Baja Luar
+Steel Block Slab=Lempengan Balok Baja
+Tin Block Stair=Tangga Balok Timah
+Inner Tin Block Stair=Tangga Balok Timah Dalam
+Outer Tin Block Stair=Tangga Balok Timah Luar
+Tin Block Slab=Lempengan Balok Timah
+Copper Block Stair=Tangga Balok Tembaga
+Inner Copper Block Stair=Tangga Balok Tembaga Dalam
+Outer Copper Block Stair=Tangga Balok Tembaga Luar
+Copper Block Slab=Lempengan Balok Tembaga
+Bronze Block Stair=Tangga Balok Perunggu
+Inner Bronze Block Stair=Tangga Balok Perunggu Dalam
+Outer Bronze Block Stair=Tangga Balok Perunggu Luar
+Bronze Block Slab=Lempengan Balok Perunggu
+Gold Block Stair=Tangga Balok Emas
+Inner Gold Block Stair=Tangga Balok Emas Dalam
+Outer Gold Block Stair=Tangga Balok Emas Luar
+Gold Block Slab=Lempengan Balok Emas
+Ice Stair=Tangga Es
+Inner Ice Stair=Tangga Es Dalam
+Outer Ice Stair=Tangga Es Luar
+Ice Slab=Lempengan Es
+Snow Block Stair=Tangga Balok Salju
+Inner Snow Block Stair=Tangga Balok Salju Dalam
+Outer Snow Block Stair=Tangga Balok Salju Luar
+Snow Block Slab=Lempengan Balok Salju
diff --git a/mods/stairs/locale/stairs.it.tr b/mods/stairs/locale/stairs.it.tr
new file mode 100644
index 00000000..16945ec6
--- /dev/null
+++ b/mods/stairs/locale/stairs.it.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Scala di vetro
+Glass Slab=Lastra di vetro
+Inner Glass Stair=Scala di vetro interna
+Outer Glass Stair=Scala di vetro esterna
+Obsidian Glass Stair=Scala di vetro d'ossidiana
+Obsidian Glass Slab=Lastra di vetro d'ossidiana
+Inner Obsidian Glass Stair=Scala di vetro d'ossidiana interna
+Outer Obsidian Glass Stair=Scala di vetro d'ossidiana esterna
+Wooden Stair=Scala di legno
+Inner Wooden Stair=Scala di legno interna
+Outer Wooden Stair=Scala di legno esterna
+Wooden Slab=Lastra di legno
+Jungle Wood Stair=Scala di legno della giungla
+Inner Jungle Wood Stair=Scala di legno della giungla interna
+Outer Jungle Wood Stair=Scala di legno della giungla esterna
+Jungle Wood Slab=Lastra di legno della giungla
+Pine Wood Stair=Scala di legno di pino
+Inner Pine Wood Stair=Scala di legno di pino interna
+Outer Pine Wood Stair=Scala di legno di pino esterna
+Pine Wood Slab=Lastra di legno di pino
+Acacia Wood Stair=Scala di legno d'acacia
+Inner Acacia Wood Stair=Scala di legno d'acacia interna
+Outer Acacia Wood Stair=Scala di legno d'acacia esterna
+Acacia Wood Slab=Lastra di legno d'acacia
+Aspen Wood Stair=Scala di legno di pioppo
+Inner Aspen Wood Stair=Scala di legno di pioppo interna
+Outer Aspen Wood Stair=Scala di legno di pioppo esterna
+Aspen Wood Slab=Lastra di legno di pioppo
+Stone Stair=Scala di pietra
+Inner Stone Stair=Scala di pietra interna
+Outer Stone Stair=Scala di pietra esterna
+Stone Slab=Lastra di pietra
+Cobblestone Stair=Scala di ciottoli
+Inner Cobblestone Stair=Scala di ciottoli interna
+Outer Cobblestone Stair=Scala di ciottoli esterna
+Cobblestone Slab=Lastra di ciottoli
+Mossy Cobblestone Stair=Scala di ciottoli muschiosi
+Inner Mossy Cobblestone Stair=Scala di ciottoli muschiosi interna
+Outer Mossy Cobblestone Stair=Scala di ciottoli muschiosi esterna
+Mossy Cobblestone Slab=Lastra di ciottoli muschiosi
+Stone Brick Stair=Scala di mattone di pietra
+Inner Stone Brick Stair=Scala di mattone di pietra interna
+Outer Stone Brick Stair=Scala di mattone di pietra esterna
+Stone Brick Slab=Lastra di mattone di pietra
+Stone Block Stair=Scala di blocco di pietra
+Inner Stone Block Stair=Scala di blocco di pietra interna
+Outer Stone Block Stair=Scala di blocco di pietra esterna
+Stone Block Slab=Lastra di blocco di pietra
+Desert Stone Stair=Scala di pietra del deserto
+Inner Desert Stone Stair=Scala di pietra del deserto interna
+Outer Desert Stone Stair=Scala di pietra del deserto esterna
+Desert Stone Slab=Lastra di pietra del deserto
+Desert Cobblestone Stair=Scala di ciottoli del deserto
+Inner Desert Cobblestone Stair=Scala di ciottoli del deserto interna
+Outer Desert Cobblestone Stair=Scala di ciottoli del deserto esterna
+Desert Cobblestone Slab=Lastra di ciottoli del deserto
+Desert Stone Brick Stair=Scala di mattone di pietra del deserto
+Inner Desert Stone Brick Stair=Scala di mattone di pietra del deserto interna
+Outer Desert Stone Brick Stair=Scala di mattone di pietra del deserto esterna
+Desert Stone Brick Slab=Lastra di mattone di pietra del deserto
+Desert Stone Block Stair=Scala di blocco di pietra del deserto
+Inner Desert Stone Block Stair=Scala di blocco di pietra del deserto interna
+Outer Desert Stone Block Stair=Scala di blocco di pietra del deserto esterna
+Desert Stone Block Slab=Lastra di blocco di pietra del deserto
+Sandstone Stair=Scala d'arenaria
+Inner Sandstone Stair=Scala d'arenaria interna
+Outer Sandstone Stair=Scala d'arenaria esterna
+Sandstone Slab=Lastra d'arenaria
+Sandstone Brick Stair=Scala di mattone d'arenaria
+Inner Sandstone Brick Stair=Scala di mattone d'arenaria interna
+Outer Sandstone Brick Stair=Scala di mattone d'arenaria esterna
+Sandstone Brick Slab=Lastra di mattone d'arenaria
+Sandstone Block Stair=Scala di blocco d'arenaria
+Inner Sandstone Block Stair=Scala di blocco d'arenaria interna
+Outer Sandstone Block Stair=Scala di blocco d'arenaria esterna
+Sandstone Block Slab=Lastra di blocco d'arenaria
+Desert Sandstone Stair=Scala d'arenaria del deserto
+Inner Desert Sandstone Stair=Scala d'arenaria del deserto interna
+Outer Desert Sandstone Stair=Scala d'arenaria del deserto esterna
+Desert Sandstone Slab=Lastra d'arenaria del deserto
+Desert Sandstone Brick Stair=Scala di mattone d'arenaria del deserto
+Inner Desert Sandstone Brick Stair=Scala di mattone d'arenaria del deserto interna
+Outer Desert Sandstone Brick Stair=Scala di mattone d'arenaria del deserto esterna
+Desert Sandstone Brick Slab=Lastra di mattone d'arenaria del deserto
+Desert Sandstone Block Stair=Scala di blocco d'arenaria del deserto
+Inner Desert Sandstone Block Stair=Scala di blocco d'arenaria del deserto interna
+Outer Desert Sandstone Block Stair=Scala di blocco d'arenaria del deserto esterna
+Desert Sandstone Block Slab=Lastra di blocco d'arenaria del deserto
+Silver Sandstone Stair=Scala d'arenaria argentata
+Inner Silver Sandstone Stair=Scala d'arenaria argentata interna
+Outer Silver Sandstone Stair=Scala d'arenaria argentata esterna
+Silver Sandstone Slab=Lastra d'arenaria argentata
+Silver Sandstone Brick Stair=Scala di mattone d'arenaria argentata
+Inner Silver Sandstone Brick Stair=Scala di mattone d'arenaria argentata interna
+Outer Silver Sandstone Brick Stair=Scala di mattone d'arenaria argentata esterna
+Silver Sandstone Brick Slab=Lastra di mattone d'arenaria argentata
+Silver Sandstone Block Stair=Scala di blocco d'arenaria argentata
+Inner Silver Sandstone Block Stair=Scala di blocco d'arenaria argentata interna
+Outer Silver Sandstone Block Stair=Scala di blocco d'arenaria argentata esterna
+Silver Sandstone Block Slab=Lastra di blocco d'arenaria argentata
+Obsidian Stair=Scala d'ossidiana
+Inner Obsidian Stair=Scala d'ossidiana interna
+Outer Obsidian Stair=Scala d'ossidiana esterna
+Obsidian Slab=Lastra d'ossidiana
+Obsidian Brick Stair=Scala di mattone d'ossidiana
+Inner Obsidian Brick Stair=Scala di mattone d'ossidiana interna
+Outer Obsidian Brick Stair=Scala di mattone d'ossidiana esterna
+Obsidian Brick Slab=Lastra di mattone d'ossidiana
+Obsidian Block Stair=Scala di blocco d'ossidiana
+Inner Obsidian Block Stair=Scala di blocco d'ossidiana interna
+Outer Obsidian Block Stair=Scala di blocco d'ossidiana esterna
+Obsidian Block Slab=Lastra di blocco d'ossidiana
+Brick Stair=Scala di mattone
+Inner Brick Stair=Scala di mattone interna
+Outer Brick Stair=Scala di mattone esterna
+Brick Slab=Lastra di mattone
+Steel Block Stair=Scala di blocco d'acciaio
+Inner Steel Block Stair=Scala di blocco d'acciaio interna
+Outer Steel Block Stair=Scala di blocco d'acciaio esterna
+Steel Block Slab=Lastra di blocco d'acciaio
+Tin Block Stair=Scala di blocco di stagno
+Inner Tin Block Stair=Scala di blocco di stagno interna
+Outer Tin Block Stair=Scala di blocco di stagno esterna
+Tin Block Slab=Lastra di blocco di stagno
+Copper Block Stair=Scala di blocco di rame
+Inner Copper Block Stair=Scala di blocco di rame interna
+Outer Copper Block Stair=Scala di blocco di rame esterna
+Copper Block Slab=Lastra di blocco di rame
+Bronze Block Stair=Scala di blocco di bronzo
+Inner Bronze Block Stair=Scala di blocco di bronzo interna
+Outer Bronze Block Stair=Scala di blocco di bronzo esterna
+Bronze Block Slab=Lastra di blocco di bronzo
+Gold Block Stair=Scala di blocco d'oro
+Inner Gold Block Stair=Scala di blocco d'oro interna
+Outer Gold Block Stair=Scala di blocco d'oro esterna
+Gold Block Slab=Lastra di blocco d'oro
+Ice Stair=Scala di ghiaccio
+Inner Ice Stair=Scala di ghiaccio interna
+Outer Ice Stair=Scala di ghiaccio esterna
+Ice Slab=Lastra di ghiaccio
+Snow Block Stair=Scala di blocco di neve
+Inner Snow Block Stair=Scala di blocco di neve interna
+Outer Snow Block Stair=Scala di blocco di neve esterna
+Snow Block Slab=Lastra di blocco di neve
diff --git a/mods/stairs/locale/stairs.ja.tr b/mods/stairs/locale/stairs.ja.tr
new file mode 100644
index 00000000..02576cd6
--- /dev/null
+++ b/mods/stairs/locale/stairs.ja.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=ガラスの階段
+Glass Slab=ガラスの厚板
+Inner Glass Stair=ガラスの凹階段
+Outer Glass Stair=ガラスの凸階段
+Obsidian Glass Stair=黒曜石ガラスの階段
+Obsidian Glass Slab=黒曜石ガラスの厚板
+Inner Obsidian Glass Stair=黒曜石ガラスの凹階段
+Outer Obsidian Glass Stair=黒曜石ガラスの凸階段
+Wooden Stair=木製の階段
+Inner Wooden Stair=木製の凹階段
+Outer Wooden Stair=木製の凸階段
+Wooden Slab=木製の厚板
+Jungle Wood Stair=ジャングル材の階段
+Inner Jungle Wood Stair=ジャングル材の凹階段
+Outer Jungle Wood Stair=ジャングル材の凸階段
+Jungle Wood Slab=ジャングル材の厚板
+Pine Wood Stair=マツ材の階段
+Inner Pine Wood Stair=マツ材の凹階段
+Outer Pine Wood Stair=マツ材の凸階段
+Pine Wood Slab=マツ材の厚板
+Acacia Wood Stair=アカシア材の階段
+Inner Acacia Wood Stair=アカシア材の凹階段
+Outer Acacia Wood Stair=アカシア材の凸階段
+Acacia Wood Slab=アカシア材の厚板
+Aspen Wood Stair=ポプラ材の階段
+Inner Aspen Wood Stair=ポプラ材の凹階段
+Outer Aspen Wood Stair=ポプラ材の凸階段
+Aspen Wood Slab=ポプラ材の厚板
+Stone Stair=石の階段
+Inner Stone Stair=石の凹階段
+Outer Stone Stair=石の凸階段
+Stone Slab=石の厚板
+Cobblestone Stair=丸石の階段
+Inner Cobblestone Stair=丸石の凹階段
+Outer Cobblestone Stair=丸石の凸階段
+Cobblestone Slab=丸石の厚板
+Mossy Cobblestone Stair=苔むした丸石の階段
+Inner Mossy Cobblestone Stair=苔むした丸石の凹階段
+Outer Mossy Cobblestone Stair=苔むした丸石の凸階段
+Mossy Cobblestone Slab=苔むした丸石の厚板
+Stone Brick Stair=石レンガの階段
+Inner Stone Brick Stair=石レンガの凹階段
+Outer Stone Brick Stair=石レンガの凸階段
+Stone Brick Slab=石レンガの厚板
+Stone Block Stair=石ブロックの階段
+Inner Stone Block Stair=石ブロックの凹階段
+Outer Stone Block Stair=石ブロックの凸階段
+Stone Block Slab=石ブロックの厚板
+Desert Stone Stair=砂漠の石の階段
+Inner Desert Stone Stair=砂漠の石の凹階段
+Outer Desert Stone Stair=砂漠の石の凸階段
+Desert Stone Slab=砂漠の石の厚板
+Desert Cobblestone Stair=砂漠の丸石の階段
+Inner Desert Cobblestone Stair=砂漠の丸石の凹階段
+Outer Desert Cobblestone Stair=砂漠の丸石の凸階段
+Desert Cobblestone Slab=砂漠の丸石の厚板
+Desert Stone Brick Stair=砂漠の石レンガの階段
+Inner Desert Stone Brick Stair=砂漠の石レンガの凹階段
+Outer Desert Stone Brick Stair=砂漠の石レンガの凸階段
+Desert Stone Brick Slab=砂漠の石レンガの厚板
+Desert Stone Block Stair=砂漠の石ブロックの階段
+Inner Desert Stone Block Stair=砂漠の石ブロックの凹階段
+Outer Desert Stone Block Stair=砂漠の石ブロックの凸階段
+Desert Stone Block Slab=砂漠の石ブロックの厚板
+Sandstone Stair=砂岩の階段
+Inner Sandstone Stair=砂岩の凹階段
+Outer Sandstone Stair=砂岩の凸階段
+Sandstone Slab=砂岩の厚板
+Sandstone Brick Stair=砂岩レンガの階段
+Inner Sandstone Brick Stair=砂岩レンガの凹階段
+Outer Sandstone Brick Stair=砂岩レンガの凸階段
+Sandstone Brick Slab=砂岩レンガの厚板
+Sandstone Block Stair=砂岩ブロックの階段
+Inner Sandstone Block Stair=砂岩ブロックの凹階段
+Outer Sandstone Block Stair=砂岩ブロックの凸階段
+Sandstone Block Slab=砂岩ブロックの厚板
+Desert Sandstone Stair=砂漠の砂岩の階段
+Inner Desert Sandstone Stair=砂漠の砂岩の凹階段
+Outer Desert Sandstone Stair=砂漠の砂岩の凸階段
+Desert Sandstone Slab=砂漠の砂岩の厚板
+Desert Sandstone Brick Stair=砂漠の砂岩レンガの階段
+Inner Desert Sandstone Brick Stair=砂漠の砂岩レンガの凹階段
+Outer Desert Sandstone Brick Stair=砂漠の砂岩レンガの凸階段
+Desert Sandstone Brick Slab=砂漠の砂岩レンガの厚板
+Desert Sandstone Block Stair=砂漠の砂岩ブロックの階段
+Inner Desert Sandstone Block Stair=砂漠の砂岩ブロックの凹階段
+Outer Desert Sandstone Block Stair=砂漠の砂岩ブロックの凸階段
+Desert Sandstone Block Slab=砂漠の砂岩ブロックの厚板
+Silver Sandstone Stair=銀色の砂岩の階段
+Inner Silver Sandstone Stair=銀色の砂岩の凹階段
+Outer Silver Sandstone Stair=銀色の砂岩の凸階段
+Silver Sandstone Slab=銀色の砂岩の厚板
+Silver Sandstone Brick Stair=銀色の砂岩レンガの階段
+Inner Silver Sandstone Brick Stair=銀色の砂岩レンガの凹階段
+Outer Silver Sandstone Brick Stair=銀色の砂岩レンガの凸階段
+Silver Sandstone Brick Slab=銀色の砂岩レンガの厚板
+Silver Sandstone Block Stair=銀色の砂岩ブロックの階段
+Inner Silver Sandstone Block Stair=銀色の砂岩ブロックの凹階段
+Outer Silver Sandstone Block Stair=銀色の砂岩ブロックの凸階段
+Silver Sandstone Block Slab=銀色の砂岩ブロックの厚板
+Obsidian Stair=黒曜石の階段
+Inner Obsidian Stair=黒曜石の凹階段
+Outer Obsidian Stair=黒曜石の凸階段
+Obsidian Slab=黒曜石の厚板
+Obsidian Brick Stair=黒曜石レンガの階段
+Inner Obsidian Brick Stair=黒曜石レンガの凹階段
+Outer Obsidian Brick Stair=黒曜石レンガの凸階段
+Obsidian Brick Slab=黒曜石レンガの厚板
+Obsidian Block Stair=黒曜石ブロックの階段
+Inner Obsidian Block Stair=黒曜石ブロックの凹階段
+Outer Obsidian Block Stair=黒曜石ブロックの凸階段
+Obsidian Block Slab=黒曜石ブロックの厚板
+Brick Stair=レンガの階段
+Inner Brick Stair=レンガの凹階段
+Outer Brick Stair=レンガの凸階段
+Brick Slab=レンガの厚板
+Steel Block Stair=鉄ブロックの階段
+Inner Steel Block Stair=鉄ブロックの凹階段
+Outer Steel Block Stair=鉄ブロックの凸階段
+Steel Block Slab=鉄ブロックの厚板
+Tin Block Stair=スズの階段
+Inner Tin Block Stair=スズの凹階段
+Outer Tin Block Stair=スズの凸階段
+Tin Block Slab=スズの厚板
+Copper Block Stair=銅ブロックの階段
+Inner Copper Block Stair=銅ブロックの凹階段
+Outer Copper Block Stair=銅ブロックの凸階段
+Copper Block Slab=銅ブロックの厚板
+Bronze Block Stair=青銅ブロックの階段
+Inner Bronze Block Stair=青銅ブロックの凹階段
+Outer Bronze Block Stair=青銅ブロックの凸階段
+Bronze Block Slab=青銅ブロックの厚板
+Gold Block Stair=金ブロックの階段
+Inner Gold Block Stair=金ブロックの凹階段
+Outer Gold Block Stair=金ブロックの凸階段
+Gold Block Slab=金ブロックの厚板
+Ice Stair=氷の階段
+Inner Ice Stair=氷の凹階段
+Outer Ice Stair=氷の凸階段
+Ice Slab=氷の厚板
+Snow Block Stair=雪の階段
+Inner Snow Block Stair=雪の凹階段
+Outer Snow Block Stair=雪の凸階段
+Snow Block Slab=雪の厚板
diff --git a/mods/stairs/locale/stairs.jbo.tr b/mods/stairs/locale/stairs.jbo.tr
new file mode 100644
index 00000000..91d28060
--- /dev/null
+++ b/mods/stairs/locale/stairs.jbo.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=lo blaci serti
+Glass Slab=lo blaci xadba bliku
+Inner Glass Stair=lo zo'i blaci serti
+Outer Glass Stair=lo ze'o blaci serti
+Obsidian Glass Stair=lo je'erma'ablaci blaci serti
+Obsidian Glass Slab=lo je'erma'ablaci blaci ke xadba bliku
+Inner Obsidian Glass Stair=lo zo'i je'erma'ablaci blaci serti
+Outer Obsidian Glass Stair=lo ze'o je'erma'ablaci blaci serti
+Wooden Stair=lo mudri serti
+Inner Wooden Stair=lo zo'i mudri serti
+Outer Wooden Stair=lo ze'o mudri serti
+Wooden Slab=lo mudri ke xadba bliku
+Jungle Wood Stair=lo glatimdemricfoi mudri serti
+Inner Jungle Wood Stair=lo zo'i glatimdemricfoi mudri serti
+Outer Jungle Wood Stair=lo ze'o glatimdemricfoi mudri serti
+Jungle Wood Slab=lo glatimdemricfoi mudri ke xadba bliku
+Pine Wood Stair=lo ku'urmudri serti
+Inner Pine Wood Stair=lo zo'i ku'urmudri serti
+Outer Pine Wood Stair=lo ze'o ku'urmudri serti
+Pine Wood Slab=lo ku'urmudri ke xadba bliku
+Acacia Wood Stair=lo atkaci,ia mudri serti
+Inner Acacia Wood Stair=lo zo'i atkaci,ia mudri serti
+Outer Acacia Wood Stair=lo ze'o atkaci,ia mudri serti
+Acacia Wood Slab=lo atkaci,ia mudri ke xadba bliku
+Aspen Wood Stair=lo mudrpopulu serti
+Inner Aspen Wood Stair=lo zo'i mudrpopulu serti
+Outer Aspen Wood Stair=lo ze'o mudrpopulu serti
+Aspen Wood Slab=lo mudrpopulu ke xadba bliku
+Stone Stair=lo rokci serti
+Inner Stone Stair=lo zo'i rokci serti
+Outer Stone Stair=lo ze'o rokci serti
+Stone Slab=lo rokci ke xadba bliku
+Cobblestone Stair=lo lolro'iboi serti
+Inner Cobblestone Stair=lo zo'i lolro'iboi serti
+Outer Cobblestone Stair=lo ze'o lolro'iboi serti
+Cobblestone Slab=lo lolro'iboi xadba bliku
+Mossy Cobblestone Stair=lo clika lolro'iboi serti
+Inner Mossy Cobblestone Stair=lo zo'i clika lolro'iboi serti
+Outer Mossy Cobblestone Stair=lo ze'o clika lolro'iboi serti
+Mossy Cobblestone Slab=lo clika lolro'iboi ke xadba bliku
+Stone Brick Stair=lo morna rokci serti
+Inner Stone Brick Stair=lo zo'i morna rokci serti
+Outer Stone Brick Stair=lo ze'o morna rokci serti
+Stone Brick Slab=lo morna rokci ke xadba bliku
+Stone Block Stair=lo rokci bliku serti
+Inner Stone Block Stair=lo zo'i rokci bliku serti
+Outer Stone Block Stair=lo ze'o rokci bliku serti
+Stone Block Slab=lo rokci bliku ke xadba bliku
+Desert Stone Stair=lo cantu'a rokci serti
+Inner Desert Stone Stair=lo zo'i cantu'a rokci serti
+Outer Desert Stone Stair=lo ze'o cantu'a rokci serti
+Desert Stone Slab=lo cantu'a rokci ke xadba bliku
+Desert Cobblestone Stair=lo cantu'a lolro'iboi serti
+Inner Desert Cobblestone Stair=lo zo'i cantu'a lolro'iboi serti
+Outer Desert Cobblestone Stair=lo ze'o cantu'a lolro'iboi serti
+Desert Cobblestone Slab=lo cantu'a lolro'iboi ke xadba bliku
+Desert Stone Brick Stair=lo morna ke cantu'a rokci serti
+Inner Desert Stone Brick Stair=lo zo'i morna ke cantu'a rokci serti
+Outer Desert Stone Brick Stair=lo ze'o morna ke cantu'a rokci serti
+Desert Stone Brick Slab=lo morna ke cantu'a rokci ke xadba bliku
+Desert Stone Block Stair=lo cantu'a rokci bliku serti
+Inner Desert Stone Block Stair=lo zo'i cantu'a rokci bliku serti
+Outer Desert Stone Block Stair=lo ze'o cantu'a rokci bliku serti
+Desert Stone Block Slab=lo cantu'a rokci bliku ke xadba bliku
+Sandstone Stair=lo canro'i serti
+Inner Sandstone Stair=lo zo'i canro'i serti
+Outer Sandstone Stair=lo ze'o canro'i serti
+Sandstone Slab=lo canro'i ke xadba bliku
+Sandstone Brick Stair=lo morna ke canro'i serti
+Inner Sandstone Brick Stair=lo zo'i morna ke canro'i serti
+Outer Sandstone Brick Stair=lo ze'o morna ke canro'i serti
+Sandstone Brick Slab=lo morna canro'i ke xadba bliku
+Sandstone Block Stair=lo canro'i bliku serti
+Inner Sandstone Block Stair=lo zo'i canro'i bliku serti
+Outer Sandstone Block Stair=lo ze'o canro'i bliku serti
+Sandstone Block Slab=lo canro'i bliku ke xadba bliku
+Desert Sandstone Stair=lo cantu'a canro'i serti
+Inner Desert Sandstone Stair=lo zo'i cantu'a canro'i serti
+Outer Desert Sandstone Stair=lo ze'o cantu'a canro'i serti
+Desert Sandstone Slab=lo cantu'a canro'i ke xadba bliku
+Desert Sandstone Brick Stair=lo morna ke cantu'a canro'i serti
+Inner Desert Sandstone Brick Stair=lo zo'i morna ke cantu'a canro'i serti
+Outer Desert Sandstone Brick Stair=lo ze'o morna ke cantu'a canro'i serti
+Desert Sandstone Brick Slab=lo morna ke cantu'a canro'i ke xadba bliku
+Desert Sandstone Block Stair=lo cantu'a canro'i bliku serti
+Inner Desert Sandstone Block Stair=lo zo'i cantu'a canro'i bliku serti
+Outer Desert Sandstone Block Stair=lo ze'o cantu'a canro'i bliku serti
+Desert Sandstone Block Slab=lo cantu'a canro'i ke xadba bliku
+Silver Sandstone Stair=lo rijyska canro'i serti
+Inner Silver Sandstone Stair=lo zo'i rijyska canro'i serti
+Outer Silver Sandstone Stair=lo ze'o rijyska canro'i serti
+Silver Sandstone Slab=lo rijyska canro'i ke xadba bliku
+Silver Sandstone Brick Stair=lo morna ke rijyska canro'i serti
+Inner Silver Sandstone Brick Stair=lo zo'i morna ke rijyska canro'i serti
+Outer Silver Sandstone Brick Stair=lo ze'o morna ke rijyska canro'i serti
+Silver Sandstone Brick Slab=lo morna ke rijyska canro'i ke xadba bliku
+Silver Sandstone Block Stair=lo rijyska canro'i bliku serti
+Inner Silver Sandstone Block Stair=lo zo'i rijyska canro'i bliku serti
+Outer Silver Sandstone Block Stair=lo ze'o rijyska canro'i bliku serti
+Silver Sandstone Block Slab=lo rijyska canro'i bliku ke xadba bliku
+Obsidian Stair=lo je'erma'ablaci serti
+Inner Obsidian Stair=lo zo'i je'erma'ablaci serti
+Outer Obsidian Stair=lo ze'o je'erma'ablaci serti
+Obsidian Slab=lo je'erma'ablaci ke xadba bliku
+Obsidian Brick Stair=lo morna ke je'erma'ablaci serti
+Inner Obsidian Brick Stair=lo zo'i morna ke je'erma'ablaci serti
+Outer Obsidian Brick Stair=lo ze'o morna ke je'erma'ablaci serti
+Obsidian Brick Slab=lo morna je'erma'ablaci ke xadba bliku
+Obsidian Block Stair=lo je'erma'ablaci bliku serti
+Inner Obsidian Block Stair=lo zo'i je'erma'ablaci bliku serti
+Outer Obsidian Block Stair=lo ze'o je'erma'ablaci bliku serti
+Obsidian Block Slab=lo je'erma'ablaci bliku ke xadba bliku
+Brick Stair=lo kitybli serti
+Inner Brick Stair=lo zo'i kitybli serti
+Outer Brick Stair=lo ze'o kitybli serti
+Brick Slab=lo kitybli xadba bliku
+Steel Block Stair=lo gasta bliku serti
+Inner Steel Block Stair=lo zo'i gasta bliku serti
+Outer Steel Block Stair=lo ze'o gasta bliku serti
+Steel Block Slab=lo gasta bliku ke xadba bliku
+Tin Block Stair=lo tinci bliku serti
+Inner Tin Block Stair=lo zo'i tinci bliku serti
+Outer Tin Block Stair=lo ze'o tinci bliku serti
+Tin Block Slab=lo tinci bliku ke xadba bliku
+Copper Block Stair=lo tunka bliku serti
+Inner Copper Block Stair=lo zo'i tunka bliku serti
+Outer Copper Block Stair=lo ze'o tunka bliku serti
+Copper Block Slab=lo tunka xadba bliku
+Bronze Block Stair=lo ransu bliku serti
+Inner Bronze Block Stair=lo zo'i ransu bliku serti
+Outer Bronze Block Stair=lo ze'o ransu bliku serti
+Bronze Block Slab=lo ransu xadba bliku
+Gold Block Stair=lo solji bliku serti
+Inner Gold Block Stair=lo zo'i solji bliku serti
+Outer Gold Block Stair=lo ze'o solji bliku serti
+Gold Block Slab=lo solji bliku ke xadba bliku
+Ice Stair=lo bisli serti
+Inner Ice Stair=lo zo'i bisli serti
+Outer Ice Stair=lo ze'o bisli serti
+Ice Slab=lo bisli ke xadba bliku
+Snow Block Stair=lo snime bliku serti
+Inner Snow Block Stair=lo zo'i snime bliku serti
+Outer Snow Block Stair=lo ze'o snime bliku serti
+Snow Block Slab=lo snime bliku ke xadba bliku
diff --git a/mods/stairs/locale/stairs.ms.tr b/mods/stairs/locale/stairs.ms.tr
new file mode 100644
index 00000000..a39c7f63
--- /dev/null
+++ b/mods/stairs/locale/stairs.ms.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Tangga Kaca
+Glass Slab=Papak Kaca
+Inner Glass Stair=Tangga Kaca Dalaman
+Outer Glass Stair=Tangga Kaca Luaran
+Obsidian Glass Stair=Tangga Obsidia
+Obsidian Glass Slab=Papak Obsidia
+Inner Obsidian Glass Stair=Tangga Obsidia Dalaman
+Outer Obsidian Glass Stair=Tangga Obsidia Luaran
+Wooden Stair=Tangga Kayu
+Inner Wooden Stair=Tangga Kayu Dalaman
+Outer Wooden Stair=Tangga Kayu Luaran
+Wooden Slab=Papak Kayu
+Jungle Wood Stair=Tangga Kayu Hutan
+Inner Jungle Wood Stair=Tangga Kayu Hutan Dalaman
+Outer Jungle Wood Stair=Tangga Kayu Hutan Luaran
+Jungle Wood Slab=Papak Kayu Hutan
+Pine Wood Stair=Tangga Kayu Pain
+Inner Pine Wood Stair=Tangga Kayu Pain Dalaman
+Outer Pine Wood Stair=Tangga Kayu Pain Luaran
+Pine Wood Slab=Papak Kayu Pain
+Acacia Wood Stair=Tangga Kayu Akasia
+Inner Acacia Wood Stair=Tangga Kayu Akasia Dalaman
+Outer Acacia Wood Stair=Tangga Kayu Akasia Luaran
+Acacia Wood Slab=Papak Kayu Akasia
+Aspen Wood Stair=Tangga Kayu Aspen
+Inner Aspen Wood Stair=Tangga Kayu Aspen Dalaman
+Outer Aspen Wood Stair=Tangga Kayu Aspen Luaran
+Aspen Wood Slab=Papak Kayu Aspen
+Stone Stair=Tangga Batu
+Inner Stone Stair=Tangga Batu Dalaman
+Outer Stone Stair=Tangga Batu Luaran
+Stone Slab=Papak Batu
+Cobblestone Stair=Tangga Batu Buntar
+Inner Cobblestone Stair=Tangga Batu Buntar Dalaman
+Outer Cobblestone Stair=Tangga Batu Buntar Luaran
+Cobblestone Slab=Papak Batu Buntar
+Mossy Cobblestone Stair=Tangga Batu Buntar Berlumut
+Inner Mossy Cobblestone Stair=Tangga Batu Buntar Berlumut Dalaman
+Outer Mossy Cobblestone Stair=Tangga Batu Buntar Berlumut Luaran
+Mossy Cobblestone Slab=Papak Batu Buntar Berlumut
+Stone Brick Stair=Tangga Bata Batu
+Inner Stone Brick Stair=Tangga Bata Batu Dalaman
+Outer Stone Brick Stair=Tangga Bata Batu Luaran
+Stone Brick Slab=Papak Bata Batu
+Stone Block Stair=Tangga Bongkah Batu
+Inner Stone Block Stair=Tangga Bongkah Batu Dalaman
+Outer Stone Block Stair=Tangga Bongkah Batu Luaran
+Stone Block Slab=Papak Bongkah Batu
+Desert Stone Stair=Tangga Batu Gurun
+Inner Desert Stone Stair=Tangga Batu Gurun Dalaman
+Outer Desert Stone Stair=Tangga Batu Gurun Luaran
+Desert Stone Slab=Papak Batu Gurun
+Desert Cobblestone Stair=Tangga Batu Buntar Gurun
+Inner Desert Cobblestone Stair=Tangga Batu Buntar Gurun Dalaman
+Outer Desert Cobblestone Stair=Tangga Batu Buntar Gurun Luaran
+Desert Cobblestone Slab=Papak Batu Buntar Gurun
+Desert Stone Brick Stair=Tangga Bata Batu Gurun
+Inner Desert Stone Brick Stair=Tangga Bata Batu Gurun Dalaman
+Outer Desert Stone Brick Stair=Tangga Bata Batu Gurun Luaran
+Desert Stone Brick Slab=Papak Bata Batu Gurun
+Desert Stone Block Stair=Tangga Bongkah Batu Gurun
+Inner Desert Stone Block Stair=Tangga Bongkah Batu Gurun Dalaman
+Outer Desert Stone Block Stair=Tangga Bongkah Batu Gurun Luaran
+Desert Stone Block Slab=Papak Bongkah Batu Gurun
+Sandstone Stair=Tangga Batu Pasir
+Inner Sandstone Stair=Tangga Batu Pasir Dalaman
+Outer Sandstone Stair=Tangga Batu Pasir Luaran
+Sandstone Slab=Papak Batu Pasir
+Sandstone Brick Stair=Tangga Bata Batu Pasir
+Inner Sandstone Brick Stair=Tangga Bata Batu Pasir Dalaman
+Outer Sandstone Brick Stair=Tangga Bata Batu Pasir Luaran
+Sandstone Brick Slab=Papak Bata Batu Pasir
+Sandstone Block Stair=Tangga Bongkah Batu Pasir
+Inner Sandstone Block Stair=Tangga Bongkah Batu Pasir Dalaman
+Outer Sandstone Block Stair=Tangga Bongkah Batu Pasir Luaran
+Sandstone Block Slab=Papak Bongkah Batu Pasir
+Desert Sandstone Stair=Tangga Batu Pasir Gurun
+Inner Desert Sandstone Stair=Tangga Batu Pasir Gurun Dalaman
+Outer Desert Sandstone Stair=Tangga Batu Pasir Gurun Luaran
+Desert Sandstone Slab=Papak Batu Pasir Gurun
+Desert Sandstone Brick Stair=Tangga Bata Batu Pasir Gurun
+Inner Desert Sandstone Brick Stair=Tangga Bata Batu Pasir Gurun Dalaman
+Outer Desert Sandstone Brick Stair=Tangga Bata Batu Pasir Gurun Luaran
+Desert Sandstone Brick Slab=Papak Bata Batu Pasir Gurun
+Desert Sandstone Block Stair=Tangga Bongkah Batu Pasir Gurun
+Inner Desert Sandstone Block Stair=Tangga Bongkah Batu Pasir Gurun Dalaman
+Outer Desert Sandstone Block Stair=Tangga Bongkah Batu Pasir Gurun Luaran
+Desert Sandstone Block Slab=Papak Bongkah Batu Pasir Gurun
+Silver Sandstone Stair=Tangga Batu Pasir Perak
+Inner Silver Sandstone Stair=Tangga Batu Pasir Perak Dalaman
+Outer Silver Sandstone Stair=Tangga Batu Pasir Perak Luaran
+Silver Sandstone Slab=Papak Batu Pasir Perak
+Silver Sandstone Brick Stair=Tangga Bata Batu Pasir Perak
+Inner Silver Sandstone Brick Stair=Tangga Bata Batu Pasir Perak Dalaman
+Outer Silver Sandstone Brick Stair=Tangga Bata Batu Pasir Perak Luaran
+Silver Sandstone Brick Slab=Papak Bata Batu Pasir Perak
+Silver Sandstone Block Stair=Tangga Bongkah Batu Pasir Perak
+Inner Silver Sandstone Block Stair=Tangga Bongkah Batu Pasir Perak Dalaman
+Outer Silver Sandstone Block Stair=Tangga Bongkah Batu Pasir Perak Luaran
+Silver Sandstone Block Slab=Papak Bongkah Batu Pasir Perak
+Obsidian Stair=Tangga Obsidia
+Inner Obsidian Stair=Tangga Obsidia Dalaman
+Outer Obsidian Stair=Tangga Obsidia Luaran
+Obsidian Slab=Papak Obsidia
+Obsidian Brick Stair=Tangga Bata Obsidia
+Inner Obsidian Brick Stair=Tangga Bata Obsidia Dalaman
+Outer Obsidian Brick Stair=Tangga Bata Obsidia Luaran
+Obsidian Brick Slab=Papak Bata Obsidia
+Obsidian Block Stair=Tangga Bongkah Obsidia
+Inner Obsidian Block Stair=Tangga Bongkah Obsidia Dalaman
+Outer Obsidian Block Stair=Tangga Bongkah Obsidia Luaran
+Obsidian Block Slab=Papak Bongkah Obsidia
+Brick Stair=Tangga Bata
+Inner Brick Stair=Tangga Bata Dalaman
+Outer Brick Stair=Tangga Bata Luaran
+Brick Slab=Papak Bata
+Steel Block Stair=Tangga Bongkah Keluli
+Inner Steel Block Stair=Tangga Bongkah Keluli Dalaman
+Outer Steel Block Stair=Tangga Bongkah Keluli Luaran
+Steel Block Slab=Papak Bongkah Keluli
+Tin Block Stair=Tangga Bongkah Timah
+Inner Tin Block Stair=Tangga Bongkah Timah Dalaman
+Outer Tin Block Stair=Tangga Bongkah Timah Luaran
+Tin Block Slab=Papak Bongkah Timah
+Copper Block Stair=Tangga Bongkah Tembaga
+Inner Copper Block Stair=Tangga Bongkah Tembaga Dalaman
+Outer Copper Block Stair=Tangga Bongkah Tembaga Luaran
+Copper Block Slab=Papak Bongkah Tembaga
+Bronze Block Stair=Tangga Bongkah Gangsa
+Inner Bronze Block Stair=Tangga Bongkah Gangsa Dalaman
+Outer Bronze Block Stair=Tangga Bongkah Gangsa Luaran
+Bronze Block Slab=Papak Bongkah Gangsa
+Gold Block Stair=Tangga Bongkah Emas
+Inner Gold Block Stair=Tangga Bongkah Emas Dalaman
+Outer Gold Block Stair=Tangga Bongkah Emas Luaran
+Gold Block Slab=Papak Bongkah Emas
+Ice Stair=Tangga Ais
+Inner Ice Stair=Tangga Ais Dalaman
+Outer Ice Stair=Tangga Ais Luaran
+Ice Slab=Papak Ais
+Snow Block Stair=Tangga Bongkah Salji
+Inner Snow Block Stair=Tangga Bongkah Salji Dalaman
+Outer Snow Block Stair=Tangga Bongkah Salji Luaran
+Snow Block Slab=Papak Bongkah Salji
diff --git a/mods/stairs/locale/stairs.pl.tr b/mods/stairs/locale/stairs.pl.tr
new file mode 100644
index 00000000..14eed7b9
--- /dev/null
+++ b/mods/stairs/locale/stairs.pl.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Schody ze szkła
+Glass Slab=Półblok ze szkła
+Inner Glass Stair=Wewnętrzne schody ze szkła
+Outer Glass Stair=Zewnętrzne schody ze szkła
+Obsidian Glass Stair=Schody z obsydianowego szkła
+Obsidian Glass Slab=Półblok z obsydianowego szkła
+Inner Obsidian Glass Stair=Wewnętrzne schody z obsydianowego szkła
+Outer Obsidian Glass Stair=Zewnętrzne schody z obsydianowego szkła
+Wooden Stair=Schody z drewna
+Inner Wooden Stair=Wewnętrzne schody z drewna
+Outer Wooden Stair=Zewnętrzne schody z drewna
+Wooden Slab=Półblok z drewna
+Jungle Wood Stair=Schody z dżunglowego drewna
+Inner Jungle Wood Stair=Wewnętrzne schody z dżunglowego drewna
+Outer Jungle Wood Stair=Zewnętrzne schody z dżunglowego drewna
+Jungle Wood Slab=Półblok z dżunglowego drewna
+Pine Wood Stair=Schody z sosnowego drzewa
+Inner Pine Wood Stair=Wewnętrzne schody z sosnowego drewna
+Outer Pine Wood Stair=Zewnętrzne schody z sosnowego drewna
+Pine Wood Slab=Półblok z sosnowego drewna
+Acacia Wood Stair=Schody z akacjowego drewna
+Inner Acacia Wood Stair=Wewnętrzne schody z akacjowego drewna
+Outer Acacia Wood Stair=Zewnętrzne schody z akacjowego drewna
+Acacia Wood Slab=Półblok z akacjowego drewna
+Aspen Wood Stair=Schody z brzozowego drewna
+Inner Aspen Wood Stair=Wewnętrzne schody z brzozowego drewna
+Outer Aspen Wood Stair=Zewnętrzne schody z brzozowego drewna
+Aspen Wood Slab=Półblok z brzozowego drewna
+Stone Stair=Schody z kamienia
+Inner Stone Stair=Wewnętrzne schody z kamienia
+Outer Stone Stair=Zewnętrzne schody z kamienia
+Stone Slab=Półblok z kamienia
+Cobblestone Stair=Schody z bruku
+Inner Cobblestone Stair=Wewnętrzne schody z bruku
+Outer Cobblestone Stair=Zewnętrzne schody z bruku
+Cobblestone Slab=Półblok z bruku
+Mossy Cobblestone Stair=Schody z bruku z mchem
+Inner Mossy Cobblestone Stair=Wewnętrzne schody z bruku z mchem
+Outer Mossy Cobblestone Stair=Zewnętrzne schody z bruku z mchem
+Mossy Cobblestone Slab=Półblok z bruku z mchem
+Stone Brick Stair=Schody z kamiennych cegieł
+Inner Stone Brick Stair=Wewnętrzne schody z kamiennych cegieł
+Outer Stone Brick Stair=Zewnętrzne schody z kamiennych cegieł
+Stone Brick Slab=Półblok z kamiennych cegieł
+Stone Block Stair=Schody z kamiennego bloku
+Inner Stone Block Stair=Wewnętrzne schody z kamiennego bloku
+Outer Stone Block Stair=Zewnętrzne schody z kamiennego bloku
+Stone Block Slab=Półblok z kamiennego bloku
+Desert Stone Stair=Schody z pustynnego kamienia
+Inner Desert Stone Stair=Wewnętrzne schody z pustynnego kamienia
+Outer Desert Stone Stair=Zewnętrzne schody z pustynnego kamienia
+Desert Stone Slab=Półblok z pustynnego kamienia
+Desert Cobblestone Stair=Schody z pustynnego bruku
+Inner Desert Cobblestone Stair=Wewnętrzne schody z pustynnego bruku
+Outer Desert Cobblestone Stair=Zewnętrzne schody z pustynnego bruku
+Desert Cobblestone Slab=Półblok z pustynnego bruku
+Desert Stone Brick Stair=Schody z pustynnych kamiennych cegieł
+Inner Desert Stone Brick Stair=Wewnętrzne schody z pustynnych kamiennych cegieł
+Outer Desert Stone Brick Stair=Zewnętrzne schody z pustynnych kamiennych cegieł
+Desert Stone Brick Slab=Półblok z pustynnych kamiennych cegieł
+Desert Stone Block Stair=Schody z pustynnego kamiennego bloku
+Inner Desert Stone Block Stair=Wewnętrzne schody z pustynnego kamiennego bloku
+Outer Desert Stone Block Stair=Zewnętrzne schody z pustynnego kamiennego bloku
+Desert Stone Block Slab=Półblok z pustynnego kamiennego bloku
+Sandstone Stair=Schody z piaskowca
+Inner Sandstone Stair=Wewnętrzne schody z piaskowca
+Outer Sandstone Stair=Zewnętrzne schody z piaskowca
+Sandstone Slab=Półblok z piaskowca
+Sandstone Brick Stair=Schody z piaskowcowych cegieł
+Inner Sandstone Brick Stair=Wewnętrzne schody z piaskowcowych cegieł
+Outer Sandstone Brick Stair=Zewnętrzne schody z piaskowcowych cegieł
+Sandstone Brick Slab=Półblok z piaskowcowych cegieł
+Sandstone Block Stair=Schody z piaskowcowego bloku
+Inner Sandstone Block Stair=Wewnętrzne schody z piaskowcowego bloku
+Outer Sandstone Block Stair=Zewnętrzne schody z piaskowcowego bloku
+Sandstone Block Slab=Półblok z piaskowcowego bloku
+Desert Sandstone Stair=Schody z pustynnego piaskowca
+Inner Desert Sandstone Stair=Wewnętrzne schody z pustynnego piaskowca
+Outer Desert Sandstone Stair=Zewnętrzne schody z pustynnego piaskowca
+Desert Sandstone Slab=Półblok z pustynnego piaskowca
+Desert Sandstone Brick Stair=Schody z pustynnych piaskowcowych cegieł
+Inner Desert Sandstone Brick Stair=Wewnętrzne schody z pustynnych piaskowcowych cegieł
+Outer Desert Sandstone Brick Stair=Zewnętrzne schody z pustynnych piaskowcowych cegieł
+Desert Sandstone Brick Slab=Półblok z pustynnych piaskowcowych cegieł
+Desert Sandstone Block Stair=Schody z pustynnego piaskowcowego bloku
+Inner Desert Sandstone Block Stair=Wewnętrzne schody z pustynnego piaskowcowego bloku
+Outer Desert Sandstone Block Stair=Zewnętrzne schody z pustynnego piaskowcowego bloku
+Desert Sandstone Block Slab=Półblok z pustynnego piaskowcowego bloku
+Silver Sandstone Stair=Schody z srebrnego piaskowca
+Inner Silver Sandstone Stair=Wewnętrzne schody z srebrnego piaskowca
+Outer Silver Sandstone Stair=Zewnętrzne schody z srebrnego piaskowca
+Silver Sandstone Slab=Półblok z srebrnego piaskowca
+Silver Sandstone Brick Stair=Schody z srebrnych piaskowcowych cegieł
+Inner Silver Sandstone Brick Stair=Wewnętrzne schody z srebrnych piaskowcowych cegieł
+Outer Silver Sandstone Brick Stair=Zewnętrzne schody z srebrnych piaskowcowych cegieł
+Silver Sandstone Brick Slab=Półblok z srebrnych piaskowcowych cegieł
+Silver Sandstone Block Stair=Schody z srebrnego piaskowcowego bloku
+Inner Silver Sandstone Block Stair=Wewnętrzne schody z srebrnego piaskowcowego bloku
+Outer Silver Sandstone Block Stair=Zewnętrzne schody z srebrnego piaskowcowego bloku
+Silver Sandstone Block Slab=Półblok z srebrnego piaskowcowego bloku
+Obsidian Stair=Schody z obsydianu
+Inner Obsidian Stair=Wewnętrzne schody z obsydianu
+Outer Obsidian Stair=Zewnętrzne schody z obsydianu
+Obsidian Slab=Półblok z obsydianu
+Obsidian Brick Stair=Schody z obsydianowych cegieł
+Inner Obsidian Brick Stair=Wewnętrzne schody z obsydianowych cegieł
+Outer Obsidian Brick Stair=Zewnętrzne schody z obsydianowych cegieł
+Obsidian Brick Slab=Półblok z obsydianowych cegieł
+Obsidian Block Stair=Schody z obsydianowego bloku
+Inner Obsidian Block Stair=Wewnętrzne schody z obsydianowego bloku
+Outer Obsidian Block Stair=Zewnętrzne schody z obsydianowego bloku
+Obsidian Block Slab=Półblok z obsydianowego bloku
+Brick Stair=Schody z cegieł
+Inner Brick Stair=Wewnętrzne schody z cegieł
+Outer Brick Stair=Zewnętrzne schody z cegieł
+Brick Slab=Półblok z cegieł
+Steel Block Stair=Schody z bloku stali
+Inner Steel Block Stair=Wewnętrzne schody z bloku stali
+Outer Steel Block Stair=Zewnętrzne schody z bloku stali
+Steel Block Slab=Półblok z bloku stali
+Tin Block Stair=Schody z bloku cyny
+Inner Tin Block Stair=Wewnętrzne schody z bloku cyny
+Outer Tin Block Stair=Zewnętrzne schody z bloku cyny
+Tin Block Slab=Półblok z bloku cyny
+Copper Block Stair=Schody z bloku miedzi
+Inner Copper Block Stair=Wewnętrzne schody z bloku miedzi
+Outer Copper Block Stair=Zewnętrzne schody z bloku miedzi
+Copper Block Slab=Półblok z bloku miedzi
+Bronze Block Stair=Schody z bloku brązu
+Inner Bronze Block Stair=Wewnętrnze schody z bloku brązu
+Outer Bronze Block Stair=Zewnętrzne schody z bloku brązu
+Bronze Block Slab=Półblok z bloku brązu
+Gold Block Stair=Schody z bloku złota
+Inner Gold Block Stair=Wewnętrzne schody z block złota
+Outer Gold Block Stair=Zewnętrzne schody z bloku złota
+Gold Block Slab=Półblok z bloku złota
+Ice Stair=Schody z lodu
+Inner Ice Stair=Wewnętrzne schody z lodu
+Outer Ice Stair=Zewnętrzne schody z lodu
+Ice Slab=Półblok z lodu
+Snow Block Stair=Schody ze śniegu
+Inner Snow Block Stair=Wewnętrzne schody ze śniegu
+Outer Snow Block Stair=Zewnętrzne schody ze śniegu
+Snow Block Slab=Półblok ze śniegu
diff --git a/mods/stairs/locale/stairs.pt_BR.tr b/mods/stairs/locale/stairs.pt_BR.tr
new file mode 100644
index 00000000..41777a81
--- /dev/null
+++ b/mods/stairs/locale/stairs.pt_BR.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Escada de vidro
+Glass Slab=Placa de vidro
+Inner Glass Stair=Escada interior de vidro
+Outer Glass Stair=Escada exterior de vidro
+Obsidian Glass Stair=Escada de vidro de obsidiana
+Obsidian Glass Slab=Placa de vidro de obsidiana
+Inner Obsidian Glass Stair=Escada interior de vidro de obsidiana
+Outer Obsidian Glass Stair=Escada exterior de vidro de obsidiana
+Wooden Stair=Escada de madeira
+Inner Wooden Stair=Escada interior de madeira
+Outer Wooden Stair=Escada exterior de madeira
+Wooden Slab=Laje de madeira
+Jungle Wood Stair=Escada de madeira da selva
+Inner Jungle Wood Stair=Escada interior de madeira da selva
+Outer Jungle Wood Stair=Escada exterior de madeira da selva
+Jungle Wood Slab=Laje de madeira da selva
+Pine Wood Stair=Escada de madeira de pinho
+Inner Pine Wood Stair=Escada interior de madeira de pinho
+Outer Pine Wood Stair=Escada exterior de madeira de pinho
+Pine Wood Slab=Laje de madeira de pinho
+Acacia Wood Stair=Escada de madeira de acácia
+Inner Acacia Wood Stair=Escada interior de madeira de acácia
+Outer Acacia Wood Stair=Escada exterior de madeira de acácia
+Acacia Wood Slab=Placa de madeira de acácia
+Aspen Wood Stair=Escada de Álamo
+Inner Aspen Wood Stair=Escada interior de Álamo
+Outer Aspen Wood Stair=Escada exterior de Álamo
+Aspen Wood Slab=Laje de Álamo
+Stone Stair=Escada de Pedra
+Inner Stone Stair=Escada interior de Pedra
+Outer Stone Stair=Escada exterior de Pedra
+Stone Slab=Laje de pedra
+Cobblestone Stair=Escada de paralelepípedo
+Inner Cobblestone Stair=Escada interior de paralelepípedo
+Outer Cobblestone Stair=Escada exterior de paralelepípedo
+Cobblestone Slab=Laje de paralelepípedo
+Mossy Cobblestone Stair=Escada de paralelepípedo com musgo
+Inner Mossy Cobblestone Stair=Escada interior de paralelepípedo com musgo
+Outer Mossy Cobblestone Stair=Escada exterior de paralelepípedo com musgo
+Mossy Cobblestone Slab=Laje de paralelepípedo com musgo
+Stone Brick Stair=Escada de tijolo de pedra
+Inner Stone Brick Stair=Escada interior de tijolo de pedra
+Outer Stone Brick Stair=Escada exterior de tijolo de pedra
+Stone Brick Slab=Laje de tijolo de pedra
+Stone Block Stair=Escada Bloco de Pedra
+Inner Stone Block Stair=Escada Interna de Bloco de Pedra
+Outer Stone Block Stair=Escada Externa de Bloco de Pedra
+Stone Block Slab=Laje de bloco de pedra
+Desert Stone Stair=Escada de Pedra do Deserto
+Inner Desert Stone Stair=Escada Interior de Pedra do Deserto
+Outer Desert Stone Stair=Escada Exterior de Pedra do Deserto
+Desert Stone Slab=Laje de pedra do deserto
+Desert Cobblestone Stair=Escada de paralelepípedo do deserto
+Inner Desert Cobblestone Stair=Escada interior de paralelepípedo do deserto
+Outer Desert Cobblestone Stair=Escada exterior de paralelepípedo do deserto
+Desert Cobblestone Slab=Laje de paralelepípedo do deserto
+Desert Stone Brick Stair=Escada de tijolo de pedra do deserto
+Inner Desert Stone Brick Stair=Escada interior de tijolos de pedra do deserto
+Outer Desert Stone Brick Stair=Escada exterior de tijolos de pedra do deserto
+Desert Stone Brick Slab=Laje de tijolo de pedra do deserto
+Desert Stone Block Stair=Escada de Bloco de Pedra do Deserto
+Inner Desert Stone Block Stair=Escada interior de bloco de pedra do deserto
+Outer Desert Stone Block Stair=Escada exterior de Bloco de Pedra do Deserto
+Desert Stone Block Slab=Laje do bloco de pedra do deserto
+Sandstone Stair=Escada de arenito
+Inner Sandstone Stair=Escada interior de arenito
+Outer Sandstone Stair=Escada Exterior de Arenito
+Sandstone Slab=Laje de arenito
+Sandstone Brick Stair=Escada de tijolo de arenito
+Inner Sandstone Brick Stair=Escada interior de tijolos de arenito
+Outer Sandstone Brick Stair=Escada Exterior de Tijolo de Arenito
+Sandstone Brick Slab=Laje de tijolo de arenito
+Sandstone Block Stair=Escada do Bloco de Arenito
+Inner Sandstone Block Stair=Escada interior de bloco de arenito
+Outer Sandstone Block Stair=Escada exterior de bloco de arenito
+Sandstone Block Slab=Laje de bloco de arenito
+Desert Sandstone Stair=Escada de arenito do deserto
+Inner Desert Sandstone Stair=Escada interior de arenito do deserto
+Outer Desert Sandstone Stair=Escada exterior de arenito do deserto
+Desert Sandstone Slab=Laje de arenito do deserto
+Desert Sandstone Brick Stair=Escada de tijolos de arenito do deserto
+Inner Desert Sandstone Brick Stair=Escada interior de tijolos de arenito do deserto
+Outer Desert Sandstone Brick Stair=Escada exterior de tijolos de arenito do deserto
+Desert Sandstone Brick Slab=Laje de tijolo de arenito do deserto
+Desert Sandstone Block Stair=Escada do bloco de arenito do deserto
+Inner Desert Sandstone Block Stair=Escada interior do bloco de arenito do deserto
+Outer Desert Sandstone Block Stair=Escada exterior de bloco de arenito do deserto
+Desert Sandstone Block Slab=Laje de bloco de arenito do deserto
+Silver Sandstone Stair=Escada de arenito prateado
+Inner Silver Sandstone Stair=Escada interior de Arenito Prateado
+Outer Silver Sandstone Stair=Escada exterior de Arenito Prateado
+Silver Sandstone Slab=Laje de arenito prateado
+Silver Sandstone Brick Stair=Escada de tijolos de arenito prateado
+Inner Silver Sandstone Brick Stair=Escada interior de tijolos de arenito prateado
+Outer Silver Sandstone Brick Stair=Escada exterior de tijolos de arenito prateado
+Silver Sandstone Brick Slab=Laje de tijolo de arenito prateado
+Silver Sandstone Block Stair=Escada de blocos de arenito prateado
+Inner Silver Sandstone Block Stair=Escada interior de bloco de arenito prateado
+Outer Silver Sandstone Block Stair=Escada exterior de bloco de arenito prateado
+Silver Sandstone Block Slab=Laje de bloco de arenito prateado
+Obsidian Stair=Escada de Obsidiana
+Inner Obsidian Stair=Escada interior de Obsidiana
+Outer Obsidian Stair=Escada exterior de Obsidiana
+Obsidian Slab=Laje de Obsidiana
+Obsidian Brick Stair=Escada de Tijolos de Obsidiana
+Inner Obsidian Brick Stair=Escada interna de Tijolos de Obsidiana
+Outer Obsidian Brick Stair=Escada externa de Tijolos de Obsidiana
+Obsidian Brick Slab=Laje de tijolos de obsidiana
+Obsidian Block Stair=Escada de Bloco de Obsidiana
+Inner Obsidian Block Stair=Escada interior de Bloco de obsidiana
+Outer Obsidian Block Stair=Escada exterior de Bloco de obsidiana
+Obsidian Block Slab=Laje de bloco de obsidiana
+Brick Stair=Escada de Tijolos
+Inner Brick Stair=Escada interior de Tijolos
+Outer Brick Stair=Escada exterior de Tijolos
+Brick Slab=Laje de tijolos
+Steel Block Stair=Escada de bloco de aço
+Inner Steel Block Stair=Escada interior de bloco de aço
+Outer Steel Block Stair=Escada exterior de bloco de aço
+Steel Block Slab=Laje de bloco de aço
+Tin Block Stair=Escada de bloco de estanho
+Inner Tin Block Stair=Escada interior de bloco de estanho
+Outer Tin Block Stair=Escada exterior de bloco de lata
+Tin Block Slab=Laje de bloco de estanho
+Copper Block Stair=Escada de bloco de cobre
+Inner Copper Block Stair=Escada interior de Bloco de Cobre
+Outer Copper Block Stair=Escada exterior do Bloco de Cobre
+Copper Block Slab=Laje de bloco de cobre
+Bronze Block Stair=Escada Bloco de Bronze
+Inner Bronze Block Stair=Escada interior de bloco de bronze
+Outer Bronze Block Stair=Escada exterior de Bloco de Bronze
+Bronze Block Slab=Laje de bloco de bronze
+Gold Block Stair=Escada Bloco de Ouro
+Inner Gold Block Stair=Escada interior de Bloco de Ouro
+Outer Gold Block Stair=Escada exterior de Bloco de Ouro
+Gold Block Slab=Laje de Bloco de Ouro
+Ice Stair=Escada de gelo
+Inner Ice Stair=Escada de gelo interior
+Outer Ice Stair=Escada de gelo exterior
+Ice Slab=Laje de gelo
+Snow Block Stair=Escada Bloco de Neve
+Inner Snow Block Stair=Escada de bloco de neve interior
+Outer Snow Block Stair=Escada de bloco de neve exterior
+Snow Block Slab=Laje de bloco de neve
diff --git a/mods/stairs/locale/stairs.ru.tr b/mods/stairs/locale/stairs.ru.tr
new file mode 100644
index 00000000..2d5850ea
--- /dev/null
+++ b/mods/stairs/locale/stairs.ru.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Стеклянная Ступень
+Glass Slab=Стеклянная Плита
+Inner Glass Stair=Угловая Стеклянная Ступень (Внутренний Угол)
+Outer Glass Stair=Угловая Стеклянная Ступень (Внешний Угол)
+Obsidian Glass Stair=Стеклянная Ступень Из Обсидиана
+Obsidian Glass Slab=Стеклянная Плита Из Обсидиана
+Inner Obsidian Glass Stair=Угловая Стеклянная Ступень Из Обсидиана (Внутренний Угол)
+Outer Obsidian Glass Stair=Угловая Стеклянная Ступень Из Обсидиана (Внешний Угол)
+Wooden Stair=Яблоневая Деревянная Ступень
+Inner Wooden Stair=Угловая Яблоневая Деревянная Ступень (Внутренний Угол)
+Outer Wooden Stair=Угловая Яблоневая Деревянная Ступень (Внешний Угол)
+Wooden Slab=Яблоневая Деревянная Плита
+Jungle Wood Stair=Тропическая Деревянная Ступень
+Inner Jungle Wood Stair=Угловая Тропическая Деревянная Ступень (Внутренний Угол)
+Outer Jungle Wood Stair=Угловая Тропическая Деревянная Ступень (Внешний Угол)
+Jungle Wood Slab=Тропическая Деревянная Плита
+Pine Wood Stair=Сосновая Деревянная Ступень
+Inner Pine Wood Stair=Угловая Сосновая Деревянная Ступень (Внутренний Угол)
+Outer Pine Wood Stair=Угловая Сосновая Деревянная Ступень (Внешний Угол)
+Pine Wood Slab=Сосновая Деревянная Плита
+Acacia Wood Stair=Деревянная Ступень Из Акации
+Inner Acacia Wood Stair=Угловая Деревянная Ступень Из Акации (Внутренний Угол)
+Outer Acacia Wood Stair=Угловая Деревянная Ступень Из Акации (Внешний Угол)
+Acacia Wood Slab=Деревянная Плита Из Акации
+Aspen Wood Stair=Осиновая Деревянная Ступень
+Inner Aspen Wood Stair=Угловая Осиновая Деревянная Ступень (Внутренний Угол)
+Outer Aspen Wood Stair=Угловая осиновая Деревянная Ступень (Внешний Угол)
+Aspen Wood Slab=Осиновая Деревянная Плита
+Stone Stair=Каменная Ступень
+Inner Stone Stair=Угловая Каменная Ступень (Внутренний Угол)
+Outer Stone Stair=Угловая Каменная Ступень (Внешний Угол)
+Stone Slab=Каменная Плита
+Cobblestone Stair=Булыжниковая Ступень
+Inner Cobblestone Stair=Угловая Булыжниковая Ступень (Внутренний Угол)
+Outer Cobblestone Stair=Угловая Булыжниковая Ступень (Внешний Угол)
+Cobblestone Slab=Булыжниковая Плита
+Mossy Cobblestone Stair=Мшистая Булыжниковая Ступень
+Inner Mossy Cobblestone Stair=Угловая Мшистая Булыжниковая Ступень (Внутренний Угол)
+Outer Mossy Cobblestone Stair=Угловая Мшистая Булыжниковая Ступень (Внешний Угол)
+Mossy Cobblestone Slab=Мшистая Булыжниковая Плита
+Stone Brick Stair=Cтупень Из Каменных Кирпичей
+Inner Stone Brick Stair=Угловая Ступень Из Каменных Кирпичей (Внутренний Угол)
+Outer Stone Brick Stair=Угловая Ступень Из Каменных Кирпичей (Внешний Угол)
+Stone Brick Slab=Плита Из Каменных Кирпичей
+Stone Block Stair=Ступень Из Каменного Блока
+Inner Stone Block Stair=Угловая Ступень Из Каменного Блока (Внутренний Угол)
+Outer Stone Block Stair=Угловая Ступень Из Каменного Блока (Внешний Угол)
+Stone Block Slab=Плита Из Каменного Блока
+Desert Stone Stair=Ступень Из Пустынного Камня
+Inner Desert Stone Stair=Угловая Ступень Из Пустынного Камня (Внутренний Угол)
+Outer Desert Stone Stair=Угловая Ступень Из Пустынного Камня (Внешний Угол)
+Desert Stone Slab=Плита Из Пустынного Камня
+Desert Cobblestone Stair=Ступень Из Пустынного Булыжника
+Inner Desert Cobblestone Stair=Угловая Ступень Из Пустынного Булыжника (Внутренний Угол)
+Outer Desert Cobblestone Stair=Угловая Ступень Из Пустынного Булыжника (Внешний Угол)
+Desert Cobblestone Slab=Плита Из Пустынного Камня
+Desert Stone Brick Stair=Ступень Из Кирпичей Пустынного Камня
+Inner Desert Stone Brick Stair=Угловая Ступень Из Кирпичей Пустынного Камня (Внутренний Угол)
+Outer Desert Stone Brick Stair=Угловая Ступень Из Кирпичей Пустынного Камня (Внешний Угол)
+Desert Stone Brick Slab=Плита Из Кирпичей Пустынного Камня
+Desert Stone Block Stair=Ступень Из Пустынного Каменного Блока
+Inner Desert Stone Block Stair=Угловая Ступень Из Пустынного Каменного Блока (Внутренний Угол)
+Outer Desert Stone Block Stair=Угловая Ступень Из Пустынного Каменного Блока (Внешний Угол)
+Desert Stone Block Slab=Плита Из Пустынного Каменного Блока
+Sandstone Stair=Песчаниковая Ступень
+Inner Sandstone Stair=Угловая Песчаниковая Ступень (Внутренний Угол)
+Outer Sandstone Stair=Угловая Песчаниковая Ступень (Внешний Угол)
+Sandstone Slab=Песчаниковая Плита
+Sandstone Brick Stair=Ступень Из Песчаниковых Кирпичей
+Inner Sandstone Brick Stair=Угловая Ступень Из Песчаниковых Кирпичей (Внутренний Угол)
+Outer Sandstone Brick Stair=Угловая Ступень Из Песчаниковых Кирпичей (Внешний Угол)
+Sandstone Brick Slab=Плита Из Песчаниковых Кирпичей
+Sandstone Block Stair=Ступень Из Песчаникового Блока
+Inner Sandstone Block Stair=Угловая Ступень Из Песчаникового Блока (Внутренний Угол)
+Outer Sandstone Block Stair=Угловая Ступень Из Песчаникового Блока (Внешний Угол)
+Sandstone Block Slab=Плита Из Песчаникового Блока
+Desert Sandstone Stair=Ступень Из Пустынного Песчаника
+Inner Desert Sandstone Stair=Угловая Ступень Из Пустынного Песчаника (Внутренний Угол)
+Outer Desert Sandstone Stair=Угловая Ступень Из Пустынного Песчаника (Внешний Угол)
+Desert Sandstone Slab=Плита Из Пустынного Песчаника
+Desert Sandstone Brick Stair=Ступень Из Кирпичей Пустынного Песчаника
+Inner Desert Sandstone Brick Stair=Угловая Ступень Из Кирпичей Пустынного Песчаника (Внутренний Угол)
+Outer Desert Sandstone Brick Stair=Угловая Ступень Из Кирпичей Пустынного Песчаника (Внешний Угол)
+Desert Sandstone Brick Slab=Плита Из Кирпичей Пустынного Песчаника
+Desert Sandstone Block Stair=Ступень Из Пустынного Песчаникового Блока
+Inner Desert Sandstone Block Stair=Угловая Ступень Из Пустынного Песчаникового Блока (Внутренний Угол)
+Outer Desert Sandstone Block Stair=Угловая Ступень Из Пустынного Песчаникового Блока (Внешний Угол)
+Desert Sandstone Block Slab=Плита Из Пустынного Песчаникового Блока
+Silver Sandstone Stair=Ступень Из Серебрянного Песчаника
+Inner Silver Sandstone Stair=Угловая Ступень Из Серебряного Песчаника (Внутренний Угол)
+Outer Silver Sandstone Stair=Угловая Ступень Из Серебряного Песчаника (Внешний Угол)
+Silver Sandstone Slab=Плита Из Серебряного Песчаника
+Silver Sandstone Brick Stair=Ступень Из Кирпичей Серебряного Песчаника
+Inner Silver Sandstone Brick Stair=Угловая Ступень Из Кирпичей Серебряного Песчаника (Внутренний Угол)
+Outer Silver Sandstone Brick Stair=Угловая Ступень Из Кирпичей Серебряного Песчаника (Внешний Угол)
+Silver Sandstone Brick Slab=Плита Из Кирпичей Серебряного Песчаника
+Silver Sandstone Block Stair=Ступень Из Серебряного Песчаникового Блока
+Inner Silver Sandstone Block Stair=Угловая Ступень Из Серебряного Песчаникового Блока (Внутренний Угол)
+Outer Silver Sandstone Block Stair=Угловая Ступень Из Серебряного Песчаникового Блока (Внешний Угол)
+Silver Sandstone Block Slab=Плита Из Серебряного Песчаникового Блока
+Obsidian Stair=Обсидиановая Ступень
+Inner Obsidian Stair=Угловая Обсидиановая Ступень (Внутренний Угол)
+Outer Obsidian Stair=Угловая Обсидиановая Ступень (Внешний Угол)
+Obsidian Slab=Обсидиановая Плита
+Obsidian Brick Stair=Ступень Из Обсидиановых Кирпичей
+Inner Obsidian Brick Stair=Угловая Ступень Из Обсидиановых Кирпичей (Внутренний Угол)
+Outer Obsidian Brick Stair=Угловая Ступень Из Обсидиановых Кирпичей (Внешний Угол)
+Obsidian Brick Slab=Плита Из Обсидиановых Кирпичей
+Obsidian Block Stair=Ступень Из Обсидианового Блока
+Inner Obsidian Block Stair=Угловая Ступень Из Обсидианового Блока (Внутренний Угол)
+Outer Obsidian Block Stair=Угловая Ступень Из Обсидианового Блока (Внешний Угол)
+Obsidian Block Slab=Плита Из Обсидианового Блока
+Brick Stair=Ступень Из Кирпичей
+Inner Brick Stair=Угловая Ступень Из Кирпичей (Внутренний Угол)
+Outer Brick Stair=Угловая Ступень Из Кирпичей (Внешний Угол)
+Brick Slab=Плита Из Кирпичей
+Steel Block Stair=Ступень Из Стального Блока
+Inner Steel Block Stair=Угловая Ступень Из Стального Блока (Внутренний Угол)
+Outer Steel Block Stair=Угловая Ступень Из Стального Блока (Внешний Угол)
+Steel Block Slab=Плита Из Стального Блока
+Tin Block Stair=Ступень Из Оловянного Блока
+Inner Tin Block Stair=Угловая Ступень Из Оловянного Блока (Внутренний Угол)
+Outer Tin Block Stair=Угловая Ступень Из Оловянного Блока (Внешний Угол)
+Tin Block Slab=Плита Из Оловянного Блока
+Copper Block Stair=Ступень Из Медного Блока
+Inner Copper Block Stair=Угловая Ступень Из Медного Блока (Внутренний Угол)
+Outer Copper Block Stair=Угловая Ступень Из Медного Блока (Внешний Угол)
+Copper Block Slab=Плита Из Медного Блока
+Bronze Block Stair=Ступень Из Бронзового Блока
+Inner Bronze Block Stair=Угловая Ступень Из Бронзового Блока (Внутренний Угол)
+Outer Bronze Block Stair=Угловая Ступень Из Бронзового Блока (Внешний Угол)
+Bronze Block Slab=Плита Из Бронзового Блока
+Gold Block Stair=Ступень Из Золотого Блока
+Inner Gold Block Stair=Угловая Ступень Из Золотого Блока (Внутренний Угол)
+Outer Gold Block Stair=Угловая Ступень Из Золотого Блока (Внешний Угол)
+Gold Block Slab=Плита Из Золотого Блока
+Ice Stair=Ледяная Ступень
+Inner Ice Stair=Угловая Ледяная Ступень (Внутренний Угол)
+Outer Ice Stair=Угловая Ледяная Ступень (Внешний Угол)
+Ice Slab=Ледяная Плита
+Snow Block Stair=Ступень Из Снежного Блока
+Inner Snow Block Stair=Угловая Ступень Из Снежного Блока (Внутренний Угол)
+Outer Snow Block Stair=Угловая Ступень Из Снежного Блока (Внешний Угол)
+Snow Block Slab=Плита Из Снежного Блока
diff --git a/mods/stairs/locale/stairs.sk.tr b/mods/stairs/locale/stairs.sk.tr
new file mode 100644
index 00000000..b006fdb5
--- /dev/null
+++ b/mods/stairs/locale/stairs.sk.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Sklenené schod
+Glass Slab=Sklenený stupienok
+Inner Glass Stair=Vnútorný sklenené schod
+Outer Glass Stair=Vonkajší sklenené schod
+Obsidian Glass Stair=Obsidiánové sklenené schod
+Obsidian Glass Slab=Obsidiánový sklenený stupienok
+Inner Obsidian Glass Stair=Vnútorný obsidiánové sklenené schod
+Outer Obsidian Glass Stair=Vonkajší obsidiánové sklenené schod
+Wooden Stair=Drevené schod
+Inner Wooden Stair=Vnútorný drevené schod
+Outer Wooden Stair=Vonkajší drevené schod
+Wooden Slab=Drevený stupienok
+Jungle Wood Stair=Drevené schod z džungľového dreva
+Inner Jungle Wood Stair=Vnútorný drevené schod z džungľového dreva
+Outer Jungle Wood Stair=Vonkajší drevené schod z džungľového dreva
+Jungle Wood Slab=Stupienok z džungľového dreva
+Pine Wood Stair=Drevené schod z borovicového dreva
+Inner Pine Wood Stair=Vnútorný drevené schod z borovicového dreva
+Outer Pine Wood Stair=Vonkajší drevené schod z borovicového dreva
+Pine Wood Slab=Stupienok z borovicového dreva
+Acacia Wood Stair=Drevené schod z akáciového dreva
+Inner Acacia Wood Stair=Vnútorný drevené schod z akáciového dreva
+Outer Acacia Wood Stair=Vonkajší drevené schod z akáciového dreva
+Acacia Wood Slab=Stupienok z akáciového dreva
+Aspen Wood Stair=Drevené schod z osiky
+Inner Aspen Wood Stair=Vnútorný drevené schod z osiky
+Outer Aspen Wood Stair=Vonkajší drevené schod z osiky
+Aspen Wood Slab=Stupienok z osiky
+Stone Stair=Kamenné schod
+Inner Stone Stair=Vnútorný kamenné schod
+Outer Stone Stair=Vonkajší kamenné schod
+Stone Slab=Kamenný stupienok
+Cobblestone Stair=Schod z dlažbového kameňa
+Inner Cobblestone Stair=Vnútorný schod z dlažbového kameňa
+Outer Cobblestone Stair=Vonkajší schod z dlažbového kameňa
+Cobblestone Slab=Stupienok z dlažbového kameňa
+Mossy Cobblestone Stair=Schod dlažbového kameňa obrastené machom
+Inner Mossy Cobblestone Stair=Vnútorný schod dlažbového kameňa obrastené machom
+Outer Mossy Cobblestone Stair=Vonkajší schod dlažbového kameňa obrastené machom
+Mossy Cobblestone Slab=Stupienok z dlažbového kameňa obrastený machom
+Stone Brick Stair=Schod z kamenných tehál
+Inner Stone Brick Stair=Vnútorný schod z kamenných tehál
+Outer Stone Brick Stair=Vonkajší schod z kamenných tehál
+Stone Brick Slab=Stupienok z kamenných tehál
+Stone Block Stair=Schod z kameňa
+Inner Stone Block Stair=Vnútorný schod z kameňa
+Outer Stone Block Stair=Vonkajší schod z kameňa
+Stone Block Slab=Stupienok z kameňa
+Desert Stone Stair=Schod z púštneho kameňa
+Inner Desert Stone Stair=Vnútorný schod z púštneho kameňa
+Outer Desert Stone Stair=Vonkajší schod z púštneho kameňa
+Desert Stone Slab=Stupienok z púštneho kameňa
+Desert Cobblestone Stair=Schod z púštneho dlažbového kameňa
+Inner Desert Cobblestone Stair=Vnútorný schod z púštneho dlažbového kameňa
+Outer Desert Cobblestone Stair=Vonkajší schod z púštneho dlažbového kameňa
+Desert Cobblestone Slab=Stupienok z púštneho dlažbového kameňa
+Desert Stone Brick Stair=Schod z tehiel z púštneho kameňa
+Inner Desert Stone Brick Stair=Vnútorný schod z tehiel z púštneho kameňa
+Outer Desert Stone Brick Stair=Vonkajší schod z tehiel z púštneho kameňa
+Desert Stone Brick Slab=Stupienok z tehiel z púštneho kameňa
+Desert Stone Block Stair=Schod z blokov púštneho kameňa
+Inner Desert Stone Block Stair=Vnútorný schod z blokov púštneho kameňa
+Outer Desert Stone Block Stair=Vonkajší schod z blokov púštneho kameňa
+Desert Stone Block Slab=Stupienok z blokov púštneho kameňa
+Sandstone Stair=Schod z pieskovca
+Inner Sandstone Stair=Vnútorný schod z pieskovca
+Outer Sandstone Stair=Vonkajší schod z pieskovca
+Sandstone Slab=Stupienok z pieskovca
+Sandstone Brick Stair=Schod z tehál pieskovca
+Inner Sandstone Brick Stair=Vnútorný schod z tehál pieskovca
+Outer Sandstone Brick Stair=Vonkajší schod z tehál pieskovca
+Sandstone Brick Slab=Stupienok z tehál pieskovca
+Sandstone Block Stair=Schod z blokov pieskovca
+Inner Sandstone Block Stair=Vnútorný schod z blokov pieskovca
+Outer Sandstone Block Stair=Vonkajší schod z blokov pieskovca
+Sandstone Block Slab=Stupienok z blokov pieskovca
+Desert Sandstone Stair=Schod z púštneho pieskovca
+Inner Desert Sandstone Stair=Vnútorný schod z púštneho pieskovca
+Outer Desert Sandstone Stair=Vonkajší schod z púštneho pieskovca
+Desert Sandstone Slab=Stupienok z púštneho pieskovca
+Desert Sandstone Brick Stair=Schod z tehál z púštneho pieskovca
+Inner Desert Sandstone Brick Stair=Vnútorný schod z tehál z púštneho pieskovca
+Outer Desert Sandstone Brick Stair=Vonkajší schod z tehál z púštneho pieskovca
+Desert Sandstone Brick Slab=Stupienok z tehál z púštneho pieskovca
+Desert Sandstone Block Stair=Schod z blokov púštneho pieskovca
+Inner Desert Sandstone Block Stair=Vnútorný schod z blokov púštneho pieskovca
+Outer Desert Sandstone Block Stair=Vonkajší schod z blokov púštneho pieskovca
+Desert Sandstone Block Slab=Stupienok z blokov púštneho pieskovca
+Silver Sandstone Stair=Schod zo strieborného pieskovca
+Inner Silver Sandstone Stair=Vnútorný schod zo strieborného pieskovca
+Outer Silver Sandstone Stair=Vonkajší schod zo strieborného pieskovca
+Silver Sandstone Slab=Stupienok zo strieborného pieskovca
+Silver Sandstone Brick Stair=Schod z tehál zo strieborného pieskovca
+Inner Silver Sandstone Brick Stair=Vnútorný schod z tehál zo strieborného pieskovca
+Outer Silver Sandstone Brick Stair=Vonkajší schod z tehál zo strieborného pieskovca
+Silver Sandstone Brick Slab=Stupienok z tehál zo strieborného pieskovca
+Silver Sandstone Block Stair=Schod z blokov strieborného pieskovca
+Inner Silver Sandstone Block Stair=Vnútorný schod z blokov strieborného pieskovca
+Outer Silver Sandstone Block Stair=Vonkajší schod z blokov strieborného pieskovca
+Silver Sandstone Block Slab=Stupienok z blokov strieborného pieskovca
+Obsidian Stair=Schod z obsidiánu
+Inner Obsidian Stair=Vnútorný schod z obsidiánu
+Outer Obsidian Stair=Vonkajší schod z obsidiánu
+Obsidian Slab=Stupienok z obsidiánu
+Obsidian Brick Stair=Schod z tehál obsidiánu
+Inner Obsidian Brick Stair=Vnútorný schod z tehál obsidiánu
+Outer Obsidian Brick Stair=Vonkajší schod z tehál obsidiánu
+Obsidian Brick Slab=Stupienok z tehál obsidiánu
+Obsidian Block Stair=Schod z bloku obsidiánu
+Inner Obsidian Block Stair=Vnútorný schod z bloku obsidiánu
+Outer Obsidian Block Stair=Vonkajší schod z bloku obsidiánu
+Obsidian Block Slab=Stupienok z bloku obsidiánu
+Brick Stair=Schod z tehál
+Inner Brick Stair=Vnútorný schod z tehál
+Outer Brick Stair=Vonkajší schod z tehál
+Brick Slab=Stupienok z tehál
+Steel Block Stair=Oceľový schod
+Inner Steel Block Stair=Vnútorný oceľový schod
+Outer Steel Block Stair=Vonkajší oceľový schod
+Steel Block Slab=Oceľový stupienok
+Tin Block Stair=Cínový schod
+Inner Tin Block Stair=Vnútorný cínový schod
+Outer Tin Block Stair=Vonkajší cínový schod
+Tin Block Slab=Cínový stupienok
+Copper Block Stair=Medený schod
+Inner Copper Block Stair=Vnútorný medený schod
+Outer Copper Block Stair=Vonkajší medený schod
+Copper Block Slab=Medený stupienok
+Bronze Block Stair=Bronzový schod
+Inner Bronze Block Stair=Vnútorný bronzový schod
+Outer Bronze Block Stair=Vonkajší bronzový schod
+Bronze Block Slab=Bronzový stupienok
+Gold Block Stair=Zlatý schod
+Inner Gold Block Stair=Vnútorný zlatý schod
+Outer Gold Block Stair=Vonkajší zlatý schod
+Gold Block Slab=Zlatý stupienok
+Ice Stair=Ľadový schod
+Inner Ice Stair=Vnútorný ľadový schod
+Outer Ice Stair=Vonkajší ľadový schod
+Ice Slab=Ľadový stupienok
+Snow Block Stair=Snehový schod
+Inner Snow Block Stair=Vnútorný snehový schod
+Outer Snow Block Stair=Vonkajší snehový schod
+Snow Block Slab=Snehový stupienok
diff --git a/mods/stairs/locale/stairs.sv.tr b/mods/stairs/locale/stairs.sv.tr
new file mode 100644
index 00000000..8044af81
--- /dev/null
+++ b/mods/stairs/locale/stairs.sv.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Glastrappa
+Glass Slab=Glasplatta
+Inner Glass Stair=Inre glasstrappa
+Outer Glass Stair=Yttre glasstrappa
+Obsidian Glass Stair=Obsidianglasstrappa
+Obsidian Glass Slab=Obsidianglasplatta
+Inner Obsidian Glass Stair=Inre obsidianglastrappa
+Outer Obsidian Glass Stair=Yttre obsidianglastrappa
+Wooden Stair=Trätrappa
+Inner Wooden Stair=Inre trätrappa
+Outer Wooden Stair=Yttre trätrappa
+Wooden Slab=Träplatta
+Jungle Wood Stair=Djungelträtrappa
+Inner Jungle Wood Stair=Inre djungelträtrappa
+Outer Jungle Wood Stair=Ytter djungelträtrappa
+Jungle Wood Slab=Djungelträplatta
+Pine Wood Stair=Tallträplatta
+Inner Pine Wood Stair=Inre tallträplatta
+Outer Pine Wood Stair=Ytter tallträplatta
+Pine Wood Slab=Tallskiva
+Acacia Wood Stair=Acaciatrappa
+Inner Acacia Wood Stair=Inre acaciatrappa
+Outer Acacia Wood Stair=Yttre acaciatrappa
+Acacia Wood Slab=Acaciaplatta
+Aspen Wood Stair=Aspträtrappa
+Inner Aspen Wood Stair=Inre aspträtrappa
+Outer Aspen Wood Stair=Yttre aspträtrappa
+Aspen Wood Slab=Aspträplatta
+Stone Stair=Stentrappa
+Inner Stone Stair=Inre stentrappa
+Outer Stone Stair=Yttre stentrappa
+Stone Slab=Stenplatta
+Cobblestone Stair=Kullerstenstrappa
+Inner Cobblestone Stair=Inre kullerstenstrappa
+Outer Cobblestone Stair=Yttre kullerstenstrappa
+Cobblestone Slab=Kullerstenplatta
+Mossy Cobblestone Stair=Mossig kullerstenstrappa
+Inner Mossy Cobblestone Stair=Inre mossig kullerstenstrappa
+Outer Mossy Cobblestone Stair=Yttre mossig kullerstenstrappa
+Mossy Cobblestone Slab=Mossig kullerstenplatta
+Stone Brick Stair=Stentegeltrappa
+Inner Stone Brick Stair=Inre stentegeltrappa
+Outer Stone Brick Stair=Yttre stentegeltrappa
+Stone Brick Slab=Stentegelplatta
+Stone Block Stair=Stenblockstrappa
+Inner Stone Block Stair=Inre stenblockstrappa
+Outer Stone Block Stair=Yttre stenblockstrappa
+Stone Block Slab=Stenblocksplatta
+Desert Stone Stair=Ökenstentrappa
+Inner Desert Stone Stair=Inre ökenstentrappa
+Outer Desert Stone Stair=Yttre ökenstentrappa
+Desert Stone Slab=Ökenstenplatta
+Desert Cobblestone Stair=Ökenkullerstenstrappa
+Inner Desert Cobblestone Stair=Inre ökenkullerstenstrappa
+Outer Desert Cobblestone Stair=Yttre ökenkullerstenstrappa
+Desert Cobblestone Slab=Ökenkullerstensplatta
+Desert Stone Brick Stair=Ökentegelstenstrappa
+Inner Desert Stone Brick Stair=Inre Ökentegelstenstrappa
+Outer Desert Stone Brick Stair=Yttre Ökentegelstenstrappa
+Desert Stone Brick Slab=Ökentegelstensplatta
+Desert Stone Block Stair=Ökenstenblockstrappa
+Inner Desert Stone Block Stair=Inre ökenstenblockstrappa
+Outer Desert Stone Block Stair=Yttre ökenstenblockstrappa
+Desert Stone Block Slab=Ökenstenblocksplatta
+Sandstone Stair=Sandstenstrappa
+Inner Sandstone Stair=Inre Sandstenstrappa
+Outer Sandstone Stair=Yttre Sandstenstrappa
+Sandstone Slab=Sandstenplatta
+Sandstone Brick Stair=Sandstentegeltrappa
+Inner Sandstone Brick Stair=Inre Sandstentegeltrappa
+Outer Sandstone Brick Stair=Yttre Sandstentegeltrappa
+Sandstone Brick Slab=Sandstentegelplatta
+Sandstone Block Stair=Sandstenblockstrappa
+Inner Sandstone Block Stair=Inre Sandstenblockstrappa
+Outer Sandstone Block Stair=Yttre Sandstenblockstrappa
+Sandstone Block Slab=Sandstenblocksplatta
+Desert Sandstone Stair=Ökensandstenstrappa
+Inner Desert Sandstone Stair=Inre ökensandstenstrappa
+Outer Desert Sandstone Stair=Yttre ökensandstenstrappa
+Desert Sandstone Slab=Ökensandstensplatta
+Desert Sandstone Brick Stair=Ökensandstentegeltrappa
+Inner Desert Sandstone Brick Stair=Inre ökensandstentegeltrappa
+Outer Desert Sandstone Brick Stair=Yttre ökensandstentegeltrappa
+Desert Sandstone Brick Slab=Ökensandstentegelplatta
+Desert Sandstone Block Stair=Ökensandstentegeltrappa
+Inner Desert Sandstone Block Stair=Inre ökensandstentegeltrappa
+Outer Desert Sandstone Block Stair=Yttre ökensandstentegeltrappa
+Desert Sandstone Block Slab=Ökensandstentegelplatta
+Silver Sandstone Stair=Silversandstenstrappa
+Inner Silver Sandstone Stair=Inre silversandstenstrappa
+Outer Silver Sandstone Stair=Yttre silversandstenstrappa
+Silver Sandstone Slab=Silversandstenstrappa
+Silver Sandstone Brick Stair=Silversandstenstegeltrappa
+Inner Silver Sandstone Brick Stair=Inre silversandstenstegeltrappa
+Outer Silver Sandstone Brick Stair=Yttre silversandstenstegeltrappa
+Silver Sandstone Brick Slab=Silversandstenstegelplatta
+Silver Sandstone Block Stair=Silversandstenblockstrappa
+Inner Silver Sandstone Block Stair=Inre silversandstenblockstrappa
+Outer Silver Sandstone Block Stair=Yttre silversandstenblockstrappa
+Silver Sandstone Block Slab=Silversandstenblocksplatta
+Obsidian Stair=Obsidiantrappa
+Inner Obsidian Stair=Inre obsidiantrappa
+Outer Obsidian Stair=Yttre obsidiantrappa
+Obsidian Slab=Obsidianplatta
+Obsidian Brick Stair=Obsidiantegeltrappa
+Inner Obsidian Brick Stair=Inre obsidiantegeltrappa
+Outer Obsidian Brick Stair=Yttre obsidiantegeltrappa
+Obsidian Brick Slab=Obsidiantegelplatta
+Obsidian Block Stair=Obsidianblocktrappa
+Inner Obsidian Block Stair=Inre Obsidianblocktrappa
+Outer Obsidian Block Stair=Yttre Obsidianblocktrappa
+Obsidian Block Slab=Obsidianblockplatta
+Brick Stair=Tegeltrappa
+Inner Brick Stair=Inre tegeltrappa
+Outer Brick Stair=Yttre tegeltrappa
+Brick Slab=Tegelplatta
+Steel Block Stair=Ståltrappa
+Inner Steel Block Stair=Inre ståltrappa
+Outer Steel Block Stair=Yttre ståltrappa
+Steel Block Slab=Stålplatta
+Tin Block Stair=Tenntrappa
+Inner Tin Block Stair=Inre tenntrappa
+Outer Tin Block Stair=Yttre tenntrappa
+Tin Block Slab=Tennplatta
+Copper Block Stair=Koppartrappa
+Inner Copper Block Stair=Inre koppartrappa
+Outer Copper Block Stair=Yttre koppartrappa
+Copper Block Slab=Kopparplatta
+Bronze Block Stair=Bronstrappa
+Inner Bronze Block Stair=Inre bronstrappa
+Outer Bronze Block Stair=Yttre bronstrappa
+Bronze Block Slab=Bronsplatta
+Gold Block Stair=Guldtrappa
+Inner Gold Block Stair=Inre guldtrappa
+Outer Gold Block Stair=Yttre guldtrappa
+Gold Block Slab=Guldplatta
+Ice Stair=Istrappa
+Inner Ice Stair=Inre istrappa
+Outer Ice Stair=Yttre istrappa
+Ice Slab=Isplatta
+Snow Block Stair=Snöblockstrappa
+Inner Snow Block Stair=Inre snöblockstrappa
+Outer Snow Block Stair=Yttre snöblockstrappa
+Snow Block Slab=Snöblocksplatta
diff --git a/mods/stairs/locale/stairs.uk.tr b/mods/stairs/locale/stairs.uk.tr
new file mode 100644
index 00000000..f501b5eb
--- /dev/null
+++ b/mods/stairs/locale/stairs.uk.tr
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=Скляна Сходинка
+Glass Slab=Скляна Плита
+Inner Glass Stair=Кутова Скляна Сходинка (Внутрішній Кут)
+Outer Glass Stair=Кутова Скляна Сходинка (Зовнішній Кут)
+Obsidian Glass Stair=Скляна Сходинка З Обсидіану
+Obsidian Glass Slab=Скляна Плита З Обсидіану
+Inner Obsidian Glass Stair=Кутова Скляна Сходинка З Обсидіану (Внутрішній Кут)
+Outer Obsidian Glass Stair=Кутова Скляна Сходинка З Обсидіану (Зовнішній Кут)
+Wooden Stair=Яблунева Дерев'яна Сходинка
+Inner Wooden Stair=Кутова Яблунева Дерев'яна Сходинка (Внутрішній Кут)
+Outer Wooden Stair=Кутова Яблунева Дерев'яна Сходинка (Внутрішній Кут)
+Wooden Slab=Яблунева Дерев'яна Плита
+Jungle Wood Stair=Дерев'яна Сходинка З Тропічного Дерева
+Inner Jungle Wood Stair=Кутова Дерев'яна Сходинка З Тропічного Дерева (Внутрішній Кут)
+Outer Jungle Wood Stair=Кутова Дерев'яна Сходинка З Тропічного Дерева (Зовнішній Кут)
+Jungle Wood Slab=Дерев'яна Плита З Тропічного Дерева
+Pine Wood Stair=Дерев'яна Сходинка З Сосни
+Inner Pine Wood Stair=Кутова Дерев'яна Сходинка З Сосни (Внутрішній Кут)
+Outer Pine Wood Stair=Кутова Дерев'яна Сходинка З Сосни (Зовнішній Кут)
+Pine Wood Slab=Дерев'яна Плита З Сосни
+Acacia Wood Stair=Дерев'яна Сходинка З Акації
+Inner Acacia Wood Stair=Кутова Дерев'яна Сходинка З Акації (Внутрішній Кут)
+Outer Acacia Wood Stair=Кутова Дерев'яна Сходинка З Акації (Зовнішній Кут)
+Acacia Wood Slab=Дерев'яна Плита З Акації
+Aspen Wood Stair=Дерев'яна Сходинка З Осики
+Inner Aspen Wood Stair=Кутова Дерев'яна Сходинка З Осики (Внутрішній Кут)
+Outer Aspen Wood Stair=Кутова Дерев'яна Сходинка З Осики (Зовнішній Кут)
+Aspen Wood Slab=Дерев'яна Плита З Осики
+Stone Stair=Кам'яна Сходинка
+Inner Stone Stair=Кутова Кам'яна Сходинка (Внутрішній Кут)
+Outer Stone Stair=Кутова Кам'яна Сходинка (Зовнішній Кут)
+Stone Slab=Кам'яна Плита
+Cobblestone Stair=Сходинка З Кругляку
+Inner Cobblestone Stair=Кутова Сходинка З Кругляку (Внутрішній Кут)
+Outer Cobblestone Stair=Кутова Сходинка З Кругляку (Зовнішній Кут)
+Cobblestone Slab=Плита З Кругляку
+Mossy Cobblestone Stair=Мохова Сходинка З Кругляку
+Inner Mossy Cobblestone Stair=Кутова Мохова Сходинка З Кругляку (Внутрішній Кут)
+Outer Mossy Cobblestone Stair=Кутова Мохова Сходинка З Кругляку (Зовнішній Кут)
+Mossy Cobblestone Slab=Мохова Плита З Кругляку
+Stone Brick Stair=Сходинка З Кам'яної Цегли
+Inner Stone Brick Stair=Кутова Сходинка З Кам'яної Цегли (Внутрішній Кут)
+Outer Stone Brick Stair=Кутова Сходинка З Кам'яної Цегли (Зовнішній Кут)
+Stone Brick Slab=Плита З Кам'яної Цегли
+Stone Block Stair=Сходинка З Кам'яного Блоку
+Inner Stone Block Stair=Кутова Сходинка З Кам'яного Блоку (Внутрішній Кут)
+Outer Stone Block Stair=Кутова Сходинка З Кам'яного Блоку (Зовнішній Кут)
+Stone Block Slab=Плита З Кам'яного Блоку
+Desert Stone Stair=Сходинка З Пустельного Каменю
+Inner Desert Stone Stair=Кутова Сходинка З Пустельного Каменю (Внутрішній Кут)
+Outer Desert Stone Stair=Кутова Сходинка З Пустельного Каменю (Зовнішній Кут)
+Desert Stone Slab=Плита З Пустельного Каменю
+Desert Cobblestone Stair=Сходинка З Пустельного Кругляку
+Inner Desert Cobblestone Stair=Кутова Сходинка З Пустельного Кругляку (Внутрішній Кут)
+Outer Desert Cobblestone Stair=Кутова Сходинка З Пустельного Кругляку (Зовнішній Кут)
+Desert Cobblestone Slab=Плита З Пустельного Каменю
+Desert Stone Brick Stair=Сходинка З Цегли Із Пустельного Каменю
+Inner Desert Stone Brick Stair=Кутова Сходинка З Цегли Із Пустельного Каменю (Внутрішній Кут)
+Outer Desert Stone Brick Stair=Кутова Сходинка З Цегли Із Пустельного Каменю (Зовнішній Кут)
+Desert Stone Brick Slab=Плита З Цегли Із Пустельного Каменю
+Desert Stone Block Stair=Сходинка З Пустельного Кам'яного Блоку
+Inner Desert Stone Block Stair=Кутова Сходинка З Пустельного Кам'яного Блоку (Внутрішній Кут)
+Outer Desert Stone Block Stair=Кутова Сходинка З Пустельного Кам'яного Блоку (Зовнішній Кут)
+Desert Stone Block Slab=Плита З Пустельного Кам'яного Блоку
+Sandstone Stair=Сходинка З Піщанику
+Inner Sandstone Stair=Кутова Сходинка З Піщанику (Внутрішній Кут)
+Outer Sandstone Stair=Кутова Сходинка З Піщанику (Зовнішній Кут)
+Sandstone Slab=Плита З Піщанику
+Sandstone Brick Stair=Сходинка З Цегли Із Піщанику
+Inner Sandstone Brick Stair=Кутова Сходинка З Цегли Із Піщанику (Внутрішній Кут)
+Outer Sandstone Brick Stair=Кутова Сходинка З Цегли Із Піщанику (Зовнішній Кут)
+Sandstone Brick Slab=Плита З Цегли Із Піщанику
+Sandstone Block Stair=Сходинка З Блоку Із Піщанику
+Inner Sandstone Block Stair=Кутова Сходинка З Блоку Із Піщанику (Внутрішній Кут)
+Outer Sandstone Block Stair=Кутова Сходинка З Блоку Із Піщанику (Зовнішній Кут)
+Sandstone Block Slab=Плита З Блоку Із Піщанику
+Desert Sandstone Stair=Сходинка З Пустельного Піщанику
+Inner Desert Sandstone Stair=Кутова Сходинка З Пустельного Піщанику (Внутрішній Кут)
+Outer Desert Sandstone Stair=Кутова Сходинка З Пустельного Піщанику (Зовнішній Кут)
+Desert Sandstone Slab=Плита З Пустельного Піщанику
+Desert Sandstone Brick Stair=Сходинка З Цегли Із Пустельного Піщанику
+Inner Desert Sandstone Brick Stair=Кутова Сходинка З Цегли Із Пустельного Піщанику (Внутрішній Кут)
+Outer Desert Sandstone Brick Stair=Кутова Сходинка З Цегли Із Пустельного Піщанику (Зовнішній Кут)
+Desert Sandstone Brick Slab=Плита З Цегли Із Пустельного Піщанику
+Desert Sandstone Block Stair=Сходинка З Блоку Із Пустельного Піщанику
+Inner Desert Sandstone Block Stair=Кутова Сходинка З Блоку Із Пустельного Піщанику (Внутрішній Кут)
+Outer Desert Sandstone Block Stair=Кутова Сходинка З Блоку Із Пустельного Піщанику (Зовнішній Кут)
+Desert Sandstone Block Slab=Плита З Блоку Із Пустельного Піщанику
+Silver Sandstone Stair=Сходинка З Срібного Піщанику
+Inner Silver Sandstone Stair=Кутова Сходинка З Срібного Піщанику (Внутрішній Кут)
+Outer Silver Sandstone Stair=Кутова Сходинка З Срібного Піщанику (Зовнішній Кут)
+Silver Sandstone Slab=Плита З Срібного Піщанику
+Silver Sandstone Brick Stair=Сходинка З Цегли Із Срібного Піщанику
+Inner Silver Sandstone Brick Stair=Кутова Сходинка З Цегли Із Срібного Піщанику (Внутрішній Кут)
+Outer Silver Sandstone Brick Stair=Кутова Сходинка З Цегли Із Срібного Піщанику (Зовнішній Кут)
+Silver Sandstone Brick Slab=Плита З Цегли Із Срібного Піщанику
+Silver Sandstone Block Stair=Сходинка З Блоку Із Срібного Піщанику
+Inner Silver Sandstone Block Stair=Кутова Сходинка З Блоку Із Срібного Піщанику (Внутрішній Кут)
+Outer Silver Sandstone Block Stair=Кутова Сходинка З Блоку Із Срібного Піщанику (Зовнішній Кут)
+Silver Sandstone Block Slab=Плита З Блоку Із Срібного Піщанику
+Obsidian Stair=Обсидіанова Сходинка
+Inner Obsidian Stair=Кутова Обсидіанова Сходинка (Внутрішній Кут)
+Outer Obsidian Stair=Кутова Обсидіанова Сходинка (Зовнішній Кут)
+Obsidian Slab=Обсидіанова Плита
+Obsidian Brick Stair=Сходинка З Обсидіанової Цегли
+Inner Obsidian Brick Stair=Кутова Сходинка З Обсидіанової Цегли (Внутрішній Кут)
+Outer Obsidian Brick Stair=Кутова Сходинка З Обсидіанової Цегли (Зовнішній Кут)
+Obsidian Brick Slab=Плита З Обсидіанової Цегли
+Obsidian Block Stair=Сходинка З Обсидіанового Блоку
+Inner Obsidian Block Stair=Кутова Сходинка З Обсидіанового Блоку (Внутрішній Кут)
+Outer Obsidian Block Stair=Кутова Сходинка З Обсидіанового Блоку (Зовнішній Кут)
+Obsidian Block Slab=Плита З Обсидіанового Блоку
+Brick Stair=Сходинка З Цегли
+Inner Brick Stair=Кутова Сходинка З Цегли (Внутрішній Кут)
+Outer Brick Stair=Кутова Сходинка З Цегли (Зовнішній Кут)
+Brick Slab=Плита З Цегли
+Steel Block Stair=Сходинка Із Сталевого Блоку
+Inner Steel Block Stair=Кутова Сходинка Із Сталевого Блоку (Внутрішній Кут)
+Outer Steel Block Stair=Кутова Сходинка Із Сталевого Блоку (Зовнішній Кут)
+Steel Block Slab=Плита Із Сталевого Блоку
+Tin Block Stair=Сходинка З Олов'яного Блоку
+Inner Tin Block Stair=Кутова Сходинка З Олов'яного Блоку (Внутрішній Кут)
+Outer Tin Block Stair=Кутова Сходинка З Олов'яного Блоку (Зовнішній Кут)
+Tin Block Slab=Плита З Олов'яного Блоку
+Copper Block Stair=Сходинка З Мідного Блоку
+Inner Copper Block Stair=Кутова Сходинка З Мідного Блоку (Внутрішній Кут)
+Outer Copper Block Stair=Кутова Сходинка З Мідного Блоку (Зовнішній Кут)
+Copper Block Slab=Плита З Мідного Блоку
+Bronze Block Stair=Сходинка З Бронзового Блоку
+Inner Bronze Block Stair=Кутова Сходинка З Бронзового Блоку (Внутрішній Кут)
+Outer Bronze Block Stair=Кутова Сходинка З Бронзового Блоку (Зовнішній Кут)
+Bronze Block Slab=Плита З Бронзового Блоку
+Gold Block Stair=Сходинка З Золотого Блоку
+Inner Gold Block Stair=Сходинка З Золотого Блоку (Внутрішній Кут)
+Outer Gold Block Stair=Сходинка З Золотого Блоку (Зовнішній Кут)
+Gold Block Slab=Плита З Золотого Блоку
+Ice Stair=Крижана Сходинка
+Inner Ice Stair=Кутова Крижана Сходинка (Внутрішній Кут)
+Outer Ice Stair=Кутова Крижана Сходинка (Зовнішній Кут)
+Ice Slab=Крижана Плита
+Snow Block Stair=Ступінь З Крижаного Блоку
+Inner Snow Block Stair=Кутова Ступінь З Крижаного Блоку (Внутрішній Кут)
+Outer Snow Block Stair=Кутова Ступінь З Крижаного Блоку (Зовнішній Кут)
+Snow Block Slab=Плита З Крижаного Блоку
diff --git a/mods/stairs/locale/stairs.zh_CN.tr b/mods/stairs/locale/stairs.zh_CN.tr
new file mode 100644
index 00000000..e37ebcbb
--- /dev/null
+++ b/mods/stairs/locale/stairs.zh_CN.tr
@@ -0,0 +1,153 @@
+# textdomain: stairs
+Glass Stair=玻璃楼梯
+Glass Slab=玻璃台阶
+Inner Glass Stair=玻璃楼梯(内)
+Outer Glass Stair=玻璃楼梯(外)
+Obsidian Glass Stair=黑曜石玻璃楼梯
+Obsidian Glass Slab=黑曜石玻璃台阶
+Inner Obsidian Glass Stair=黑曜石玻璃楼梯(内)
+Outer Obsidian Glass Stair=黑曜石玻璃楼梯(外)
+Wooden Stair=木楼梯
+Inner Wooden Stair=木楼梯(内)
+Outer Wooden Stair=木楼梯(外)
+Wooden Slab=木台阶
+Jungle Wood Stair=丛林木楼梯
+Inner Jungle Wood Stair=丛林木楼梯(内)
+Outer Jungle Wood Stair=丛林木楼梯(外)
+Jungle Wood Slab=丛林木台阶
+Pine Wood Stair=松木楼梯
+Inner Pine Wood Stair=松木楼梯(内)
+Outer Pine Wood Stair=松木楼梯(外)
+Pine Wood Slab=松木台阶
+Acacia Wood Stair=金合欢木楼梯
+Inner Acacia Wood Stair=金合欢木楼梯(内)
+Outer Acacia Wood Stair=金合欢木楼梯(外)
+Acacia Wood Slab=金合欢木台阶
+Aspen Wood Stair=白杨木楼梯
+Inner Aspen Wood Stair=白杨木楼梯(内)
+Outer Aspen Wood Stair=白杨木楼梯(外)
+Aspen Wood Slab=白杨木台阶
+Stone Stair=石楼梯
+Inner Stone Stair=石楼梯(内)
+Outer Stone Stair=石楼梯(外)
+Stone Slab=石台阶
+Cobblestone Stair=鹅卵石楼梯
+Inner Cobblestone Stair=鹅卵石楼梯(内)
+Outer Cobblestone Stair=鹅卵石楼梯(外)
+Cobblestone Slab=鹅卵石台阶
+Mossy Cobblestone Stair=苔藓覆盖的鹅卵石楼梯
+Inner Mossy Cobblestone Stair=苔藓覆盖的鹅卵石楼梯(内)
+Outer Mossy Cobblestone Stair=苔藓覆盖的鹅卵石楼梯(外)
+Mossy Cobblestone Slab=苔藓覆盖的鹅卵石台阶
+Stone Brick Stair=石砖楼梯
+Inner Stone Brick Stair=石砖楼梯(内)
+Outer Stone Brick Stair=石砖楼梯(外)
+Stone Brick Slab=石砖台阶
+Stone Block Stair=石块楼梯
+Inner Stone Block Stair=石块楼梯(内)
+Outer Stone Block Stair=石块楼梯(外)
+Stone Block Slab=石块台阶
+Desert Stone Stair=沙漠石楼梯
+Inner Desert Stone Stair=沙漠石楼梯(内)
+Outer Desert Stone Stair=沙漠石楼梯(外)
+Desert Stone Slab=沙漠石台阶
+Desert Cobblestone Stair=沙漠鹅卵石楼梯
+Inner Desert Cobblestone Stair=沙漠鹅卵石楼梯(内)
+Outer Desert Cobblestone Stair=沙漠鹅卵石楼梯(外)
+Desert Cobblestone Slab=沙漠鹅卵石台阶
+Desert Stone Brick Stair=沙漠石砖楼梯
+Inner Desert Stone Brick Stair=沙漠石砖楼梯(内)
+Outer Desert Stone Brick Stair=沙漠石砖楼梯(外)
+Desert Stone Brick Slab=沙漠石砖台阶
+Desert Stone Block Stair=沙漠石块楼梯
+Inner Desert Stone Block Stair=沙漠石块楼梯(内)
+Outer Desert Stone Block Stair=沙漠石块楼梯(外)
+Desert Stone Block Slab=沙漠石块台阶
+Sandstone Stair=沙石楼梯
+Inner Sandstone Stair=沙石楼梯(内)
+Outer Sandstone Stair=沙石楼梯(外)
+Sandstone Slab=沙石台阶
+Sandstone Brick Stair=沙石砖楼梯
+Inner Sandstone Brick Stair=沙石砖楼梯(内)
+Outer Sandstone Brick Stair=沙石砖楼梯(外)
+Sandstone Brick Slab=沙石砖台阶
+Sandstone Block Stair=沙石块楼梯
+Inner Sandstone Block Stair=沙石块楼梯(内)
+Outer Sandstone Block Stair=沙石块楼梯(外)
+Sandstone Block Slab=沙石块台阶
+Desert Sandstone Stair=沙漠沙石楼梯
+Inner Desert Sandstone Stair=沙漠沙石楼梯(内)
+Outer Desert Sandstone Stair=沙漠沙石楼梯(外)
+Desert Sandstone Slab=沙漠沙石台阶
+Desert Sandstone Brick Stair=沙漠沙石砖楼梯
+Inner Desert Sandstone Brick Stair=沙漠沙石砖楼梯(内)
+Outer Desert Sandstone Brick Stair=沙漠沙石砖楼梯(外)
+Desert Sandstone Brick Slab=沙漠沙石砖台阶
+Desert Sandstone Block Stair=沙漠沙石块楼梯
+Inner Desert Sandstone Block Stair=沙漠沙石块楼梯(内)
+Outer Desert Sandstone Block Stair=沙漠沙石块楼梯(外)
+Desert Sandstone Block Slab=沙漠沙石块台阶
+Silver Sandstone Stair=银沙石楼梯
+Inner Silver Sandstone Stair=银沙石楼梯(内)
+Outer Silver Sandstone Stair=银沙石楼梯(外)
+Silver Sandstone Slab=银沙石台阶
+Silver Sandstone Brick Stair=银沙石砖楼梯
+Inner Silver Sandstone Brick Stair=银沙石砖楼梯(内)
+Outer Silver Sandstone Brick Stair=银沙石砖楼梯(外)
+Silver Sandstone Brick Slab=银沙石砖台阶
+Silver Sandstone Block Stair=银沙石块楼梯
+Inner Silver Sandstone Block Stair=银沙石块楼梯(内)
+Outer Silver Sandstone Block Stair=银沙石块楼梯(外)
+Silver Sandstone Block Slab=银沙石块台阶
+Obsidian Stair=黑曜石楼梯
+Inner Obsidian Stair=黑曜石楼梯(内)
+Outer Obsidian Stair=黑曜石楼梯(外)
+Obsidian Slab=黑曜石台阶
+Obsidian Brick Stair=黑曜石砖楼梯
+Inner Obsidian Brick Stair=黑曜石砖楼梯(内)
+Outer Obsidian Brick Stair=黑曜石砖楼梯(外)
+Obsidian Brick Slab=黑曜石砖台阶
+Obsidian Block Stair=黑曜石块楼梯
+Inner Obsidian Block Stair=黑曜石块楼梯(内)
+Outer Obsidian Block Stair=黑曜石块楼梯(外)
+Obsidian Block Slab=黑曜石块台阶
+Brick Stair=砖楼梯
+Inner Brick Stair=砖楼梯(内)
+Outer Brick Stair=砖楼梯(外)
+Brick Slab=砖台阶
+Steel Block Stair=铁块楼梯
+Inner Steel Block Stair=铁块楼梯(内)
+Outer Steel Block Stair=铁块楼梯(外)
+Steel Block Slab=铁块台阶
+Tin Block Stair=锡块楼梯
+Inner Tin Block Stair=锡块楼梯(内)
+Outer Tin Block Stair=锡块楼梯(外)
+Tin Block Slab=锡块台阶
+Copper Block Stair=铜块楼梯
+Inner Copper Block Stair=铜块楼梯(内)
+Outer Copper Block Stair=铜块楼梯(外)
+Copper Block Slab=铜块台阶
+Bronze Block Stair=青铜块楼梯
+Inner Bronze Block Stair=青铜块楼梯(内)
+Outer Bronze Block Stair=青铜块楼梯(外)
+Bronze Block Slab=青铜块台阶
+Gold Block Stair=金块楼梯
+Inner Gold Block Stair=金块楼梯(内)
+Outer Gold Block Stair=金块楼梯(外)
+Gold Block Slab=金块台阶
+Ice Stair=冰楼梯
+Inner Ice Stair=冰块楼梯(内)
+Outer Ice Stair=冰块楼梯(外)
+Ice Slab=冰台阶
+Snow Block Stair=雪块楼梯
+Inner Snow Block Stair=雪块楼梯(内)
+Outer Snow Block Stair=雪块楼梯(外)
+Snow Block Slab=雪块台阶
+
+
+##### not used anymore #####
+
+Blue Stained Stair=蓝木楼梯
+Inner Blue Stained Stair=蓝木楼梯(内)
+Outer Blue Stained Stair=蓝木楼梯(外)
+Blue Stained Slab=蓝木台阶
diff --git a/mods/stairs/locale/stairs.zh_TW.tr b/mods/stairs/locale/stairs.zh_TW.tr
new file mode 100644
index 00000000..eaed61f9
--- /dev/null
+++ b/mods/stairs/locale/stairs.zh_TW.tr
@@ -0,0 +1,153 @@
+# textdomain: stairs
+Glass Stair=玻璃樓梯
+Glass Slab=玻璃臺階
+Inner Glass Stair=玻璃樓梯(內)
+Outer Glass Stair=玻璃樓梯(外)
+Obsidian Glass Stair=黑曜石玻璃樓梯
+Obsidian Glass Slab=黑曜石玻璃臺階
+Inner Obsidian Glass Stair=黑曜石玻璃樓梯(內)
+Outer Obsidian Glass Stair=黑曜石玻璃樓梯(外)
+Wooden Stair=木製樓梯
+Inner Wooden Stair=木樓梯(內)
+Outer Wooden Stair=木樓梯(外)
+Wooden Slab=木製臺階
+Jungle Wood Stair=叢林木樓梯
+Inner Jungle Wood Stair=叢林木樓梯(內)
+Outer Jungle Wood Stair=叢林木樓梯(外)
+Jungle Wood Slab=叢林木臺階
+Pine Wood Stair=松木樓梯
+Inner Pine Wood Stair=松木樓梯(內)
+Outer Pine Wood Stair=松木樓梯(外)
+Pine Wood Slab=松木臺階
+Acacia Wood Stair=金合歡木樓梯
+Inner Acacia Wood Stair=金合歡木樓梯(內)
+Outer Acacia Wood Stair=金合歡木樓梯(外)
+Acacia Wood Slab=金合歡木臺階
+Aspen Wood Stair=白楊木樓梯
+Inner Aspen Wood Stair=白楊木樓梯(內)
+Outer Aspen Wood Stair=白楊木樓梯(外)
+Aspen Wood Slab=白楊木臺階
+Stone Stair=石樓梯
+Inner Stone Stair=石樓梯(內)
+Outer Stone Stair=石樓梯(外)
+Stone Slab=石臺階
+Cobblestone Stair=圓石樓梯
+Inner Cobblestone Stair=圓石樓梯(內)
+Outer Cobblestone Stair=圓石樓梯(外)
+Cobblestone Slab=圓石臺階
+Mossy Cobblestone Stair=苔石樓梯
+Inner Mossy Cobblestone Stair=苔石樓梯(內)
+Outer Mossy Cobblestone Stair=苔石樓梯(外)
+Mossy Cobblestone Slab=苔石臺階
+Stone Brick Stair=石磚樓梯
+Inner Stone Brick Stair=石磚樓梯(內)
+Outer Stone Brick Stair=石磚樓梯(外)
+Stone Brick Slab=石磚臺階
+Stone Block Stair=石塊樓梯
+Inner Stone Block Stair=石塊樓梯(內)
+Outer Stone Block Stair=石塊樓梯(外)
+Stone Block Slab=石塊臺階
+Desert Stone Stair=沙漠石樓梯
+Inner Desert Stone Stair=沙漠石樓梯(內)
+Outer Desert Stone Stair=沙漠石樓梯(外)
+Desert Stone Slab=沙漠石臺階
+Desert Cobblestone Stair=沙漠圓石樓梯
+Inner Desert Cobblestone Stair=沙漠圓石樓梯(內)
+Outer Desert Cobblestone Stair=沙漠圓石樓梯(外)
+Desert Cobblestone Slab=沙漠圓石臺階
+Desert Stone Brick Stair=沙漠石磚樓梯
+Inner Desert Stone Brick Stair=沙漠石磚樓梯(內)
+Outer Desert Stone Brick Stair=沙漠石磚樓梯(外)
+Desert Stone Brick Slab=沙漠石磚臺階
+Desert Stone Block Stair=沙漠石塊樓梯
+Inner Desert Stone Block Stair=沙漠石塊樓梯(內)
+Outer Desert Stone Block Stair=沙漠石塊樓梯(外)
+Desert Stone Block Slab=沙漠石塊臺階
+Sandstone Stair=沙石樓梯
+Inner Sandstone Stair=沙石樓梯(內)
+Outer Sandstone Stair=沙石樓梯(外)
+Sandstone Slab=沙石臺階
+Sandstone Brick Stair=沙石磚樓梯
+Inner Sandstone Brick Stair=沙石磚樓梯(內)
+Outer Sandstone Brick Stair=沙石磚樓梯(外)
+Sandstone Brick Slab=沙石磚臺階
+Sandstone Block Stair=沙石塊樓梯
+Inner Sandstone Block Stair=沙石塊樓梯(內)
+Outer Sandstone Block Stair=沙石塊樓梯(外)
+Sandstone Block Slab=沙石塊臺階
+Desert Sandstone Stair=沙漠沙石樓梯
+Inner Desert Sandstone Stair=沙漠沙石樓梯(內)
+Outer Desert Sandstone Stair=沙漠沙石樓梯(外)
+Desert Sandstone Slab=沙漠沙石臺階
+Desert Sandstone Brick Stair=沙漠沙石磚樓梯
+Inner Desert Sandstone Brick Stair=沙漠沙石磚樓梯(內)
+Outer Desert Sandstone Brick Stair=沙漠沙石磚樓梯(外)
+Desert Sandstone Brick Slab=沙漠沙石磚臺階
+Desert Sandstone Block Stair=沙漠沙石塊樓梯
+Inner Desert Sandstone Block Stair=沙漠沙石塊樓梯(內)
+Outer Desert Sandstone Block Stair=沙漠沙石塊樓梯(外)
+Desert Sandstone Block Slab=沙漠沙石塊臺階
+Silver Sandstone Stair=銀沙石樓梯
+Inner Silver Sandstone Stair=銀沙石樓梯(內)
+Outer Silver Sandstone Stair=銀沙石樓梯(外)
+Silver Sandstone Slab=銀沙石臺階
+Silver Sandstone Brick Stair=銀沙石磚樓梯
+Inner Silver Sandstone Brick Stair=銀沙石磚樓梯(內)
+Outer Silver Sandstone Brick Stair=銀沙石磚樓梯(外)
+Silver Sandstone Brick Slab=銀沙石磚臺階
+Silver Sandstone Block Stair=銀沙石塊樓梯
+Inner Silver Sandstone Block Stair=銀沙石塊樓梯(內)
+Outer Silver Sandstone Block Stair=銀沙石塊樓梯(外)
+Silver Sandstone Block Slab=銀沙石塊臺階
+Obsidian Stair=黑曜石樓梯
+Inner Obsidian Stair=黑曜石樓梯(內)
+Outer Obsidian Stair=黑曜石樓梯(外)
+Obsidian Slab=黑曜石臺階
+Obsidian Brick Stair=黑曜石磚樓梯
+Inner Obsidian Brick Stair=黑曜石磚樓梯(內)
+Outer Obsidian Brick Stair=黑曜石磚樓梯(外)
+Obsidian Brick Slab=黑曜石磚臺階
+Obsidian Block Stair=黑曜石塊樓梯
+Inner Obsidian Block Stair=黑曜石塊樓梯(內)
+Outer Obsidian Block Stair=黑曜石塊樓梯(外)
+Obsidian Block Slab=黑曜石塊臺階
+Brick Stair=磚樓梯
+Inner Brick Stair=磚樓梯(內)
+Outer Brick Stair=磚樓梯(外)
+Brick Slab=磚制臺階
+Steel Block Stair=鐵塊樓梯
+Inner Steel Block Stair=鐵塊樓梯(內)
+Outer Steel Block Stair=鐵塊樓梯(外)
+Steel Block Slab=鐵塊臺階
+Tin Block Stair=錫塊樓梯
+Inner Tin Block Stair=錫塊樓梯(內)
+Outer Tin Block Stair=錫塊樓梯(外)
+Tin Block Slab=錫塊臺階
+Copper Block Stair=銅塊樓梯
+Inner Copper Block Stair=銅塊樓梯(內)
+Outer Copper Block Stair=銅塊樓梯(外)
+Copper Block Slab=銅塊臺階
+Bronze Block Stair=青銅塊樓梯
+Inner Bronze Block Stair=青銅塊樓梯(內)
+Outer Bronze Block Stair=青銅塊樓梯(外)
+Bronze Block Slab=青銅塊臺階
+Gold Block Stair=金塊樓梯
+Inner Gold Block Stair=金塊樓梯(內)
+Outer Gold Block Stair=金塊樓梯(外)
+Gold Block Slab=金塊臺階
+Ice Stair=冰階梯
+Inner Ice Stair=冰塊樓梯(內)
+Outer Ice Stair=冰塊樓梯(外)
+Ice Slab=冰臺階
+Snow Block Stair=雪塊樓梯
+Inner Snow Block Stair=雪塊樓梯(內)
+Outer Snow Block Stair=雪塊樓梯(外)
+Snow Block Slab=雪塊臺階
+
+
+##### not used anymore #####
+
+Blue Stained Stair=藍木樓梯
+Inner Blue Stained Stair=藍木樓梯(內)
+Outer Blue Stained Stair=藍木樓梯(外)
+Blue Stained Slab=藍木臺階
diff --git a/mods/stairs/locale/template.txt b/mods/stairs/locale/template.txt
new file mode 100644
index 00000000..ca2c8657
--- /dev/null
+++ b/mods/stairs/locale/template.txt
@@ -0,0 +1,145 @@
+# textdomain: stairs
+Glass Stair=
+Glass Slab=
+Inner Glass Stair=
+Outer Glass Stair=
+Obsidian Glass Stair=
+Obsidian Glass Slab=
+Inner Obsidian Glass Stair=
+Outer Obsidian Glass Stair=
+Wooden Stair=
+Inner Wooden Stair=
+Outer Wooden Stair=
+Wooden Slab=
+Jungle Wood Stair=
+Inner Jungle Wood Stair=
+Outer Jungle Wood Stair=
+Jungle Wood Slab=
+Pine Wood Stair=
+Inner Pine Wood Stair=
+Outer Pine Wood Stair=
+Pine Wood Slab=
+Acacia Wood Stair=
+Inner Acacia Wood Stair=
+Outer Acacia Wood Stair=
+Acacia Wood Slab=
+Aspen Wood Stair=
+Inner Aspen Wood Stair=
+Outer Aspen Wood Stair=
+Aspen Wood Slab=
+Stone Stair=
+Inner Stone Stair=
+Outer Stone Stair=
+Stone Slab=
+Cobblestone Stair=
+Inner Cobblestone Stair=
+Outer Cobblestone Stair=
+Cobblestone Slab=
+Mossy Cobblestone Stair=
+Inner Mossy Cobblestone Stair=
+Outer Mossy Cobblestone Stair=
+Mossy Cobblestone Slab=
+Stone Brick Stair=
+Inner Stone Brick Stair=
+Outer Stone Brick Stair=
+Stone Brick Slab=
+Stone Block Stair=
+Inner Stone Block Stair=
+Outer Stone Block Stair=
+Stone Block Slab=
+Desert Stone Stair=
+Inner Desert Stone Stair=
+Outer Desert Stone Stair=
+Desert Stone Slab=
+Desert Cobblestone Stair=
+Inner Desert Cobblestone Stair=
+Outer Desert Cobblestone Stair=
+Desert Cobblestone Slab=
+Desert Stone Brick Stair=
+Inner Desert Stone Brick Stair=
+Outer Desert Stone Brick Stair=
+Desert Stone Brick Slab=
+Desert Stone Block Stair=
+Inner Desert Stone Block Stair=
+Outer Desert Stone Block Stair=
+Desert Stone Block Slab=
+Sandstone Stair=
+Inner Sandstone Stair=
+Outer Sandstone Stair=
+Sandstone Slab=
+Sandstone Brick Stair=
+Inner Sandstone Brick Stair=
+Outer Sandstone Brick Stair=
+Sandstone Brick Slab=
+Sandstone Block Stair=
+Inner Sandstone Block Stair=
+Outer Sandstone Block Stair=
+Sandstone Block Slab=
+Desert Sandstone Stair=
+Inner Desert Sandstone Stair=
+Outer Desert Sandstone Stair=
+Desert Sandstone Slab=
+Desert Sandstone Brick Stair=
+Inner Desert Sandstone Brick Stair=
+Outer Desert Sandstone Brick Stair=
+Desert Sandstone Brick Slab=
+Desert Sandstone Block Stair=
+Inner Desert Sandstone Block Stair=
+Outer Desert Sandstone Block Stair=
+Desert Sandstone Block Slab=
+Silver Sandstone Stair=
+Inner Silver Sandstone Stair=
+Outer Silver Sandstone Stair=
+Silver Sandstone Slab=
+Silver Sandstone Brick Stair=
+Inner Silver Sandstone Brick Stair=
+Outer Silver Sandstone Brick Stair=
+Silver Sandstone Brick Slab=
+Silver Sandstone Block Stair=
+Inner Silver Sandstone Block Stair=
+Outer Silver Sandstone Block Stair=
+Silver Sandstone Block Slab=
+Obsidian Stair=
+Inner Obsidian Stair=
+Outer Obsidian Stair=
+Obsidian Slab=
+Obsidian Brick Stair=
+Inner Obsidian Brick Stair=
+Outer Obsidian Brick Stair=
+Obsidian Brick Slab=
+Obsidian Block Stair=
+Inner Obsidian Block Stair=
+Outer Obsidian Block Stair=
+Obsidian Block Slab=
+Brick Stair=
+Inner Brick Stair=
+Outer Brick Stair=
+Brick Slab=
+Steel Block Stair=
+Inner Steel Block Stair=
+Outer Steel Block Stair=
+Steel Block Slab=
+Tin Block Stair=
+Inner Tin Block Stair=
+Outer Tin Block Stair=
+Tin Block Slab=
+Copper Block Stair=
+Inner Copper Block Stair=
+Outer Copper Block Stair=
+Copper Block Slab=
+Bronze Block Stair=
+Inner Bronze Block Stair=
+Outer Bronze Block Stair=
+Bronze Block Slab=
+Gold Block Stair=
+Inner Gold Block Stair=
+Outer Gold Block Stair=
+Gold Block Slab=
+Ice Stair=
+Inner Ice Stair=
+Outer Ice Stair=
+Ice Slab=
+Snow Block Stair=
+Inner Snow Block Stair=
+Outer Snow Block Stair=
+Snow Block Slab=
diff --git a/mods/stairs/mod.conf b/mods/stairs/mod.conf
new file mode 100644
index 00000000..4e6ce601
--- /dev/null
+++ b/mods/stairs/mod.conf
@@ -0,0 +1,7 @@
+name = stairs
+description = Adds stairs, slabs, inner and outer corners and slopes.
+optional_depends = default
+min_minetest_version = 5.0
+release = 30967
+author = TenPlus1
+title = Stairs Redo
diff --git a/mods/stairs/models/stairs_slope.obj b/mods/stairs/models/stairs_slope.obj
new file mode 100644
index 00000000..f5434c30
--- /dev/null
+++ b/mods/stairs/models/stairs_slope.obj
@@ -0,0 +1,71 @@
+# Blender v2.76 (sub 0) OBJ File: ''
+# www.blender.org
+mtllib simplyslope_slope.mtl
+o stairs_top
+v -0.5 0.5 0.5
+v 0.5 0.5 0.5
+o stairs_right
+v -0.5 -0.5 -0.5
+v -0.5 -0.5 0.5
+v -0.5 0.5 0.5
+v -0.5 0.0 0.0
+vt 0.0 0.0
+vt 1.0 0.0
+vt 1.0 1.0
+vn -1.0 0.0 0.0
+usemtl None
+s 1
+f 3/1/1 4/2/1 5/3/1
+o front
+v -0.5 -0.5 -0.5
+v -0.5 0.5 0.5
+v 0.5 0.5 0.5
+v 0.5 -0.5 -0.5
+vt 1.0 0.0
+vt 1.0 0.999938
+vt 0.0 0.999938
+vt 0.0 0.0
+vn 0.0 0.707100 -0.707100
+usemtl None
+s 1
+f 7/4/2 8/5/2 9/6/2 10/7/2
+o left
+v 0.5 -0.5 -0.5
+v 0.5 0.5 0.5
+v 0.5 -0.5 0.5
+v 0.5 0.0 0.0
+vt 1.0 0.0
+vt 0.0 1.0
+vt 0.0 0.0
+vn 1.0 0.0 0.0
+usemtl None
+s 1
+f 11/8/3 12/9/3 13/10/3
+l 14 11
+l 14 12
+o bottom
+v -0.5 -0.5 0.5
+v -0.5 -0.5 -0.5
+v 0.5 -0.5 -0.5
+v 0.5 -0.5 0.5
+vt 1.0 0.0
+vt 1.0 1.0
+vt 0.0 1.0
+vt 0.0 0.0
+vn 0.0 -1.0 -0.0
+usemtl None
+s 1
+f 15/11/4 16/12/4 17/13/4 18/14/4
+o back
+v 0.5 -0.5 0.5
+v 0.5 0.5 0.5
+v -0.5 0.5 0.5
+v -0.5 -0.5 0.5
+vt 1.0 0.0
+vt 1.0 1.0
+vt 0.0 1.0
+vt 0.0 0.0
+vn 0.0 -0.0 1.0
+usemtl None
+s 1
+f 19/15/5 20/16/5 21/17/5 22/18/5
\ No newline at end of file
diff --git a/mods/stairs/models/stairs_slope_inner.obj b/mods/stairs/models/stairs_slope_inner.obj
new file mode 100644
index 00000000..2a780ea6
--- /dev/null
+++ b/mods/stairs/models/stairs_slope_inner.obj
@@ -0,0 +1,83 @@
+# Blender v2.76 (sub 0) OBJ File: ''
+# www.blender.org
+g top
+v 0.5 0.5 -0.5
+v 0.5 0.5 0.5
+v -0.5 0.5 0.5
+v -0.5 -0.5 -0.5
+v -0.5 -0.5 -0.5
+vt 1.0 1.0
+vt 0.0 1.0
+vt 1.0 0.0
+vt 1.0 1.0
+vt 0.0 1.0
+vt 0.0 0.0
+vn 0.0 0.7071 -0.7071
+vn -0.7071 0.7071 0.0
+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.5 -0.5 0.5
+v 0.5 -0.5 -0.5
+v -0.5 -0.5 0.5
+v -0.5 -0.5 -0.5
+v 0.5 -0.5 0.5
+vt 1.0 1.0
+vt 0.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vn 0.0 -1.0 -0.0
+s 1
+f 9/7/3 7/8/3 6/9/3 8/10/3
+l 8 10
+g right
+v -0.5 0.5 0.5
+v -0.5 -0.5 -0.5
+v -0.5 -0.5 0.5
+vt 1.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vn -1.0 0.0 0.0
+s 1
+f 11/11/4 12/12/4 13/13/4
+g left
+v 0.5 0.5 -0.5
+v 0.5 0.5 0.5
+v 0.5 -0.5 0.5
+v 0.5 -0.5 -0.5
+v 0.5 -0.5 0.5
+vt 1.0 1.0
+vt 0.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vn 1.0 0.0 0.0
+s 1
+f 14/14/5 15/15/5 16/16/5 17/17/5
+l 15 18
+g back
+v 0.5 0.5 0.5
+v 0.5 -0.5 0.5
+v -0.5 0.5 0.5
+v -0.5 -0.5 0.5
+v 0.5 -0.5 0.5
+vt 0.0 0.0
+vt 1.0 0.0
+vt 1.0 1.0
+vt 0.0 1.0
+vn 0.0 -0.0 1.0
+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.5 0.5 -0.5
+v 0.5 -0.5 -0.5
+v -0.5 -0.5 -0.5
+v -0.5 -0.5 -0.5
+vt 0.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vn 0.0 0.0 -1.0
+s 1
+f 24/22/7 25/23/7 27/24/7
diff --git a/mods/stairs/models/stairs_slope_outer.obj b/mods/stairs/models/stairs_slope_outer.obj
new file mode 100644
index 00000000..c95d39b1
--- /dev/null
+++ b/mods/stairs/models/stairs_slope_outer.obj
@@ -0,0 +1,50 @@
+# Blender v2.76 (sub 0) OBJ File: ''
+# www.blender.org
+g top
+v -0.5 -0.5 0.5
+v -0.5 -0.5 -0.5
+v 0.5 -0.5 -0.5
+v 0.5 0.5 0.5
+vt 1.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vt 0.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vn -0.7071 0.7071 0.0
+vn 0.0 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.5 -0.5 0.5
+v -0.5 -0.5 0.5
+v -0.5 -0.5 -0.5
+v 0.5 -0.5 -0.5
+vt 1.0 1.0
+vt 0.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vn 0.0 -1.0 -0.0
+s off
+f 5/7/3 6/8/3 7/9/3 8/10/3
+g right
+v 0.5 -0.5 0.5
+v -0.5 -0.5 0.5
+v 0.5 0.5 0.5
+vt 0.0 0.0
+vt 1.0 0.0
+vt 1.0 1.0
+vn 0.0 -0.0 1.0
+s off
+f 10/11/4 9/12/4 11/13/4
+g left
+v 0.5 -0.5 0.5
+v 0.5 -0.5 -0.5
+v 0.5 0.5 0.5
+vt 0.0 1.0
+vt 0.0 0.0
+vt 1.0 0.0
+vn 1.0 0.0 0.0
+s off
+f 14/14/5 12/15/5 13/16/5
diff --git a/mods/stairs/screenshot.png b/mods/stairs/screenshot.png
new file mode 100644
index 00000000..5c329015
Binary files /dev/null and b/mods/stairs/screenshot.png differ
diff --git a/mods/stairs/settingtypes.txt b/mods/stairs/settingtypes.txt
new file mode 100644
index 00000000..12c5ba7c
--- /dev/null
+++ b/mods/stairs/settingtypes.txt
@@ -0,0 +1 @@
+stairs.glass_sides (Show glass stair side texture) bool true
diff --git a/mods/stairs/stairs.lua b/mods/stairs/stairs.lua
new file mode 100644
index 00000000..fa620fba
--- /dev/null
+++ b/mods/stairs/stairs.lua
@@ -0,0 +1,564 @@
+
+-- Translation support
+
+local S = core.get_translator("stairs")
+
+-- Local functions so we can apply translations
+
+local function my_register_stair_and_slab(subname, recipeitem, groups, images,
+ desc_stair, desc_slab, sounds, worldaligntex)
+
+ stairs.register_stair(subname, recipeitem, groups, images, S(desc_stair),
+ sounds, worldaligntex)
+
+ stairs.register_stair_inner(subname, recipeitem, groups, images, "",
+ sounds, worldaligntex, S("Inner " .. desc_stair))
+
+ stairs.register_stair_outer(subname, recipeitem, groups, images, "",
+ sounds, worldaligntex, S("Outer " .. desc_stair))
+
+ stairs.register_slab(subname, recipeitem, groups, images, S(desc_slab),
+ sounds, worldaligntex)
+end
+
+local function my_register_all(subname, recipeitem, groups, images, desc,
+ sounds, worldaligntex)
+
+ my_register_stair_and_slab(subname, recipeitem, groups, images, desc .. " Stair",
+ desc .. " Slab", sounds, worldaligntex)
+
+ stairs.register_slope(subname, recipeitem, groups, images, S(desc .. " Slope"),
+ sounds, worldaligntex)
+
+ stairs.register_slope_inner(subname, recipeitem, groups, images,
+ S("Inner " .. desc .. " Slope"), sounds, worldaligntex)
+
+ stairs.register_slope_outer(subname, recipeitem, groups, images,
+ S("Outer " .. desc .. " Slope"), sounds, worldaligntex)
+end
+
+--= Default Minetest
+
+if core.get_modpath("default") then
+
+ -- Wood
+
+ my_register_all("wood", "default:wood",
+ {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ {"default_wood.png"},
+ "Wooden",
+ nil, true)
+
+ my_register_all("junglewood", "default:junglewood",
+ {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ {"default_junglewood.png"},
+ "Jungle Wood",
+ nil, true)
+
+ my_register_all("pine_wood", "default:pinewood",
+ {choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ {"default_pine_wood.png"},
+ "Pine Wood",
+ nil, true)
+
+ -- Register aliases for new pine node names
+ core.register_alias("stairs:stair_pinewood", "stairs:stair_pine_wood")
+ core.register_alias("stairs:slab_pinewood", "stairs:slab_pine_wood")
+
+ my_register_all("acacia_wood", "default:acacia_wood",
+ {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
+ {"default_acacia_wood.png"},
+ "Acacia Wood",
+ nil, true)
+
+ my_register_all("aspen_wood", "default:aspen_wood",
+ {choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ {"default_aspen_wood.png"},
+ "Aspen Wood",
+ nil, true)
+
+ -- Stone
+
+ my_register_all("stone", "default:stone",
+ {cracky = 3},
+ {"default_stone.png"},
+ "Stone",
+ nil, true)
+
+ my_register_all("stonebrick", "default:stonebrick",
+ {cracky = 2},
+ {"default_stone_brick.png"},
+ "Stone Brick",
+ nil, true)
+
+ my_register_all("stone_block", "default:stone_block",
+ {cracky = 2},
+ {"default_stone_block.png"},
+ "Stone Block",
+ nil, true)
+
+ my_register_all("cobble", "default:cobble",
+ {cracky = 3},
+ {"default_cobble.png"},
+ "Cobble",
+ nil, true)
+
+ my_register_all("mossycobble", "default:mossycobble",
+ {cracky = 3},
+ {"default_mossycobble.png"},
+ "Mossy Cobble",
+ nil, true)
+
+ my_register_all("desert_stone", "default:desert_stone",
+ {cracky = 3},
+ {"default_desert_stone.png"},
+ "Desert Stone",
+ nil, true)
+
+ my_register_all("desert_stonebrick", "default:desert_stonebrick",
+ {cracky = 2},
+ {"default_desert_stone_brick.png"},
+ "Desert Stone Brick",
+ nil, false)
+
+ my_register_all("desert_stone_block", "default:desert_stone_block",
+ {cracky = 2},
+ {"default_desert_stone_block.png"},
+ "Desert Stone Block",
+ nil, true)
+
+ my_register_all("desert_cobble", "default:desert_cobble",
+ {cracky = 3},
+ {"default_desert_cobble.png"},
+ "Desert Cobble",
+ nil, true)
+
+ -- Sandstone
+
+ my_register_all("sandstone", "default:sandstone",
+ {crumbly = 1, cracky = 3},
+ {"default_sandstone.png"},
+ "Sandstone",
+ nil, true)
+
+ my_register_all("sandstonebrick", "default:sandstonebrick",
+ {cracky = 2},
+ {"default_sandstone_brick.png"},
+ "Sandstone Brick",
+ nil, false)
+
+ my_register_all("sandstone_block", "default:sandstone_block",
+ {cracky = 2},
+ {"default_sandstone_block.png"},
+ "Sandstone Block",
+ nil, true)
+
+ my_register_all("desert_sandstone", "default:desert_sandstone",
+ {crumbly = 1, cracky = 3},
+ {"default_desert_sandstone.png"},
+ "Desert Sandstone",
+ nil, true)
+
+ my_register_all("desert_sandstone_brick", "default:desert_sandstone_brick",
+ {cracky = 2},
+ {"default_desert_sandstone_brick.png"},
+ "Desert Sandstone Brick",
+ nil, false)
+
+ my_register_all("desert_sandstone_block", "default:desert_sandstone_block",
+ {cracky = 2},
+ {"default_desert_sandstone_block.png"},
+ "Desert Sandstone Block",
+ nil, true)
+
+ my_register_all("silver_sandstone", "default:silver_sandstone",
+ {crumbly = 1, cracky = 3},
+ {"default_silver_sandstone.png"},
+ "Silver Sandstone",
+ nil, true)
+
+ my_register_all("silver_sandstone_brick", "default:silver_sandstone_brick",
+ {cracky = 2},
+ {"default_silver_sandstone_brick.png"},
+ "Silver Sandstone Brick",
+ nil, false)
+
+ my_register_all("silver_sandstone_block", "default:silver_sandstone_block",
+ {cracky = 2},
+ {"default_silver_sandstone_block.png"},
+ "Silver Sandstone Block",
+ nil, true)
+
+ -- Obsidian
+
+ my_register_all("obsidian", "default:obsidian",
+ {cracky = 1, level = 2},
+ {"default_obsidian.png"},
+ "Obsidian",
+ nil, true)
+
+ my_register_all("obsidianbrick", "default:obsidianbrick",
+ {cracky = 1, level = 2},
+ {"default_obsidian_brick.png"},
+ "Obsidian Brick",
+ nil, false)
+
+ my_register_all("obsidian_block", "default:obsidian_block",
+ {cracky = 1, level = 2},
+ {"default_obsidian_block.png"},
+ "Obsidian Block",
+ nil, true)
+
+ -- Cloud (with overrides)
+
+ stairs.register_stair("cloud", "default:cloud",
+ {unbreakable = 1, not_in_creative_inventory = 1},
+ {"default_cloud.png"},
+ S("Cloud Stair"),
+ nil)
+
+ core.override_item("stairs:stair_cloud", {
+ on_blast = function() end,
+ on_drop = function(itemstack, dropper, pos) end,
+ drop = {},
+ })
+
+ stairs.register_slab("cloud", "default:cloud",
+ {unbreakable = 1, not_in_creative_inventory = 1},
+ {"default_cloud.png"},
+ S("Cloud Slab"),
+ nil)
+
+ core.override_item("stairs:slab_cloud", {
+ on_blast = function() end,
+ on_drop = function(itemstack, dropper, pos) end,
+ drop = {},
+ })
+
+ -- Ores
+
+ my_register_all("coal", "default:coalblock",
+ {cracky = 3},
+ {"default_coal_block.png"},
+ "Coal",
+ nil, true)
+
+ my_register_all("steelblock", "default:steelblock",
+ {cracky = 1, level = 2},
+ {"default_steel_block.png"},
+ "Steel",
+ nil, true)
+
+ my_register_all("copperblock", "default:copperblock",
+ {cracky = 1, level = 2},
+ {"default_copper_block.png"},
+ "Copper",
+ nil, true)
+
+ my_register_all("bronzeblock", "default:bronzeblock",
+ {cracky = 1, level = 2},
+ {"default_bronze_block.png"},
+ "Bronze",
+ nil, true)
+
+ my_register_all("tinblock", "default:tinblock",
+ {cracky = 1, level = 2},
+ {"default_tin_block.png"},
+ "Tin",
+ nil, true)
+
+ my_register_all("mese", "default:mese",
+ {cracky = 1, level = 2},
+ {"default_mese_block.png"},
+ "Mese",
+ nil)
+
+ my_register_all("goldblock", "default:goldblock",
+ {cracky = 1},
+ {"default_gold_block.png"},
+ "Gold",
+ nil)
+
+ my_register_all("diamondblock", "default:diamondblock",
+ {cracky = 1, level = 3},
+ {"default_diamond_block.png"},
+ "Diamond",
+ nil)
+
+ -- Setting to show glass stair sides
+ local gsides = core.settings:get_bool("stairs.glass_sides") ~= false
+
+ -- Old glass stairs
+-- my_register_all("glass", "default:glass",
+-- {cracky = 3, oddly_breakable_by_hand = 3},
+-- {"default_glass.png"},
+-- "Glass",
+-- stairs.glass)
+
+ -- Glass (stairs registered seperately to use special texture)
+
+ local face_tex = "default_glass.png"
+ local side_tex = gsides and "stairs_glass_quarter.png" or face_tex
+
+ stairs.register_stair(
+ "glass",
+ "default:glass",
+ {cracky = 3, oddly_breakable_by_hand = 3},
+ {side_tex, face_tex, side_tex, side_tex, face_tex, side_tex},
+ S("Glass Stair"),
+ nil,
+ false)
+
+ stairs.register_slab(
+ "glass",
+ "default:glass",
+ {cracky = 3, oddly_breakable_by_hand = 3},
+ {face_tex, face_tex, side_tex},
+ S("Glass Slab"),
+ nil,
+ false)
+
+ stairs.register_stair_inner(
+ "glass",
+ "default:glass",
+ {cracky = 3, oddly_breakable_by_hand = 3},
+ {side_tex, face_tex, side_tex, face_tex, face_tex, side_tex},
+ "",
+ nil,
+ false,
+ S("Inner Glass Stair"))
+
+ stairs.register_stair_outer(
+ "glass",
+ "default:glass",
+ {cracky = 3, oddly_breakable_by_hand = 3},
+ {side_tex, face_tex, side_tex, side_tex, side_tex, side_tex},
+ "",
+ nil,
+ false,
+ S("Outer Glass Stair"))
+
+ stairs.register_slope(
+ "glass",
+ "default:glass",
+ {cracky = 3, oddly_breakable_by_hand = 3},
+ {face_tex},
+ S("Glass Slope"),
+ nil)
+
+ stairs.register_slope_inner(
+ "glass",
+ "default:glass",
+ {cracky = 3, oddly_breakable_by_hand = 3},
+ {face_tex},
+ S("Glass Inner Slope"),
+ nil)
+
+ stairs.register_slope_outer(
+ "glass",
+ "default:glass",
+ {cracky = 3, oddly_breakable_by_hand = 3},
+ {face_tex},
+ S("Glass Outer Slope"),
+ nil)
+
+ -- Old obsidian glass stairs
+-- my_register_all("obsidian_glass", "default:obsidian_glass",
+-- {cracky = 2},
+-- {"default_obsidian_glass.png"},
+-- "Obsidian Glass",
+-- stairs.glass)
+
+ -- Obsidian Glass (stairs registered seperately to use special texture)
+
+ face_tex = "default_obsidian_glass.png"
+ side_tex = gsides and "stairs_obsidian_glass_quarter.png" or face_tex
+
+ stairs.register_stair(
+ "obsidian_glass",
+ "default:obsidian_glass",
+ {cracky = 2},
+ {side_tex, face_tex, side_tex, side_tex, face_tex, side_tex},
+ S("Obsidian Glass Stair"),
+ nil,
+ false)
+
+ stairs.register_slab(
+ "obsidian_glass",
+ "default:obsidian_glass",
+ {cracky = 2},
+ {face_tex, face_tex, side_tex},
+ S("Obsidian Glass Slab"),
+ nil,
+ false)
+
+ stairs.register_stair_inner(
+ "obsidian_glass",
+ "default:obsidian_glass",
+ {cracky = 2},
+ {side_tex, face_tex, side_tex, face_tex, face_tex, side_tex},
+ "",
+ nil,
+ false,
+ S("Inner Obsidian Glass Stair"))
+
+ stairs.register_stair_outer(
+ "obsidian_glass",
+ "default:obsidian_glass",
+ {cracky = 2},
+ {side_tex, face_tex, side_tex, side_tex, side_tex, side_tex},
+ "",
+ nil,
+ false,
+ S("Outer Obsidian Glass Stair"))
+
+ stairs.register_slope(
+ "obsidian_glass",
+ "default:obsidian_glass",
+ {cracky = 2},
+ {face_tex},
+ S("Obsidian Glass Slope"),
+ nil)
+
+ stairs.register_slope_inner(
+ "obsidian_glass",
+ "default:obsidian_glass",
+ {cracky = 2},
+ {face_tex},
+ S("Obsidian Glass Inner Slope"),
+ nil)
+
+ stairs.register_slope_outer(
+ "obsidian_glass",
+ "default:obsidian_glass",
+ {cracky = 2},
+ {face_tex},
+ S("Obsidian Glass Outer Slope"),
+ nil)
+
+ -- Brick, Snow and Ice
+
+ my_register_all("brick", "default:brick",
+ {cracky = 3},
+ {"default_brick.png"},
+ "Brick",
+ nil, false)
+
+ my_register_all("snowblock", "default:snowblock",
+ {crumbly = 3, cools_lava = 1, snowy = 1},
+ {"default_snow.png"},
+ "Snow Block",
+ nil, true)
+
+ my_register_all("ice", "default:ice",
+ {cracky = 3, cools_lava = 1, slippery = 3},
+ {"default_ice.png"},
+ "Ice",
+ nil, true)
+end
+
+-- More Ores mod
+
+if core.get_modpath("moreores") then
+
+ my_register_all("silver_block", "moreores:silver_block",
+ {cracky = 1, level = 2},
+ {"moreores_silver_block.png"},
+ "Silver",
+ nil, true)
+
+ my_register_all("mithril_block", "moreores:mithril_block",
+ {cracky = 1, level = 2},
+ {"moreores_mithril_block.png"},
+ "Mithril",
+ nil, true)
+end
+
+-- Mobs mod
+
+if core.get_modpath("mobs_animal") then
+
+ my_register_all("cheeseblock", "mobs:cheeseblock",
+ {crumbly = 3, flammable = 2},
+ {"mobs_cheeseblock.png"},
+ "Cheese Block",
+ nil, true)
+
+ my_register_all("honey_block", "mobs:honey_block",
+ {crumbly = 3, flammable = 2},
+ {"mobs_honey_block.png"},
+ "Honey Block",
+ nil, true)
+end
+
+-- Homedecor mod
+
+if core.get_modpath("homedecor_roofing") then
+
+ my_register_all("shingles_asphalt", "homedecor:shingles_asphalt",
+ {snappy = 3},
+ {"homedecor_shingles_asphalt.png"},
+ "Asphalt Shingle",
+ nil, true)
+
+ my_register_all("shingles_terracotta", "homedecor:shingles_terracotta",
+ {snappy = 3},
+ {"homedecor_shingles_terracotta.png"},
+ "Terracotta Shingle",
+ nil, true)
+
+ my_register_all("shingles_wood", "homedecor:shingles_wood",
+ {snappy = 3},
+ {"homedecor_shingles_wood.png"},
+ "Wooden Shingle",
+ nil, true)
+end
+
+-- Castle mod
+
+if core.get_modpath("castle_masonry") then
+
+ my_register_all("dungeon_stone", "castle:dungeon_stone",
+ {cracky = 2},
+ {"castle_dungeon_stone.png"},
+ "Dungeon",
+ nil)
+
+ my_register_all("stonewall", "castle:stonewall",
+ {cracky = 2},
+ {"castle_stonewall.png"},
+ "Castle Wall",
+ nil, true)
+end
+
+-- Wool mod
+
+if core.get_modpath("wool") then
+
+ local colours = {
+ {"black", "Black"},
+ {"blue", "Blue"},
+ {"brown", "Brown"},
+ {"cyan", "Cyan"},
+ {"dark_green", "Dark Green"},
+ {"dark_grey", "Dark Grey"},
+ {"green", "Green"},
+ {"grey", "Grey"},
+ {"magenta", "Magenta"},
+ {"orange", "Orange"},
+ {"pink", "Pink"},
+ {"red", "Red"},
+ {"violet", "Violet"},
+ {"white", "White"},
+ {"yellow", "Yellow"},
+ }
+
+ for i = 1, #colours, 1 do
+
+ my_register_all("wool_" .. colours[i][1], "wool:" .. colours[i][1],
+ {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, flammable = 3},
+ {"wool_" .. colours[i][1] .. ".png"},
+ colours[i][2] .. " Wool",
+ nil) -- nil to use node's own sounds
+ end
+end
diff --git a/mods/stairs/textures/stairs_glass_quarter.png b/mods/stairs/textures/stairs_glass_quarter.png
new file mode 100644
index 00000000..2c6b9e8d
Binary files /dev/null and b/mods/stairs/textures/stairs_glass_quarter.png differ
diff --git a/mods/stairs/textures/stairs_obsidian_glass_quarter.png b/mods/stairs/textures/stairs_obsidian_glass_quarter.png
new file mode 100644
index 00000000..6e43396b
Binary files /dev/null and b/mods/stairs/textures/stairs_obsidian_glass_quarter.png differ
diff --git a/mods/xdecor/.gitignore b/mods/xdecor/.gitignore
new file mode 100644
index 00000000..ef02689c
--- /dev/null
+++ b/mods/xdecor/.gitignore
@@ -0,0 +1,22 @@
+## Files related to minetest development cycle
+/*.patch
+# GNU Patch reject file
+*.rej
+
+## Editors and Development environments
+*~
+*.swp
+*.bak*
+*.orig
+# Vim
+*.vim
+# Kate
+.*.kate-swp
+.swp.*
+# Eclipse (LDT)
+.project
+.settings/
+.buildpath
+.metadata
+# Idea IDE
+.idea/*
diff --git a/mods/xdecor/.luacheckrc b/mods/xdecor/.luacheckrc
new file mode 100644
index 00000000..45fb59f5
--- /dev/null
+++ b/mods/xdecor/.luacheckrc
@@ -0,0 +1,14 @@
+allow_defined_top = true
+
+read_globals = {
+ "minetest",
+ "vector", "ItemStack",
+ "default",
+ "stairs", "doors", "xpanes",
+ "xdecor",
+ table = {fields = {"copy"}},
+ string = {fields = {"split"}},
+ "unpack",
+ "stairsplus",
+ "mesecon"
+}
diff --git a/mods/xdecor/API.md b/mods/xdecor/API.md
new file mode 100644
index 00000000..d127ac53
--- /dev/null
+++ b/mods/xdecor/API.md
@@ -0,0 +1,30 @@
+# API for X-Decor-libre
+
+X-Decor-libre is mostly self-contained but it allows for limited extension with
+a simple API. Not that extensibility is not the main goal of this mod.
+
+The function documentation can be found in the respective source code files
+under the header "--[[ API FUNCTIONS ]]".
+
+These are the features:
+
+## Add custom tool enchantments
+
+You can register tools to be able to be enchanted at the enchanting table.
+
+See `src/enchanting.lua` for details.
+
+## Add custom hammers
+
+You can add a custom hammer for repairing tools at the workbench,
+using custom stats.
+
+See `src/workbench.lua` for details.
+
+## Add cut nodes
+
+You can register "cut" node variants of an existing node which can
+be created at the workbench.
+This will add thin stairs, half stairs, panels, microcubes, etc.
+
+See `src/workbench.lua` for details.
diff --git a/mods/xdecor/CHESS_README.md b/mods/xdecor/CHESS_README.md
new file mode 100644
index 00000000..faddcfa8
--- /dev/null
+++ b/mods/xdecor/CHESS_README.md
@@ -0,0 +1,521 @@
+# Chess
+
+## Introduction
+
+You can play Chess in X-Decor-libre!
+
+While the game of Chess is well-known and widespread and its
+rules are well-documented all over the Internet and elsewhere,
+the devil still lies in the detail.
+
+In X-Decor-libre, the game of Chess is closely modeled after
+the FIDE Laws of Chess from January 2023. However, for a
+computer version of Chess, there are still some details
+that might need explanation.
+
+## Objective
+
+Chess is played between two players on a chessboard. One player plays
+with white pieces while the other one plays with black pieces.
+The goal of the game is to put the king of the opponent under attack
+in such a way they have no legal move. This is known as ‘checkmate’.
+It is not allowed to put one’s king in danger, to leave him in danger
+or to capture the opponent’s king.
+
+## How to play
+
+You need a chessboard to play. Craft yourself a chessboard like this:
+
+ BWB
+ sss
+
+* B = Black Dye
+* W = White Dye
+* s = Wooden Slab (from apple tree)
+
+Place the chessboard and examine it. You will see a close-up of the chessboard.
+
+### The Chess interface
+
+On the screen that pops up, you can choose to play against the
+computer (Singleplayer) or another player on the server (Multiplayer).
+You may also use the multiplayer option to play against yourself.
+The computer player is quite weak.
+
+Click on the corresponding button to start the game.
+
+Once the game has started, you see the following things:
+
+To the left, the large chessboard consisting of 8×8 dark and white squares.
+The pieces are put on the chessboard. If there is no active game,
+the chessboard is empty.
+
+During a game, the interface has the following meaning:
+
+Above and below the chessboard, plaques show the name of the players.
+Above the chessboard is the player playing Black and below it
+the player playing White.
+An arrow left of the plaque shows whose turn it is. The name plaques
+may also show the “game status”, such as victory, checkmate (=loss),
+draw, being “in check”, etc.
+
+On the right side, a list of moves that have been made is shown.
+It is written in a figurine long algebraic notation (see appendix).
+
+The two boxes below the list of moves is where all the captured pieces
+go. This has no gameplay significance but it may serve as a visual
+aid to see how badly hurt the player's “armies” are.
+
+The top right corner is used for starting a new game. Press
+“New Game” to start a new game. This ends the current game.
+
+The bottom right corner right corner is used for special
+player actions, such as resigning or claiming a draw.
+
+Note that during a game, the buttons only work for the two players
+playing Chess. They don’t work for anyone else.
+
+## The rules of Chess
+
+### Starting a game
+
+Select Singleplayer or Multiplayer. In Singleplayer, you choose
+the color you play as by clicking the corresponding button.
+
+White always plays first.
+
+In multiplayer, anyone can make the first move.
+The player making the first move as White will play as White,
+the player making the first move as Black will play as Black.
+After that, the players are “locked” to their colors and
+nobody else can play as White or Black.
+
+### The chessboard
+
+The chessboard is a board of 8×8 squares alternating between light
+and dark squares. Each square is either empty or holds exactly one
+chess piece.
+
+### The Chess pieces and how they move
+
+Each player starts with the same pieces on opposing sides of the board,
+only their color is different.
+
+There are 4 types of moves you can make:
+
+* Normal move: You pick up the piece and place it to an empty square
+* Capturing move: You pick up the piece and place it on top of an opposing piece
+ Your piece will land on that square and the opponent’s piece is removed
+* En passant: Special capturing move of the pawn (see below)
+* Castling: Special king+rook move (see below)
+
+It is not possible to place your piece on your own pieces.
+It is not possible to capture a king or your own pieces.
+Any square on which a piece could capture another piece in theory
+(even if it is actually empty) is considered to be “attacked”.
+
+For most pieces, the rules for making a normal move and
+a capturing move are identical. Only for the pawn it is different
+(read below).
+
+If the square of the king is attacked, he and the player playing him
+is considered to be in “check”.
+While a player is in check, any move which would their own
+king under attack is not allowed.
+
+#### How to actually move
+
+Each move can be made by either clicking on the piece and then clicking again
+on the destination. The destination is either an empty square or a square
+occupied by an opponent’s piece (which will be captured).
+You can also do the same via drag-and-drop.
+
+Once you made a valid move by placing the piece to its destination, it is
+final and cannot be taken back. This ends your move and it’s your
+opponent’s turn (exception: promotion, see below).
+
+If you pick up a piece and put it back, nothing happens, it is still
+your turn and you can still do your move normally. Also, if you try
+to make an invalid move, nothing happens as well.
+
+(Nerd info: For the purposes of the FIDE Laws of Chess, pieces are never
+considered “touched” here. Thus, article 4 of the FIDE Laws of Chess has
+no effect.)
+
+#### Rook
+
+The rook looks like a tower and can move to any of square that lies
+in a straight horizontal or vertical line from it.
+It cannot move beyond pieces that are in the way.
+
+The rook may be involved in Castling, see “King” below.
+
+#### Bishop
+
+The bishop can move to any square on a diagonal line from it.
+It cannot move beyond pieces that are in the way.
+
+#### Queen
+
+The queen combines the powers of the rook and bishop and can
+move to any square in a straight horizontal, vertical
+or diagonal line from it.
+It cannot move beyond pieces that are in the way.
+
+#### Knight
+
+The knight looks like a horse and can move to any square closest to
+it that is not in its same horizontal line (also known as “rank”),
+vertical line (also known as “file”), or diagonal of the board.
+To illustrate this:
+
+ ..X.X..
+ .X...X.
+ ...n...
+ .X...X.
+ ..X.X..
+
+In this diagram, “n” represents the knight and the Xes are all the
+possible squares it can theoretically reach. The dots are empty
+squares.
+
+Unlike the other pieces, pieces are never “in the way” of the knight.
+You might say the knight can “jump over” them, if you will.
+
+#### King
+
+The king can move exactly one square in any direction: horizontally,
+vertically or diagonally. Also, the king can never move to any square that
+is attacked by an opponent’s piece.
+
+The king also has a special move called “Castling”.
+
+##### Castling
+
+Castling is a special move in which two pieces move at once.
+Both the king and a rook move horizontally from their starting positions.
+The king will move two squares horizontally and a rook will be
+moved next to him.
+
+Each player has two possible castling moves available, involving each
+of the 2 starting rooks.
+
+Castling has several conditions:
+
+- The king must not have moved yet
+- The rook you wish to castle with must not have moved yet
+- All of the squares between king and rook must be empty
+- The king must not be under attack
+- The king’s destination as well the square it crosses must not be under attack
+- You can castle only horizontally
+
+If all the conditions are met, here’s how you castle:
+
+Place the king two squares towards the rook you want to castle with.
+This square is where the king will end up. The rook will then
+automatically move towards the king and “jump” to the square
+behind the king, from the rook’s viewpoint.
+
+**Remember**: You *must* move the king (not the rook) if you want
+to castle. If you move the rook instead, this is considered
+to be a regular move of the rook alone.
+
+#### Pawn
+
+The pawn has various ways to move.
+The pawn has a “walking direction”, it walks and captures towards
+the opponent’s side (i.e. the side on which the opponent’s
+pieces have started).
+
+The pawn’s basic moves are:
+
+1. Single step: The pawn moves one step vertically towards the
+ opponent’s side.
+2. Double step: Like a single step, but it moves two squares instead.
+ This is only possible from the pawn’s start position.
+
+A pawn can never move backwards.
+
+In both cases, the destination square must be empty as well as any crossed square.
+The pawn cannot capture by a single or double step, however.
+
+The capturing move of the pawn is different. To capture, the pawn has to
+move one step diagonally towards the opponent’s side, either left or right.
+
+To illustrate, in the following diagram, the X’es represent the
+squares attacked by a white pawn (w) and a black pawn (b):
+
+ .X.X..b..
+ ..w..X.X.
+
+##### En passant capture
+
+An en passant capture is a pawn move that is available if a pawn
+of the current player stands on a square left or right from an
+opposing pawn that has made a double step in the previous move.
+
+In this situation, the first pawn may move as if the second pawn
+had made a single step instead. This will be considered as a
+capturing move and the opposing pawn will be removed from the board.
+
+
+Consider this example: Here, “w” represents a white pawn, “b” a black pawn and “.”
+an empty square. White moves upwards and Black downwards. Consider this starting
+position:
+
+ b.
+ ..
+ .w
+
+Now, White does a double step:
+
+ bw
+ ..
+ ..
+
+Black decides to do an en passant capture. For this, the black pawn moves one
+diagonal step towards the square just crossed by the opponent. The white
+pawn is captured and removed.
+
+ ..
+ .b
+ ..
+
+Remember! An 'en passant' capture is only possible in the move directly after
+a pawn’s double step. So if the chance for a particular en passant capture
+is not taken, it will be gone from that point on.
+
+##### Promotion
+
+When a pawn reaches the other end of the chessboard (from its viewpoint),
+it will be promoted. A promotion is considered to be part of the move.
+
+When promotion happens, the boxes where normally the captured pieces go
+will turn into a prompt. The current player must choose a new
+piece to replace the pawn with:
+A queen, rook, bishop or knight of the same color.
+Just click the corresponding button. These buttons only work for the
+current player. Promotion is mandatory and no other moves are possible
+until it is completed.
+
+Once a piece was selected, the pawn will be replaced, which
+immediately activates its powers. This ends the move.
+
+### The end of the game
+
+There are various ways for the game of Chess to end. A game always
+ends in victory of one player, or in a draw.
+
+#### Checkmate
+Checkmating your opponent is the primary goal of Chess.
+The player who has checkmated the opponent king wins the game and ends it.
+
+You are checkmated when it’s your turn, your own king is in check
+(i.e. under attack) and you have no valid move available.
+This immediately ends the game and your opponent wins.
+
+#### Stalemate
+If it’s a player’s turn, but they have no possible move and their
+king is not in check, the game immediately ends in a draw.
+This is called a “stalemate”.
+
+#### Resign
+During the game, the possibility of resigning arises. Resigning
+basically means “giving up” and this leads to an instant loss
+and the victory of your opponent.
+Resigning is available after one’s name has been recorded on
+the name plaque. Resigning is possible even when it’s not your turn.
+
+To resign, click the skull icon in the bottom right.
+
+#### Dead position
+If during the game, on the board there are only the following pieces left,
+the game ends in a draw:
+
+* king versus king
+* king versus king and bishop
+* king versus king and knight
+* king and bishop versus king and bishop, and both bishops stand on squares of the same color
+
+This is called a “dead position”. For example, a board with only a white
+and a black king is a draw.
+
+NOTE: In general, a dead position is any position from which neither player can
+give checkmate, no matter how they move, but only those 4 cases above
+lead to an instant draw in X-Decor-libre because it is tricky to
+determine whether any position is “dead”.
+
+However, dead positions are still guaranteed to end the game eventually
+due to the 75-move rule.
+
+#### 50-move rule
+If in the last 50 consecutive moves of each player, no piece was
+captured and no pawn was moved, the player whose turn it is can invoke
+the 50-move rule to draw the game instantly.
+
+When it’s your turn, and you believe your *next* move will satisfy
+the condition of the 50-move rule, you may also invoke this rule
+to draw the game, but in this case, you still have to make the move.
+If this move satisfies the 50-move rule, the game is drawn.
+But if not, this counts as a normal move, your turn ends and the
+game continues as normal.
+
+A button on the bottom right will appear when this rule is available.
+The button is not shown when there are too few such moves for this
+draw claim to be successful.
+
+The icon represents a barricade, as if the game of Chess itself
+has been blocked. This one will instantly draw the game.
+If you still would have to make the game-drawing move, the
+icon represents half a barricade.
+Note the tooltip.
+
+Note the latter icon is no guarantee you can actually draw the
+game in the next move, only that such a draw claim is plausible.
+
+#### 75-move rule
+If in the last 75 consecutive moves of each player, no piece was captured
+and no pawn was moved, the game automatically ends in a draw.
+
+Exception: If the last move has lead to a checkmate. In this case, checkmate
+takes precedence.
+
+#### Threefold repetition rule
+If the current position has appeared at least 3 times in the game
+the current player can invoke the threefold repetition rule to draw
+the game instantly.
+
+Two positions are considered to be the same “same” if a position in which
+the chessboard has the same pieces of the same color on the same squares,
+it is the same player's turn, the castling rights are the same
+and the vulnerability of pawns to en passant captures (if any) is the same.
+
+Pawns are considered “vulnerable” to an en passant capture immediately
+after a double step turn, no matter if is actually in danger of
+being captured that way.
+
+This rule can also be invoked when you think your *next* move will
+lead to the 3rd (or more) repeated position in the game. This
+works similar as for the 50-move rule.
+
+Like for the 50-move rule, a button appears on the bottom right
+once this rule can be invoked.
+
+If the 3 same position has already occurred, the icon will
+represent 3 chess squares stacked on top of each other.
+If the game-drawing move still has to be made, the top
+square is a “ghost square”.
+
+#### Fivefold repetition rule
+If the same position (as defined above) has appeared at for
+least 5 times, the game is drawn.
+
+#### No agreeing to draw
+
+Unlike in other Chess programs, the players cannot agree to draw.
+
+#### Game result
+
+Once the game has ended, the game result is shown on the name plaques of the
+players as well in chat (to the players only). From this point on, everyone
+(even spectators) can start a new game with “New Game”.
+
+
+## Resetting the chessboard
+
+While a game of Chess is ongoing, the chessboard can’t be dug and the game
+can’t be stopped by other players. But to prevent two players blocking a
+chessboard forever, there is a 5-minute timer. If no player makes a move
+for 5 minutes, then the chessboard can be reset and dug by anyone.
+
+Exception: Players with the `protection_bypass` privilege can always
+dig the chessboard.
+
+
+## Appendix
+
+### The Chess Notation
+
+The chessboard interface shows a list of all moves on the right side.
+
+The list of moves is written in a special notation called “algebraic notation”.
+There are many variants of it, so this section explains what it means in X-Decor-libre.
+
+This mod uses a longform figurine algebraic notation. “figurine” means that
+icons are used for the chess pieces. “longform” means the start
+and end coordinates are shown in full.
+
+Square coordinates are important in any Chess notation. In algebraic notation,
+each square is assigned coordinated with a letter from a to h,
+followed by a number from 1 to 8.
+Provided that the player playing White is on the “bottom” side of the chessboard,
+the squares are numbered from the bottom left square in ascending order.
+The horizontal lines (“ranks”) are numbered 1 to 8, starting from the bottom.
+The vertical lines (“files”) are numbered a to h, starting from the left.
+So from White's viewpoint, the bottom-left square is a1. The square above it
+is a2, then a3, a4, ... a8. The square right of a1 is b1, then c1, d1, ... h1.
+The top-right square is h8.
+
+(Note that on a real chessboard, all of the coordinates are flipped from Black’s viewpoint
+because the board is rotated 180° from their view. In X-Decor-libre, this does not
+matter because the board is always aligned the same way.)
+
+In the list of moves, each line shows 3 things: Move number, white’s move, black’s move (if made).
+The move number is a simple counter that increases after each move of *both* players, starting by 1.
+
+In the notation, a move by a single player is called a “halfmove”. The two moves
+of each White and then Black are called a “fullmove”.
+
+#### Normal moves
+
+Normally, a halfmove is written like this, in this order:
+
+1. Symbol of moved piece (called “figurine”)
+2. Start coordinates, a dash or cross, destination coordinates
+3. “e.p.”, if it was an en passant capture -OR- symbol of piece to which a pawn was promoted to
+
+For number 1, the symbol is only shown if the piece is not a pawn.
+For number 2, the syntax for normal moves is like: “a1–a2”. This means the piece was moved from a1 to a2.
+The dash means it was a normal move.
+For capturing moves, the dash is replaced with a cross “×”. If it was an en passant capture, then
+“ e.p” is appended, like so: “a5×b4 e.p.”.
+If a pawn was promoted, the symbol of the new piece is appended.
+The figurines are always of the color of the player.
+
+Both halfmoves on a line are separated by spacing.
+
+#### Castling
+
+When a player castles, it is notated the following way:
+
+* “0–0” for castling with the rook on file h (“kingside castling”)
+* “0–0–0” for castling with the rook on file a (“queenside castling”)
+
+#### Game completion
+
+If the game came to an end, the game result is written in a final separate line as:
+
+* “1–0” if White won
+* “0–1” if Black won
+* “½–½” in case of a draw
+
+#### Example
+
+ 1. d2–d4 e7–e6
+ 2. ♔e1–d2 ♛d8–h4
+ 3. d4–d5 e6×d5
+ ...
+ 8. d8×d8♖ ♞b8–c6
+ 9. e2–e4 d4×e3 e.p.
+
+Explanation of the moves:
+
+* 1.: First fullmove: White moves pawn from d2 to d4, Black moves pawn from e7 to e6
+* 2.: Second fullmove: White moves king from e1 to d2, Black moves queen from d8 to h4
+* 3.: Third fullmove: White moves pawn from d4 to d5, Black moves pawn from d6 to d5 and captures
+* 8.: Eighth fullmove: White moves pawn from d7 to d8, captures a piece and promotes it to rook, Black moves knight from b8 to c6
+* 9.: Ninth fullmove: White moves pawn from e2 to e4, black moves pawn from d4 to e3 and captures en passant
+
+#### Other symbols
+
+Other symbols are not used. So there are no special symbols for check and checkmate and no comments for moves considered good or bad.
diff --git a/mods/xdecor/LICENSE b/mods/xdecor/LICENSE
new file mode 100644
index 00000000..0bbaa900
--- /dev/null
+++ b/mods/xdecor/LICENSE
@@ -0,0 +1,63 @@
+┌──────────────────────────────────────────────────────────────────────┐
+│ Copyright (c) 2015-2021 kilbith │
+│ │
+│ Code: Modified BSD License │
+│ Textures: CC0 (credits: Gambit, kilbith, Cisoun) │
+│ │
+│ Textures (radio and speaker) by │
+│ MCL (CC BY 4.0 Int'l) │
+│ │
+│ Textures (Chess icons for the Chess notation) │
+│ originally by Wikimedia user Cbnurnett, │
+│ scaled down and edited by Wuzzy (CC BY-SA 3.0 Unported) │
+│ │
+│ Textures (hanging candle) by │
+│ Wuzzy (CC0) │
+│ │
+│ Textures (rooster) by │
+│ sirrobzeroone (CC0) │
+│ │
+│ Sounds: │
+│ - xdecor_boiling_water.ogg - by Audionautics - CC BY 3.0 │
+│ freesound.org/people/Audionautics/sounds/133901/ │
+│ - xdecor_bouncy.ogg - by Blender Foundation - CC BY 3.0 │
+│ opengameart.org/content/funny-comic-cartoon-bounce-sound │
+│ - xdecor_enchanting.ogg - by Kostas17, edit by Wuzzy - CC BY 3.0 │
+│ freesound.org/people/Kostas17/sounds/542825/ │
+└──────────────────────────────────────────────────────────────────────┘
+
+-- Modified BSD License --
+
+Copyright (c) 2015-2021 kilbith
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+* Neither the name of the copyright holder nor the names of contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-- End of Modified BSD License --
+
+## References for other licenses:
+
+* CC BY 3.0: http://creativecommons.org/licenses/by/3.0/
+* CC BY 4.0: http://creativecommons.org/licenses/by/4.0/
+* CC0: https://creativecommons.org/publicdomain/zero/1.0/
diff --git a/mods/xdecor/OLD_README.md b/mods/xdecor/OLD_README.md
new file mode 100644
index 00000000..f52f4296
--- /dev/null
+++ b/mods/xdecor/OLD_README.md
@@ -0,0 +1,19 @@
+Note: This is the original readme of the xdecor mod, added here for documentation/historic reasons.
+
+## X-Decor ##
+
+[](https://content.minetest.net/packages/jp/xdecor/)
+
+A decoration mod meant to be simple and well-featured.
+It adds a bunch of cute cubes, various mechanisms and stuff for [cutting](https://forum.minetest.net/viewtopic.php?f=11&t=14085), [enchanting](https://forum.minetest.net/viewtopic.php?f=11&t=14087), cooking, etc.
+This mod is a lightweight alternative to HomeDecor and MoreBlocks.
+
+### Requirements ###
+This mod requires at least version 5.1 of Minetest.
+
+### Credits ###
+
+Special thanks to Gambit for the textures from the PixelBOX pack for Minetest.
+Thanks to all contributors who keep this mod alive.
+
+
diff --git a/mods/xdecor/README.md b/mods/xdecor/README.md
new file mode 100644
index 00000000..fe2cfbd2
--- /dev/null
+++ b/mods/xdecor/README.md
@@ -0,0 +1,147 @@
+## X-Decor-libre [`xdecor`] ##
+
+[](https://content.luanti.org/packages/Wuzzy/xdecor/)
+
+X-Decor-libre is a libre Luanti mod which adds various decorative blocks
+as well as simple gimmicks.
+
+This is a libre version (free software, free media) of the X-Decor mod for Luanti.
+It is the same as X-Decor, except with all the non-free files replaced and with
+bugfixes. There are no new features.
+
+## New blocks
+
+This mod adds many decoration blocks: Flower pot, weathervane, radio, speaker,
+wooden tile, new bricks, lamps, candles, new doors, packed ice, and more.
+
+This mod also adds 7 new block shapes for many Minetest Game blocks. They can
+be created by using the workbench. This includes panels, mini blocks and flat
+stairs.
+
+## Special nodes
+
+Most blocks in this mod are purely decorative, but there are also many special
+blocks with special features:
+
+* Workbench: Storage, crafting, cutting and repairing
+ * Storage: 16 item slots for item storage
+ * Craft: 3×3 crafting grid
+ * Cut: Put a full cube-shaped block to create new shapes
+ * Repair: Put a damaged tool and a hammer and wait for it to be repaired
+* Enchanting table: Upgrade your tools with mese crystals
+* Ender Chest: Interdimensional inventory that is the same no matter
+ where you put the ender chest
+* Mailbox: Lets you receive items from other players
+* Item Frame: You can place an item into it to show it off
+* Cushion: Reduces fall damage
+* Cushion Block: Reduces fall damage even more
+* Trampoline: Jump on it to bounce off. Very low fall damage
+* Cauldron: For storing water and cooking soups
+ * Recipe: Pour water in, light a fire below it and throw
+ in some food items. Collect the soup with a bowl
+* Lever: Pull the lever to activate doors next to it
+* Pressure Plate: Step on it to activate doors next to it
+* Chessboard: Play Chess against a player or the computer (see `CHESS_README.md`)
+
+## For developers
+
+X-Decor-libre can be extended in a limited fashion. See `API.md` for details.
+
+### X-Decor-libre vs X-Decor
+
+X-Decor is a popular mod in Luanti but it is (as the time of writing this text)
+non-free software, there are various files under proprietary licenses.
+
+The purpose of this repository is to provide the community a fully-free fork of
+X-Decor with clearly documented licenses and to fix bugs. No new features are
+planned.
+
+#### List of changes
+The following bugs of X-Decor (as of 01/07/2023) are fixed:
+
+* Changed packed ice recipe to avoid recipe collision with Ethereal
+* Changed prison door recipe colliding with Minetest Game's Iron Bar Door
+* Beehives no longer show that the bees are busy when they're not
+* Fixed incorrect/incomplete node sounds
+* Fix poorly placed buttons in enchantment screen
+* Fix broken texture of cut Permafrost with Moss nodes
+* Fix awkward lantern rotation
+* Lanterns can no longer attach to sides
+* Fix item stacking issues of curtains
+* Cauldrons no longer turn river water to normal water
+* Fix boiling water in cauldrons not reliably cooling down
+* Fix boiling water sound not playing when rejoining
+* Fix cauldron with soup boiling forever
+* Fix cauldrons being heated up by fireflies
+* Fix rope and painting not compatible with itemframe
+* Fix itemframe, lever being offset when put into itemframe
+* Fix storage formspecs not closing if exploded
+* Show short item description in itemframe instead of itemstring
+* Minor typo fixes
+* Fix bad rope placement prediction
+* Fixed the broken Chess game
+
+Maintenance updates:
+* HUGE rework of Chess to make it actually be like real Chess (more or less)
+* New supported Chess rules (based on the FIDE Laws of Chess)
+ * En passant
+ * Choose your pawn promotion
+ * Fixed incomplete enforcement of castling rule
+ * 50-turn rule and 75-turn rule
+ * Threefold repetition rule and fivefold repetition rule
+ * Announce the winner or loser, or a drawn game
+* Many technical improvements for Chess
+* Renamed blocks:
+ * "Empty Shelf" to "Plain Shelf"
+ * "Slide Door" to "Paper Door"
+ * "Rooster" to "Weathercock"
+ * "Stone Tile" to "Polished Stone Block"
+ * "Desert Stone Tile" to "Polished Desert Stone Block"
+ * "Iron Light Box" to "Steel Lattice Light Box"
+ * "Wooden Light Box" to "Wooden Cross Light Box"
+ * "Wooden Light Box 2" to "Wooden Rhombus Light Box"
+* Added fuel recipes for wooden-based things
+* Changed a few confusing recipes to make more sense
+* Improved textures for cut glass, obsidian glass, woodframed glass,
+ permafrost with moss and permafrost with stones
+* Improved side texture of wood frame and rusty bar
+* Add honey and cushion block to creative inventory
+* Doors now count as nodes in creative inventory
+* Cobwebs are no longer considered (fake) liquids
+* Storage blocks now drop their inventory when exploded
+* Made several strings translatable
+* Translation updates
+* Add description to every setting
+* Add tooltip extensions for some interactive items (uses `tt` mod)
+* Add crafting guide support for `unified_inventory` mod (honey)
+* Rope no longer extends infinitely in Creative Mode
+* Added manual for Chess in `CHESS_README.md`
+
+#### List of replaced files
+
+This is the list of non-free files in the original X-Decor mod
+(as of commit 8b614b3513f2719d5975c883180c011cb7428c8d)
+that X-Decor-libre replaces:
+
+* `textures/xdecor_candle_hanging.png`
+* `textures/xdecor_radio_back.png`
+* `textures/xdecor_radio_front.png`
+* `textures/xdecor_radio_side.png`
+* `textures/xdecor_radio_top.png`
+* `textures/xdecor_rooster.png`
+* `textures/xdecor_speaker_back.png`
+* `textures/xdecor_speaker_front.png`
+* `textures/xdecor_speaker_side.png`
+* `textures/xdecor_speaker_top.png`
+* `sounds/xdecor_enchanting.ogg`
+
+(see `LICENSE` file for licensing).
+
+## Technical information
+X-Decor-libre is a fork of X-Decor, from ,
+forked at Git commit ID 8b614b3513f2719d5975c883180c011cb7428c8d.
+
+Note the technical mod name of X-Decor-libre is the same as for X-Decor: `xdecor`.
+This is because this mod is meant to be a drop-in-replacement.
+
+The original readme of X-Decor can be found at `OLD_README.md`.
diff --git a/mods/xdecor/handlers/animations.lua b/mods/xdecor/handlers/animations.lua
new file mode 100644
index 00000000..147cb691
--- /dev/null
+++ b/mods/xdecor/handlers/animations.lua
@@ -0,0 +1,154 @@
+local mod_player_api = minetest.get_modpath("player_api") ~= nil
+
+local sitting = {}
+local seats_occupied = {}
+
+local function bottom_face(pointed_thing)
+ if not pointed_thing then
+ return
+ end
+ return pointed_thing.above.y < pointed_thing.under.y
+end
+
+local function stand_up(player_name)
+ if not mod_player_api then
+ return
+ end
+ local player = minetest.get_player_by_name(player_name)
+ if not player then
+ return
+ end
+ player_api.player_attached[player_name] = false
+
+ local old_anim = player_api.get_animation(player)
+ if old_anim and old_anim.animation == "sit" then
+ player_api.set_animation(player, "stand")
+ end
+
+ local hash = minetest.hash_node_position(sitting[player_name])
+ seats_occupied[hash] = nil
+ sitting[player_name] = nil
+
+ minetest.log("action", "[xdecor] "..player_name.." stands up at "..minetest.pos_to_string(player:get_pos(), 0))
+end
+
+--[[ Used when player interacts with "sittable" node to sit down
+or stand up when interacting with that node again. Should
+be used in `on_rightclick` handler
+* `pos`: Position where to sit down player (MUST only use integers for coordinates!)
+* `node`: Node table of node to sit on
+* `clicker`: Player who interacted with node (from `on_rightclick`)
+* `pointed_thing`: From `on_rightclick` ]]
+function xdecor.sit(pos, node, clicker, pointed_thing)
+ if not mod_player_api then
+ return
+ end
+ -- Can't sit down if bottom face was pointed at
+ if bottom_face(pointed_thing) then
+ return
+ end
+ local player_name = clicker:get_player_name()
+ local objs = minetest.get_objects_inside_radius(pos, 0.1)
+ local vel = clicker:get_velocity()
+ local ctrl = clicker:get_player_control()
+
+ -- Stand up if sitting
+ if sitting[player_name] then
+ stand_up(player_name)
+
+ -- Sit down if not sitting and not attached
+ elseif not sitting[player_name] and not player_api.player_attached[player_name] and node.param2 <= 3 and
+ not ctrl.sneak and vector.equals(vel, vector.new()) then
+
+ -- Can't sit down on note already occupied by player
+ local hash = minetest.hash_node_position(pos)
+ if seats_occupied[hash] then
+ return
+ end
+
+ player_api.player_attached[player_name] = true
+ player_api.set_animation(clicker, "sit")
+ sitting[player_name] = table.copy(pos)
+ seats_occupied[hash] = player_name
+ clicker:set_pos(pos)
+
+ if node.param2 == 0 then
+ clicker:set_look_horizontal(0)
+ elseif node.param2 == 1 then
+ clicker:set_look_horizontal(3*(math.pi/2))
+ elseif node.param2 == 2 then
+ clicker:set_look_horizontal(math.pi)
+ elseif node.param2 == 3 then
+ clicker:set_look_horizontal(math.pi/2)
+ end
+
+ minetest.log("action", "[xdecor] "..player_name.." sits down at "..minetest.pos_to_string(pos, 0))
+ end
+end
+
+-- Called when `digger` (a player object) wants to
+-- dig a node at pos. Returns true if it's allowed,
+-- false otherwise. This checks if the node at pos
+-- is an occupied sittable node.
+-- Can be used for the `can_dig` node function.
+function xdecor.sit_dig(pos, digger)
+ if not mod_player_api then
+ return true
+ end
+ local hash = minetest.hash_node_position(pos)
+ if seats_occupied[hash] then
+ return false
+ end
+
+ return true
+end
+
+-- To be called when a seat (sittable node) got destroyed
+-- to clean up state. Precisely, this should be used
+-- as the `after_destruct` handler.
+function xdecor.sit_destruct(pos)
+ local hash = minetest.hash_node_position(pos)
+ local occupier = seats_occupied[hash]
+ if occupier then
+ stand_up(occupier)
+ seats_occupied[hash] = nil
+ sitting[occupier] = nil
+ end
+end
+
+-- Automatically cause players to stand up if they pressed a control
+-- or moved away from the seat
+minetest.register_globalstep(function(dtime)
+ local to_stand_up = {}
+ for player_name, sitting_pos in pairs(sitting) do
+ local player = minetest.get_player_by_name(player_name)
+ if player then
+ local ctrl = player:get_player_control()
+ if ctrl.up or ctrl.down or ctrl.left or ctrl.right or ctrl.sneak or ctrl.jump then
+ table.insert(to_stand_up, player_name)
+ elseif vector.distance(player:get_pos(), sitting_pos) > 0.55 then
+ table.insert(to_stand_up, player_name)
+ end
+ end
+ end
+ for s=1, #to_stand_up do
+ stand_up(to_stand_up[s])
+ end
+end)
+
+-- Force player to stand on death (to the seat is released)
+minetest.register_on_dieplayer(function(player)
+ local player_name = player:get_player_name()
+ if sitting[player_name] then
+ stand_up(player_name)
+ end
+end)
+
+minetest.register_on_leaveplayer(function(player)
+ local player_name = player:get_player_name()
+ if sitting[player_name] then
+ local hash = minetest.hash_node_position(sitting[player_name])
+ seats_occupied[hash] = nil
+ sitting[player_name] = nil
+ end
+end)
diff --git a/mods/xdecor/handlers/glasscut.lua b/mods/xdecor/handlers/glasscut.lua
new file mode 100644
index 00000000..f87aed66
--- /dev/null
+++ b/mods/xdecor/handlers/glasscut.lua
@@ -0,0 +1,170 @@
+-- Tile definitions for cut nodes of glass nodes:
+-- * Woodframed Glass (this mod)
+-- * Glass (Minetest Game)
+-- * Obsidian Glass (Minetest Game)
+-- This is done so the glass nodes still look nice
+-- when cut.
+-- If we would only use the base glass tile, most
+-- cut nodes look horrible because there are no
+-- clear contours.
+
+local template_suffixes_glass = {
+ stair = {
+ "_split.png",
+ ".png",
+ "_stairside.png^[transformFX",
+ "_stairside.png",
+ ".png",
+ "_split.png",
+ },
+ stair_inner = {
+ "_stairside.png^[transformR270",
+ ".png",
+ "_stairside.png^[transformFX",
+ ".png",
+ ".png",
+ "_stairside.png",
+ },
+ stair_outer = {
+ "_stairside.png^[transformR90",
+ ".png",
+ "_outer_stairside.png",
+ "_stairside.png",
+ "_stairside.png^[transformR90",
+ "_outer_stairside.png",
+ },
+ halfstair = {
+ "_cube.png",
+ ".png",
+ "_stairside.png^[transformFX",
+ "_stairside.png",
+ "_split.png^[transformR90",
+ "_cube.png",
+ },
+ slab = {
+ ".png",
+ ".png",
+ "_split.png",
+ },
+ cube = { "_cube.png" },
+ thinstair = { "_split.png" },
+ micropanel = { "_split.png" },
+ panel = {
+ "_split.png",
+ "_split.png",
+ "_cube.png",
+ "_cube.png",
+ "_split.png",
+ },
+}
+
+-- Template for "grass-covered" and similar nodes.
+-- This is defined in a way so that the cut nodes
+-- still have a natural-looking grass cover.
+-- !TOP and !BOTTOM are special and will be
+-- replaced via function argument.
+
+local template_suffixes_grasscover = {
+ stair = {
+ "!TOP",
+ "!BOTTOM",
+ "_stairside.png^[transformFX",
+ "_stairside.png",
+ ".png",
+ "_split.png",
+ },
+ stair_inner = {
+ "!TOP",
+ "!BOTTOM",
+ "_stairside.png^[transformFX",
+ ".png",
+ ".png",
+ "_stairside.png",
+ },
+ stair_outer = {
+ "!TOP",
+ "!BOTTOM",
+ "_outer_stairside.png",
+ "_stairside.png",
+ "_stairside.png^[transformR90",
+ "_outer_stairside.png",
+ },
+ halfstair = {
+ "!TOP",
+ "!BOTTOM",
+ "_stairside.png^[transformFX",
+ "_stairside.png",
+ ".png",
+ "_cube.png",
+ },
+ slab = {
+ "!TOP",
+ "!BOTTOM",
+ "_split.png",
+ },
+ cube = { "!TOP", "!BOTTOM", "_cube.png" },
+ thinstair = { "!TOP", "!BOTTOM", "_cube.png" },
+ micropanel = { "!TOP", "!BOTTOM", "!TOP" },
+ nanoslab = { "!TOP", "!BOTTOM", "!TOP" },
+ microslab = { "!TOP", "!BOTTOM", "!TOP" },
+ panel = {
+ "!TOP",
+ "!BOTTOM",
+ "_cube.png",
+ "_cube.png",
+ "_split.png",
+ },
+}
+
+local generate_tilenames_glass = function(prefix, default_texture)
+ if not default_texture then
+ default_texture = prefix
+ end
+ local cuts = {}
+ for t, tiles in pairs(template_suffixes_glass) do
+ cuts[t] = {}
+ for i=1, #tiles do
+ if tiles[i] == ".png" then
+ cuts[t][i] = default_texture .. tiles[i]
+ else
+ cuts[t][i] = prefix .. tiles[i]
+ end
+ end
+ end
+ return cuts
+end
+
+local generate_tilenames_grasscover = function(prefix, default_texture, base, top, bottom)
+ if not default_texture then
+ default_texture = prefix
+ end
+ local cuts = {}
+ for t, tiles in pairs(template_suffixes_grasscover) do
+ cuts[t] = {}
+ for i=1, #tiles do
+ if tiles[i] == "!TOP" then
+ cuts[t][i] = {name=top, align_style="world"}
+ elseif tiles[i] == "!BOTTOM" then
+ cuts[t][i] = {name=bottom, align_style="world"}
+ else
+ if tiles[i] == ".png" then
+ cuts[t][i] = default_texture .. tiles[i]
+ else
+ cuts[t][i] = prefix .. tiles[i]
+ end
+ if base then
+ cuts[t][i] = base .. "^" .. cuts[t][i]
+ end
+ end
+ end
+ end
+ return cuts
+end
+
+xdecor.glasscuts = {
+ ["xdecor:woodframed_glass"] = generate_tilenames_glass("xdecor_woodframed_glass"),
+ ["default:glass"] = generate_tilenames_glass("stairs_glass", "default_glass"),
+ ["default:obsidian_glass"] = generate_tilenames_glass("stairs_obsidian_glass", "default_obsidian_glass"),
+ ["default:permafrost_with_moss"] = generate_tilenames_grasscover("xdecor_permafrost_moss", "default_moss_side", "default_permafrost.png", "default_moss.png", "default_permafrost.png"),
+ ["default:permafrost_with_stones"] = generate_tilenames_grasscover("xdecor_permafrost_stones", "default_stones_side", "default_permafrost.png", "default_permafrost.png^default_stones.png", "default_permafrost.png"),
+}
diff --git a/mods/xdecor/handlers/helpers.lua b/mods/xdecor/handlers/helpers.lua
new file mode 100644
index 00000000..42635f01
--- /dev/null
+++ b/mods/xdecor/handlers/helpers.lua
@@ -0,0 +1,68 @@
+-- Returns the greatest numeric key in a table.
+function xdecor.maxn(T)
+ local n = 0
+ for k in pairs(T) do
+ if k > n then
+ n = k
+ end
+ end
+
+ return n
+end
+
+-- Returns the length of an hash table.
+function xdecor.tablelen(T)
+ local n = 0
+
+ for _ in pairs(T) do
+ n = n + 1
+ end
+
+ return n
+end
+
+-- Deep copy of a table. Borrowed from mesecons mod (https://github.com/Jeija/minetest-mod-mesecons).
+function xdecor.tablecopy(T)
+ if type(T) ~= "table" then
+ return T -- No need to copy.
+ end
+
+ local new = {}
+
+ for k, v in pairs(T) do
+ if type(v) == "table" then
+ new[k] = xdecor.tablecopy(v)
+ else
+ new[k] = v
+ end
+ end
+
+ return new
+end
+
+function xdecor.stairs_valid_def(def)
+ return (def.drawtype == "normal" or def.drawtype:sub(1,5) == "glass") and
+ (def.groups.cracky or def.groups.choppy) and
+ not def.on_construct and
+ not def.after_place_node and
+ not def.on_rightclick and
+ not def.on_blast and
+ not def.allow_metadata_inventory_take and
+ not (def.groups.not_in_creative_inventory == 1) and
+ not (def.groups.not_cuttable == 1) and
+ not def.groups.wool and
+ (def.tiles and type(def.tiles[1]) == "string" and not
+ def.tiles[1]:find("default_mineral")) and
+ not def.mesecons and
+ def.description and
+ def.description ~= "" and
+ def.light_source == 0
+end
+
+function xdecor.get_inventory_drops(pos, listnames)
+ local drops = {}
+ for l=1, #listnames do
+ default.get_inventory_drops(pos, listnames[l], drops)
+ end
+ return drops
+end
diff --git a/mods/xdecor/handlers/nodeboxes.lua b/mods/xdecor/handlers/nodeboxes.lua
new file mode 100644
index 00000000..4a3d8ca5
--- /dev/null
+++ b/mods/xdecor/handlers/nodeboxes.lua
@@ -0,0 +1,67 @@
+xdecor.box = {
+ slab_y = function(height, shift)
+ return {
+ -0.5,
+ -0.5 + (shift or 0),
+ -0.5,
+ 0.5,
+ -0.5 + height + (shift or 0),
+ 0.5
+ }
+ end,
+ slab_z = function(depth)
+ return {-0.5, -0.5, -0.5 + depth, 0.5, 0.5, 0.5}
+ end,
+ bar_y = function(radius)
+ return {-radius, -0.5, -radius, radius, 0.5, radius}
+ end,
+ cuboid = function(radius_x, radius_y, radius_z)
+ return {-radius_x, -radius_y, -radius_z, radius_x, radius_y, radius_z}
+ end
+}
+
+xdecor.nodebox = {
+ regular = {type = "regular"},
+ null = {
+ type = "fixed", fixed = {0,0,0,0,0,0}
+ }
+}
+
+xdecor.pixelbox = function(size, boxes)
+ local fixed = {}
+ for _, box in ipairs(boxes) do
+ -- `unpack` has been changed to `table.unpack` in newest Lua versions.
+ local x, y, z, w, h, l = unpack(box)
+ fixed[#fixed + 1] = {
+ (x / size) - 0.5,
+ (y / size) - 0.5,
+ (z / size) - 0.5,
+ ((x + w) / size) - 0.5,
+ ((y + h) / size) - 0.5,
+ ((z + l) / size) - 0.5
+ }
+ end
+
+ return {type = "fixed", fixed = fixed}
+end
+
+local mt = {}
+
+mt.__index = function(table, key)
+ local ref = xdecor.box[key]
+ local ref_type = type(ref)
+
+ if ref_type == "function" then
+ return function(...)
+ return {type = "fixed", fixed = ref(...)}
+ end
+ elseif ref_type == "table" then
+ return {type = "fixed", fixed = ref}
+ elseif ref_type == "nil" then
+ error(key .. "could not be found among nodebox presets and functions")
+ end
+
+ error("unexpected datatype " .. tostring(type(ref)) .. " while looking for " .. key)
+end
+
+setmetatable(xdecor.nodebox, mt)
diff --git a/mods/xdecor/handlers/registration.lua b/mods/xdecor/handlers/registration.lua
new file mode 100644
index 00000000..44eacc2e
--- /dev/null
+++ b/mods/xdecor/handlers/registration.lua
@@ -0,0 +1,183 @@
+local S = minetest.get_translator("xdecor")
+
+xdecor.xbg = default.gui_bg .. default.gui_bg_img .. default.gui_slots
+local default_inventory_size = 32
+
+local default_inventory_formspecs = {
+ ["8"] = [[ size[8,6]
+ list[context;main;0,0;8,1;]
+ list[current_player;main;0,2;8,4;]
+ listring[current_player;main]
+ listring[context;main] ]] ..
+ default.get_hotbar_bg(0,2),
+
+ ["16"] = [[ size[8,7]
+ list[context;main;0,0;8,2;]
+ list[current_player;main;0,3;8,4;]
+ listring[current_player;main]
+ listring[context;main] ]] ..
+ default.get_hotbar_bg(0,3),
+
+ ["24"] = [[ size[8,8]
+ list[context;main;0,0;8,3;]
+ list[current_player;main;0,4;8,4;]
+ listring[current_player;main]
+ listring[context;main]" ]] ..
+ default.get_hotbar_bg(0,4),
+
+ ["32"] = [[ size[8,9]
+ list[context;main;0,0.3;8,4;]
+ list[current_player;main;0,4.85;8,1;]
+ list[current_player;main;0,6.08;8,3;8]
+ listring[current_player;main]
+ listring[context;main] ]] ..
+ default.get_hotbar_bg(0,4.85)
+}
+
+local function get_formspec_by_size(size)
+ local formspec = default_inventory_formspecs[tostring(size)]
+ return formspec or default_inventory_formspecs
+end
+
+local default_can_dig = function(pos)
+ local inv = minetest.get_meta(pos):get_inventory()
+ return inv:is_empty("main")
+end
+
+local function xdecor_stairs_alternative(nodename, def)
+ local mod, name = nodename:match("(.*):(.*)")
+
+ for groupname, value in pairs(def.groups) do
+ if groupname ~= "cracky" and groupname ~= "choppy" and
+ groupname ~= "flammable" and groupname ~= "crumbly" and
+ groupname ~= "snappy" then
+ def.groups.groupname = nil
+ end
+ end
+
+ if minetest.get_modpath("moreblocks") then
+ stairsplus:register_all(
+ mod,
+ name,
+ nodename,
+ {
+ description = def.description,
+ tiles = def.tiles,
+ groups = def.groups,
+ sounds = def.sounds,
+ }
+ )
+ elseif minetest.get_modpath("stairs") then
+ local custom_tiles = xdecor.glasscuts[nodename]
+ if custom_tiles and (custom_tiles.slab or custom_tiles.stair) then
+ if custom_tiles.stair then
+ stairs.register_stair(name, nodename,
+ def.groups, custom_tiles.stair, S("@1 Stair", def.description),
+ def.sounds)
+ stairs.register_stair_inner(name, nodename,
+ def.groups, custom_tiles.stair_inner, "", def.sounds, nil, S("Inner @1 Stair", def.description))
+ stairs.register_stair_outer(name, nodename,
+ def.groups, custom_tiles.stair_outer, "", def.sounds, nil, S("Outer @1 Stair", def.description))
+ end
+ if custom_tiles.slab then
+ stairs.register_slab(name, nodename,
+ def.groups, custom_tiles.slab, S("@1 Slab", def.description),
+ def.sounds)
+ end
+ else
+ stairs.register_stair_and_slab(name,nodename,
+ def.groups,
+ def.tiles,
+ S("@1 Stair", def.description),
+ S("@1 Slab", def.description),
+ def.sounds, nil,
+ S("Inner @1 Stair", def.description),
+ S("Outer @1 Stair", def.description)
+ )
+ end
+ end
+ end
+
+function xdecor.register(name, def)
+ def.drawtype = def.drawtype or (def.mesh and "mesh") or (def.node_box and "nodebox")
+ def.sounds = def.sounds or default.node_sound_defaults()
+
+ if not (def.drawtype == "normal" or def.drawtype == "signlike" or
+ def.drawtype == "plantlike" or def.drawtype == "glasslike_framed" or
+ def.drawtype == "glasslike_framed_optional") then
+ def.paramtype2 = def.paramtype2 or "facedir"
+ end
+
+ if def.sunlight_propagates ~= false and
+ (def.drawtype == "plantlike" or def.drawtype == "torchlike" or
+ def.drawtype == "signlike" or def.drawtype == "fencelike") then
+ def.sunlight_propagates = true
+ end
+
+ if not def.paramtype and
+ (def.light_source or def.sunlight_propagates or
+ def.drawtype == "nodebox" or def.drawtype == "mesh") then
+ def.paramtype = "light"
+ end
+
+ local infotext = def.infotext
+ local inventory = def.inventory
+ def.inventory = nil
+
+ if inventory then
+ def.on_construct = def.on_construct or function(pos)
+ local meta = minetest.get_meta(pos)
+ if infotext then meta:set_string("infotext", infotext) end
+
+ local size = inventory.size or default_inventory_size
+ local inv = meta:get_inventory()
+
+ inv:set_size("main", size)
+ meta:set_string("formspec",
+ (inventory.formspec or get_formspec_by_size(size)) .. xdecor.xbg)
+ end
+
+ def.can_dig = def.can_dig or default_can_dig
+
+ elseif infotext and not def.on_construct then
+ def.on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", infotext)
+ end
+ end
+
+ minetest.register_node("xdecor:" .. name, def)
+
+ local workbench = minetest.settings:get_bool("enable_xdecor_workbench")
+
+ if workbench == false and
+ (minetest.get_modpath("moreblocks") or minetest.get_modpath("stairs")) then
+ if xdecor.stairs_valid_def(def) then
+ xdecor_stairs_alternative("xdecor:"..name, def)
+ end
+ end
+end
+
+-- Registers aliases for a node that had a name collision
+-- with a node from the moreblocks mod
+function xdecor.register_legacy_aliases(original_basename, new_basename)
+ minetest.register_alias("xdecor:"..original_basename, "xdecor:"..new_basename)
+ minetest.register_alias("xdecor:"..original_basename.."_panel", "xdecor:"..new_basename.."_panel")
+ minetest.register_alias("xdecor:"..original_basename.."_doublepanel", "xdecor:"..new_basename.."_doublepanel")
+ minetest.register_alias("xdecor:"..original_basename.."_micropanel", "xdecor:"..new_basename.."_micropanel")
+ minetest.register_alias("xdecor:"..original_basename.."_halfstair", "xdecor:"..new_basename.."_halfstair")
+ minetest.register_alias("xdecor:"..original_basename.."_thinstair", "xdecor:"..new_basename.."_thinstair")
+ minetest.register_alias("xdecor:"..original_basename.."_cube", "xdecor:"..new_basename.."_cube")
+ minetest.register_alias("xdecor:"..original_basename.."_microslab", "xdecor:"..new_basename.."_microslab")
+ minetest.register_alias("xdecor:"..original_basename.."_nanoslab", "xdecor:"..new_basename.."_nanoslab")
+ if not minetest.get_modpath("moreblocks") then
+ minetest.register_alias("stairs:slab_"..original_basename, "stairs:slab_"..new_basename)
+ minetest.register_alias("stairs:stair_"..original_basename, "stairs:stair_"..new_basename)
+ minetest.register_alias("stairs:stair_outer_"..original_basename, "stairs:stair_outer_"..new_basename)
+ minetest.register_alias("stairs:stair_inner_"..original_basename, "stairs:stair_inner"..new_basename)
+ end
+ if minetest.get_modpath("stairsplus") and minetest.global_exists("stairsplus") and stairsplus.api then
+ stairsplus.api.register_alias_all("xdecor:"..original_basename, "xdecor:"..new_basename)
+ end
+end
+
diff --git a/mods/xdecor/init.lua b/mods/xdecor/init.lua
new file mode 100644
index 00000000..090a76c3
--- /dev/null
+++ b/mods/xdecor/init.lua
@@ -0,0 +1,44 @@
+--local t = os.clock()
+
+xdecor = {}
+local modpath = minetest.get_modpath("xdecor")
+
+dofile(modpath .. "/handlers/glasscut.lua")
+dofile(modpath .. "/handlers/animations.lua")
+dofile(modpath .. "/handlers/helpers.lua")
+dofile(modpath .. "/handlers/nodeboxes.lua")
+dofile(modpath .. "/handlers/registration.lua")
+
+dofile(modpath .. "/src/nodes.lua")
+dofile(modpath .. "/src/recipes.lua")
+
+-- Load modules that can be enabled and disabled by settings
+local subpart = {
+ "chess",
+ "cooking",
+ "enchanting",
+ "hive",
+ "itemframe",
+ "mailbox",
+ "mechanisms",
+ "rope",
+ -- Workbench MUST be loaded after all other subparts that register nodes
+ -- last for the default 'cut node' registrations to work
+ "workbench",
+}
+
+for _, name in ipairs(subpart) do
+ local enable = minetest.settings:get_bool("enable_xdecor_" .. name, true)
+ if enable then
+ dofile(modpath .. "/src/" .. name .. ".lua")
+ end
+end
+
+-- Special case: enchanted tools. This code is split from enchanting to
+-- deal with loading order.
+-- Enchanted tools registered last because they depend on previous
+-- subparts
+local enable_enchanting = minetest.settings:get_bool("enable_xdecor_enchanting", true)
+if enable_enchanting then
+ dofile(modpath .. "/src/enchanted_tools.lua")
+end
diff --git a/mods/xdecor/locale/template.txt b/mods/xdecor/locale/template.txt
new file mode 100644
index 00000000..4e52ab8b
--- /dev/null
+++ b/mods/xdecor/locale/template.txt
@@ -0,0 +1,217 @@
+# textdomain: xdecor
+A libre decoration mod meant to be simple and well-featured.=
+@1 Stair=
+Inner @1 Stair=
+Outer @1 Stair=
+@1 Slab=
+Weak Computer=
+Weak Computer 1=
+Weak Computer 2=
+Chess=
+Chess Debug=
+Select a game mode=
+Select a mode:=
+Singleplayer=
+Multiplayer=
+Bot vs Bot=
+check=
+checkmate=
+resigned=
+winner=
+loser=
+draw=
+PROMOTION@nFOR BLACK!=
+Promote pawn to:=
+PROMOTION@nFOR WHITE!=
+DRAW CLAIM@nBY WHITE!=
+DRAW CLAIM@nBY BLACK!=
+The player has invoked the 50-move rule for the next move. The next move might draw the game.=
+The player has invoked the threefold-repetition rule for the next move. The next move might draw the game.=
+New game=
+Resign=
+Select a color:=
+White=
+Black=
+Invoke the 50-move rule for your next move=
+Invoke the 50-move rule and draw the game=
+Invoke the threefold repetition rule and draw the game=
+Invoke the threefold repetition rule for your next move=
+You have checkmated @1. You win!=
+You were checkmated by @1. You lose!=
+The game ended up in a stalemate! It's a draw!=
+The game ended up in a dead position! It's a draw!=
+No piece was captured and no pawn was moved for 75 consecutive moves of each player. It's a draw!=
+You have drawn the game by invoking the 50-move rule.=
+@1 has drawn the game by invoking the 50-move rule.=
+You have failed to make a game-drawing move. The game continues.=
+@1 made a draw claim using the 50-move rule but it was false. The game continues.=
+The exact same position has occured 5 times. It's a draw!=
+You have drawn the game by invoking the threefold repetition rule.=
+@1 has drawn the game by invoking the threefold repetition rule.=
+@1 made a draw claim using the threefold repetition rule but it was false. The game continues.=
+Chess Board=
+Someone else plays white pieces!=
+It's not your turn!=
+Someone else plays black pieces!=
+Black cannot move first!=
+@1 s=
+@1 min @2 s=
+You can't reset the chessboard, a game has been started. Try again in @1.=
+Resigning is not possible yet.=
+You have resigned.=
+@1 has resigned. You win!=
+You can't resign, you're not playing in this game.=
+You can't claim a draw, it's not your turn!=
+You're only a spectator in this game of Chess.=
+This isn't the time for promotion.=
+It's not your turn! This promotion is meant for the other player.=
+You can't dig the chessboard, a game has been started. Reset it first or dig it again in @1.=
+You can't dig the chessboard, a game has been started. Try it again in @1.=
+Play a game of Chess against another player or the computer=
+White Pawn=
+Black Pawn=
+White Rook=
+Black Rook=
+White Knight=
+Black Knight=
+White Bishop=
+Black Bishop=
+White Queen=
+Black Queen=
+White King=
+Black King=
+Light a fire below to heat it up=
+Use a bowl to eat the soup=
+Drop foods inside to make a soup=
+Cauldron (empty)=
+Cauldron (cold water)=
+Cauldron (cold river water)=
+Cauldron (cold soup)=
+Cauldron (boiling water)=
+Cauldron (boiling river water)=
+Cauldron (boiling soup)=
+No room in your inventory to add a bucket of water.=
+No room in your inventory to add a bowl of soup.=
+Cauldron=
+For storing water and cooking soup=
+Cauldron with Water (cold)=
+Cauldron with River Water (cold)=
+Cauldron with Soup (cold)=
+Cauldron with Water (boiling)=
+Cauldron with River Water (boiling)=
+Cauldron with Soup (boiling)=
+Bowl=
+Bowl of soup=
+Efficiency=
+Durability=
+Sharpness=
+@1 (+@2%)=
+Your weapon inflicts more damage=
+Your tool lasts longer=
+Your tool digs faster=
+Enchantment Table=
+Enchant your tools with mese crystals=
+Enchanted @1@n@2=
+Enchanted @1=
+The bees are busy making honey.=
+The bees are looking for flowers.=
+The bees want to pollinate more flowers.=
+The bees are idle.=
+The bees are resting.=
+Artificial Hive=
+Bees live here and produce honey=
+Honey=
+Made by bees=
+@1 (owned by @2)=
+Item Frame=
+For presenting a single item=
+× @1=
+Mailbox=
+Last donators=
+Send your goods to@n@1=
+@1's Mailbox=
+The mailbox is full.=
+Lets other players give you things=
+Opens doors when stepped on=
+Wooden Pressure Plate=
+Stone Pressure Plate=
+Lever=
+Opens doors when pulled=
+Bamboo Frame=
+Chainlink=
+Rusty Iron Bars=
+Wood Frame=
+Barricade=
+Barrel=
+Wooden Cabinet=
+24 inventory slots=
+Half Wooden Cabinet=
+8 inventory slots=
+Plain Shelf=
+Multi Shelf=
+Candle=
+Chair=
+Cobweb=
+Red Curtain=
+Cushion=
+Cushion Block=
+Japanese Door=
+Prison Door=
+Rusty Prison Door=
+Screen Door=
+Paper Door=
+Woodglass Door=
+Ender Chest=
+Interdimensional inventory=
+Ivy=
+Weathercock=
+Lantern=
+Hanging Lantern=
+Steel Lattice Light Box=
+Wooden Cross Light Box=
+Wooden Rhombus Light Box=
+Potted White Dandelion=
+Potted Yellow Dandelion=
+Potted Geranium=
+Potted Rose=
+Potted Tulip=
+Potted Viola=
+Painting=
+Garden Stone Path=
+Cactus Brick=
+Coal Stone Tile=
+Polished Desert Stone Block=
+Hardened Clay=
+Moon Brick=
+Runestone=
+Polished Stone Block=
+Packed Ice=
+Wooden Tile=
+Table=
+Tatami=
+Trampoline=
+Television=
+Wood Framed Glass=
+Radio=
+Speaker=
+Rope=
+Nanoslab=
+Micropanel=
+Microslab=
+Thin Stair=
+Cube=
+Panel=
+Slab=
+Double Panel=
+Half-Stair=
+Stair=
+Cut=
+Repair=
+Crafting=
+Storage=
+Back=
+Work Bench=
+For cutting blocks, repairing tools with a hammer, crafting and storing items=
+@1 @2=
+Repairs tools at the work bench=
+Hammer=
diff --git a/mods/xdecor/locale/xdecor.de.tr b/mods/xdecor/locale/xdecor.de.tr
new file mode 100644
index 00000000..1b3a0809
--- /dev/null
+++ b/mods/xdecor/locale/xdecor.de.tr
@@ -0,0 +1,217 @@
+# textdomain: xdecor
+A libre decoration mod meant to be simple and well-featured.=Eine freie simple Dekorationsmod mit netten Features.
+@1 Stair=@1treppe
+Inner @1 Stair=Innere @1treppe
+Outer @1 Stair=Äußere @1treppe
+@1 Slab=@1platte
+Weak Computer=Schwacher Computer
+Weak Computer 1=Schwacher Computer 1
+Weak Computer 2=Schwacher Computer 2
+Chess=Schach
+Chess Debug=Schachdebug
+Select a game mode=Wählen Sie einen Spielmodus
+Select a mode:=Modus wählen:
+Singleplayer=Einzelspieler
+Multiplayer=Mehrspieler
+Bot vs Bot=Bot vs. Bot
+check=Schach
+checkmate=Schachmatt
+resigned=aufgegeben
+winner=Sieger
+loser=Verlierer
+draw=Remis
+PROMOTION@nFOR BLACK!=UMWANDLUNG@nFÜR SCHWARZ!
+Promote pawn to:=Bauer umwandeln zu:
+PROMOTION@nFOR WHITE!=UMWANDLUNG@nFÜR WEISS!
+DRAW CLAIM@nBY WHITE!=REMISANSPRUCH@nVON WEISS!
+DRAW CLAIM@nBY BLACK!=REMISANSPRUCH@nVON SCHWARZ!
+The player has invoked the 50-move rule for the next move. The next move might draw the game.=Der Spieler will für den nächsten Zug die 50-Züge-Regel anwenden. Der nächste Zug könnte die Partie remis enden lassen.
+The player has invoked the threefold-repetition rule for the next move. The next move might draw the game.=Der Spieler will für den nächsten Zug die Regel für wiederholte Stellungen anwenden. Der nächste Zug könnte die Partie remis enden lassen.
+New game=Neues Spiel
+Resign=Aufgeben
+Select a color:=Farbe wählen:
+White=Weiß
+Black=Schwarz
+Invoke the 50-move rule for your next move=50-Züge-Regel für den nächsten Zug anwenden
+Invoke the 50-move rule and draw the game=50-Züge-Regel anwenden und die Partie remis enden lassen
+Invoke the threefold repetition rule and draw the game=Stellungswiederholungsregel anwenden und die Partie remis enden lassen
+Invoke the threefold repetition rule for your next move=Stellungswiederholungsregel für den nächsten Zug anwenden
+You have checkmated @1. You win!=Sie haben @1 schachmatt gesetzt. Sieg!
+You were checkmated by @1. You lose!=Sie wurden von @1 schachmatt gesetzt. Niederlage!
+The game ended up in a stalemate! It's a draw!=Das Partie endete in einem Patt. Das ist ein Remis!
+The game ended up in a dead position! It's a draw!=Das Partie endete in einer toten Stellung. Das ist ein Remis!
+No piece was captured and no pawn was moved for 75 consecutive moves of each player. It's a draw!=Für 75 Züge in Folge von je beiden Spielern wurde keine Figur geschlagen und kein Bauer gezogen. Es ist ein Remis!
+You have drawn the game by invoking the 50-move rule.=Sie haben die Partie remis enden lassen, indem Sie die 50-Züge-Regel angewandt haben.
+@1 has drawn the game by invoking the 50-move rule.=@1 hat die Partie mittels der 50-Züge Regel remis enden lassen.
+You have failed to make a game-drawing move. The game continues.=Sie haben keinen Zug gemacht, der die Partie remis enden lässt. Die Partie geht weiter.
+@1 made a draw claim using the 50-move rule but it was false. The game continues.=@1 hat ein Remis mittels der 50-Züge-Regel beansprucht aber lag falsch. Die Partie geht weiter.
+The exact same position has occured 5 times. It's a draw!=Die exakt gleiche Stellung ist 5 mal aufgetreten. Das ist ein Remis!
+You have drawn the game by invoking the threefold repetition rule.=Sie haben die Partie remis enden lassen, indem Sie die Stellungswiederholungsregel angewandt haben.
+@1 has drawn the game by invoking the threefold repetition rule.=@1 hat die Partie remis mittels der Stellungswiederholungsregel enden lassen.
+@1 made a draw claim using the threefold repetition rule but it was false. The game continues.=@1 hat ein Remis mittels der Stellungswiederholungsregel beansprucht aber lag falsch. Die Partie geht weiter.
+Chess Board=Schachbrett
+Someone else plays white pieces!=Jemand anderes spielt Weiß!
+It's not your turn!=Sie sind nicht am Zug!
+Someone else plays black pieces!=Jemand anderes spielt Schwarz!
+Black cannot move first!=Schwarz darf nicht zuerst ziehen!
+@1 s=@1 s
+@1 min @2 s=@1 min @2 s
+You can't reset the chessboard, a game has been started. Try again in @1.=Sie können das Schachbrett nicht zurücksetzen, es wurde eine Partie gestartet. Versuchen Sie es erneut in @1.
+Resigning is not possible yet.=Aufgeben ist noch nicht möglich.
+You have resigned.=Sie haben aufgegeben.
+@1 has resigned. You win!=@1 hat aufgegeben. Sie haben gewonnen!
+You can't resign, you're not playing in this game.=Sie können nicht aufgeben, Sie spielen nicht in dieser Partie.
+You can't claim a draw, it's not your turn!=Sie können kein Remis beanspruchen, Sie sind nicht am Zug!
+You're only a spectator in this game of Chess.=Sie sind nur Zuschauer bei dieser Schachpartie.
+This isn't the time for promotion.=Jetzt ist nicht die Zeit für eine Umwandlung.
+It's not your turn! This promotion is meant for the other player.=Sie sind nicht am Zug! Diese Umwandlung ist für den anderen Spieler.
+You can't dig the chessboard, a game has been started. Reset it first or dig it again in @1.=Sie können das Schachbrett nicht abbauen, es wurde eine Partie gestartet. Setzen Sie es zuerst zurück oder bauen Sie es in @1 erneut ab.
+You can't dig the chessboard, a game has been started. Try it again in @1.=Sie können das Schachbrett nicht abbauen, es wurde eine Partie gestartet. Versuchen Sie es erneut in @1.
+Play a game of Chess against another player or the computer=Für Schachspiele gegen einen anderen Spieler oder den Computer
+White Pawn=Weißer Bauer
+Black Pawn=Schwarzer Bauer
+White Rook=Weißer Turm
+Black Rook=Schwarzer Turm
+White Knight=Weißer Springer
+Black Knight=Schwarzer Springer
+White Bishop=Weißer Läufer
+Black Bishop=Schwarzer Läufer
+White Queen=Weiße Dame
+Black Queen=Schwarze Dame
+White King=Weißer König
+Black King=Schwarzer König
+Light a fire below to heat it up=Entfachen Sie unten ein Feuer, um ihn zu erhitzen
+Use a bowl to eat the soup=Schüssel benutzen, um die Suppe zu essen
+Drop foods inside to make a soup=Nahrungsmittel einwerfen, um Suppe zu machen
+Cauldron (empty)=Kessel (leer)
+Cauldron (cold water)=Kessel (kaltes Wasser)
+Cauldron (cold river water)=Kessel (kaltes Flusswasser)
+Cauldron (cold soup)=Kessel (kalte Suppe)
+Cauldron (boiling water)=Kessel (kochendes Wasser)
+Cauldron (boiling river water)=Kessel (kochendes Flusswasser)
+Cauldron (boiling soup)=Kessel (kochende Supppe)
+No room in your inventory to add a bucket of water.=Zu wenig Platz im Inventar für einen Eimer Wasser.
+No room in your inventory to add a bowl of soup.=Zu wenig Platz im Inventar für eine Schüssel voll Suppe.
+Cauldron=Kessel
+For storing water and cooking soup=Zur Lagerung von Wasser und zum Suppe kochen
+Cauldron with Water (cold)=Kessel mit Wasser (kalt)
+Cauldron with River Water (cold)=Kessel mit Flusssasser (kalt)
+Cauldron with Soup (cold)=Kessel mit Suppe (kalt)
+Cauldron with Water (boiling)=Kessel mit Wasser (kochend)
+Cauldron with River Water (boiling)=Kessel mit Flusswasser (kochend)
+Cauldron with Soup (boiling)=Kessel mit Suppe (kochend)
+Bowl=Schüssel
+Bowl of soup=Schüssel mit Suppe
+Efficiency=Effizienz
+Durability=Haltbarkeit
+Sharpness=Schärfe
+@1 (+@2%)=@1 (+@2%)
+Your weapon inflicts more damage=Ihre Waffe richtet mehr Schaden an
+Your tool lasts longer=Ihr Werkzeug hält länger
+Your tool digs faster=Ihr Werkzeug arbeitet schneller
+Enchantment Table=Zaubertisch
+Enchant your tools with mese crystals=Werkzeuge mit Mesekristallen verzaubern
+Enchanted @1@n@2=@1 (verzaubert)@n@2
+Enchanted @1=@1 (verzaubert)
+The bees are busy making honey.=Die Bienen sind beschäftigt, Honig herzustellen.
+The bees are looking for flowers.=Die Bienen halten nach Blumen Ausschau.
+The bees want to pollinate more flowers.=Die Bienen wollen mehr Blumen bestäuben.
+The bees are idle.=Die Bienen sind unbeschäftigt.
+The bees are resting.=Die Bienen ruhen sich aus.
+Artificial Hive=Künstlicher Bienenstock
+Bees live here and produce honey=Hier leben Bienen, die Honig produzieren
+Honey=Honig
+Made by bees=Hergestellt von Bienen
+@1 (owned by @2)=@1 (Eigentum von @2)
+Item Frame=Gegenstandsrahmen
+For presenting a single item=Präsentiert einen einzelnen Gegenstand
+× @1=× @1
+Mailbox=Briefkasten
+Last donators=Letzte Spender
+Send your goods to@n@1=Senden Sie Ihre Waren an@n@1
+@1's Mailbox=Briefkasten von @1
+The mailbox is full.=Der Briefkasten ist voll.
+Lets other players give you things=Hiermit kann man von anderen Spielern Dinge erhalten
+Opens doors when stepped on=Öffnet Türen beim Betreten
+Wooden Pressure Plate=Holzdruckplatte
+Stone Pressure Plate=Steindruckplatte
+Lever=Schalthebel
+Opens doors when pulled=Öffnet Türen beim Betätigen
+Bamboo Frame=Bambusgerüst
+Chainlink=Maschendraht
+Rusty Iron Bars=Rostige Eisenstäbe
+Wood Frame=Holzrahmen
+Barricade=Barrikade
+Barrel=Fass
+Wooden Cabinet=Holzschrank
+24 inventory slots=24 Inventarplätze
+Half Wooden Cabinet=Halber Holzschrank
+8 inventory slots=8 Inventarplätze
+Plain Shelf=Schlichtes Regal
+Multi Shelf=Mehrzweckregal
+Candle=Kerze
+Chair=Stuhl
+Cobweb=Spinnenwebe
+Red Curtain=Roter Vorhang
+Cushion=Sitzkissen
+Cushion Block=Sitzkissenblock
+Japanese Door=Japanische Tür
+Prison Door=Kerkertür
+Rusty Prison Door=Rostige Kerkertür
+Screen Door=Fliegengittertür
+Paper Door=Papiertür
+Woodglass Door=Tür mit Lichtausschnitt
+Ender Chest=Endertruhe
+Interdimensional inventory=Interdimensionales Inventar
+Ivy=Efeu
+Weathercock=Wetterhahn
+Lantern=Laterne
+Hanging Lantern=Hängende Laterne
+Steel Lattice Light Box=Stahlgitterlichtbox
+Wooden Cross Light Box=Holzkreuzlichtbox
+Wooden Rhombus Light Box=Holzrautenlichtbox
+Potted White Dandelion=Weißer Löwenzahn im Topf
+Potted Yellow Dandelion=Gelber Löwenzahn im Topf
+Potted Geranium=Geranie im Topf
+Potted Rose=Rose im Topf
+Potted Tulip=Tulpe im Topf
+Potted Viola=Veilchen im Topf
+Painting=Gemälde
+Garden Stone Path=Steingartenweg
+Cactus Brick=Kaktusziegel
+Coal Stone Tile=Kohlesteinkachel
+Polished Desert Stone Block=Hochglanzwüstensteinblock
+Hardened Clay=Gehärteter Ton
+Moon Brick=Mondziegel
+Runestone=Runenstein
+Polished Stone Block=Hochglanzsteinblock
+Packed Ice=Packeis
+Wooden Tile=Holzkachel
+Table=Tisch
+Tatami=Tatamimatte
+Trampoline=Trampolin
+Television=Fernseher
+Wood Framed Glass=Holzrahmenglas
+Radio=Radio
+Speaker=Lautsprecher
+Rope=Seil
+Nanoslab=nanoplatte
+Micropanel=mikropaneel
+Microslab=mikroplatte
+Thin Stair=flachtreppe
+Cube=würfel
+Panel=paneel
+Slab=platte
+Double Panel=doppelpaneel
+Half-Stair=halbtreppe
+Stair=Treppe
+Cut=Zuschnitt
+Repair=Reparatur
+Crafting=Fertigung
+Storage=Lager
+Back=Zurück
+Work Bench=Werkbank
+For cutting blocks, repairing tools with a hammer, crafting and storing items=Für Blockzuschnitt, Werkzeugreparatur mit Hammer, Fertigung und Lagerung
+@1 @2=@1@2
+Repairs tools at the work bench=Repariert Werkzeuge an der Werkbank
+Hammer=Hammer
diff --git a/mods/xdecor/locale/xdecor.fr.tr b/mods/xdecor/locale/xdecor.fr.tr
new file mode 100644
index 00000000..10292e7f
--- /dev/null
+++ b/mods/xdecor/locale/xdecor.fr.tr
@@ -0,0 +1,236 @@
+# textdomain: xdecor
+A libre decoration mod meant to be simple and well-featured.=
+@1 Stair=
+Inner @1 Stair=
+Outer @1 Stair=
+@1 Slab=
+Weak Computer=
+Weak Computer 1=
+Weak Computer 2=
+Chess=Echecs
+Chess Debug=
+Select a game mode=
+Select a mode:=Sélectionnez un mode de jeu:
+Singleplayer=Solo
+Multiplayer=Multijoueur
+Bot vs Bot=
+check=échec
+checkmate=
+resigned=
+winner=
+loser=
+draw=
+PROMOTION@nFOR BLACK!=
+Promote pawn to:=
+PROMOTION@nFOR WHITE!=
+DRAW CLAIM@nBY WHITE!=
+DRAW CLAIM@nBY BLACK!=
+The player has invoked the 50-move rule for the next move. The next move might draw the game.=
+The player has invoked the threefold-repetition rule for the next move. The next move might draw the game.=
+New game=Nouvelle partie
+Resign=
+Select a color:=
+White=
+Black=
+Invoke the 50-move rule for your next move=
+Invoke the 50-move rule and draw the game=
+Invoke the threefold repetition rule and draw the game=
+Invoke the threefold repetition rule for your next move=
+You have checkmated @1. You win!=
+You were checkmated by @1. You lose!=
+The game ended up in a stalemate! It's a draw!=
+The game ended up in a dead position! It's a draw!=
+No piece was captured and no pawn was moved for 75 consecutive moves of each player. It's a draw!=
+You have drawn the game by invoking the 50-move rule.=
+@1 has drawn the game by invoking the 50-move rule.=
+You have failed to make a game-drawing move. The game continues.=
+@1 made a draw claim using the 50-move rule but it was false. The game continues.=
+The exact same position has occured 5 times. It's a draw!=
+You have drawn the game by invoking the threefold repetition rule.=
+@1 has drawn the game by invoking the threefold repetition rule.=
+@1 made a draw claim using the threefold repetition rule but it was false. The game continues.=
+Chess Board=Echiquier
+Someone else plays white pieces!=Quelqu’un d’autre joue les pièces blanches !
+It's not your turn!=
+Someone else plays black pieces!=Quelqu’un d’autre joue les pièces noires !
+Black cannot move first!=
+@1 s=
+@1 min @2 s=
+You can't reset the chessboard, a game has been started. Try again in @1.=
+Resigning is not possible yet.=
+You have resigned.=
+@1 has resigned. You win!=
+You can't resign, you're not playing in this game.=
+You can't claim a draw, it's not your turn!=
+You're only a spectator in this game of Chess.=
+This isn't the time for promotion.=
+It's not your turn! This promotion is meant for the other player.=
+You can't dig the chessboard, a game has been started. Reset it first or dig it again in @1.=
+You can't dig the chessboard, a game has been started. Try it again in @1.=
+Play a game of Chess against another player or the computer=
+White Pawn=Pion blanc
+Black Pawn=Pion noir
+White Rook=Tour blanche
+Black Rook=Tour noire
+White Knight=Cavalier blanc
+Black Knight=Cavalier noir
+White Bishop=Fou blanc
+Black Bishop=Fou noir
+White Queen=Reine blanche
+Black Queen=Reine noire
+White King=Roi blanc
+Black King=Roi noir
+Light a fire below to heat it up=
+Use a bowl to eat the soup=Utilisez un bol pour boire la soupe
+Drop foods inside to make a soup=Placez des ingrédients à l’intérieur pour faire une soupe
+Cauldron (empty)=Chaudron (vide)
+Cauldron (cold water)=
+Cauldron (cold river water)=
+Cauldron (cold soup)=
+Cauldron (boiling water)=
+Cauldron (boiling river water)=
+Cauldron (boiling soup)=
+No room in your inventory to add a bucket of water.=Pas de place dans votre inventaire pour ajouter un seau d’eau.
+No room in your inventory to add a bowl of soup.=Pas de place dans votre inventaire pour ajouter un bol de soupe.
+Cauldron=Chaudron
+For storing water and cooking soup=
+Cauldron with Water (cold)=
+Cauldron with River Water (cold)=
+Cauldron with Soup (cold)=
+Cauldron with Water (boiling)=
+Cauldron with River Water (boiling)=
+Cauldron with Soup (boiling)=
+Bowl=Bol
+Bowl of soup=Bol de soupe
+Efficiency=Efficacité
+Durability=Durabilité
+Sharpness=Tranchant
+@1 (+@2%)=
+Your weapon inflicts more damage=Votre arme inflige plus de dégâts
+Your tool lasts longer=Votre outil dure plus longtemps
+Your tool digs faster=Votre outil creuse plus vite
+Enchantment Table=Table d’enchantements
+Enchant your tools with mese crystals=
+Enchanted @1@n@2=
+Enchanted @1=
+The bees are busy making honey.=
+The bees are looking for flowers.=
+The bees want to pollinate more flowers.=
+The bees are idle.=
+The bees are resting.=
+Artificial Hive=Ruche artificielle
+Bees live here and produce honey=
+Honey=Miel
+Made by bees=
+@1 (owned by @2)=@1 (propriété de @2)
+Item Frame=Cadre
+For presenting a single item=
+× @1=
+Mailbox=Boite aux lettres
+Last donators=Derniers donateurs
+Send your goods to@n@1=Envoyer vos biens à@n@1
+@1's Mailbox=Boite aux lettres de @1
+The mailbox is full.=La boite aux lettres est pleine.
+Lets other players give you things=
+Opens doors when stepped on=
+Wooden Pressure Plate=Plaque de pression en bois
+Stone Pressure Plate=Plaque de pression en pierre
+Lever=Levier
+Opens doors when pulled=
+Bamboo Frame=Cadre en bambou
+Chainlink=Maillon de chaîne
+Rusty Iron Bars=Barreaux en fer rouillé
+Wood Frame=Cadre en bois
+Barricade=Barricade
+Barrel=Tonneau
+Wooden Cabinet=Meuble en bois
+24 inventory slots=
+Half Wooden Cabinet=Demi meuble en bois
+8 inventory slots=
+Plain Shelf=
+Multi Shelf=Étagères multiple
+Candle=Bougie
+Chair=Chaise
+Cobweb=Toile d’araignée
+Red Curtain=Rideaux rouge
+Cushion=Coussin
+Cushion Block=Bloc de coussin
+Japanese Door=Porte japonaise
+Prison Door=Porte de prison
+Rusty Prison Door=Barreaux de prison rouillés
+Screen Door=Porte avec moustiquaire
+Paper Door=
+Woodglass Door=Porte vitrée
+Ender Chest=Coffre de l’End
+Interdimensional inventory=
+Ivy=Lierre
+Weathercock=
+Lantern=Lanterne
+Hanging Lantern=
+Steel Lattice Light Box=
+Wooden Cross Light Box=
+Wooden Rhombus Light Box=
+Potted White Dandelion=Pissenlit blanc en pot
+Potted Yellow Dandelion=Pissenlit jaune en pot
+Potted Geranium=Géranium en pot
+Potted Rose=Rose en pot
+Potted Tulip=Tulipe en pot
+Potted Viola=Violette en pot
+Painting=Tableau
+Garden Stone Path=Chemin de pierres de jardin
+Cactus Brick=Brique en cactus
+Coal Stone Tile=Carreau en charbon et pierre
+Polished Desert Stone Block=
+Hardened Clay=Argile durcie
+Moon Brick=Brique lunaire
+Runestone=Pierre runique
+Polished Stone Block=
+Packed Ice=Glace compactée
+Wooden Tile=Carreau en bois
+Table=Table
+Tatami=Tatami
+Trampoline=Trampoline
+Television=Télévision
+Wood Framed Glass=Verre encadré par du bois
+Radio=
+Speaker=
+Rope=Corde
+Nanoslab=
+Micropanel=
+Microslab=
+Thin Stair=
+Cube=
+Panel=
+Slab=
+Double Panel=
+Half-Stair=
+Stair=
+Cut=Couper
+Repair=Réparer
+Crafting=Fabrication
+Storage=Stockage
+Back=Retour
+Work Bench=Atelier
+For cutting blocks, repairing tools with a hammer, crafting and storing items=
+@1 @2=
+Repairs tools at the work bench=
+Hammer=Marteau
+
+
+##### not used anymore #####
+
+Enchanted @1 @2@n@3=@2 en @1 enchantée@n@3
+Enchanted @1 @2=@2 en @1 enchantée
+You can't reset the chessboard, a game has been started. If you aren't a current player, try again in @1=Vous ne pouvez pas mettre à zéro l’échiquier, une partie a été commencée. Si ce n’est pas votre tour de jouer, réessayez dans @1
+You can't dig the chessboard, a game has been started. Reset it first if you're a current player, or dig it again in @1=Vous ne pouvez pas récupérer l’échiquier, une partie à été commencée. Remettez le à zéro si vous c’est votre tour de jouer, ou réessayez dans @1
+Cauldron (idle)=Chaudron (inactif)
+Cauldron (active) - Drop foods inside to make a soup=Chaudron (actif) - Placez des ingrédients à l’intérieur pour faire une soupe
+Cauldron (active) - Use a bowl to eat the soup=Chaudron (actif) - Utilisez un bol pour boire la soupe
+Cauldron (active)=Chaudron (actif)
+Bees are busy making honey…=Les abeilles sont occupées à fabriquer du miel…
+Desert Stone Tile=Carreau en pierre du désert
+Empty Shelf=Étagère vide
+Iron Light Box=Boite lumineuse en fer
+Slide Door=Porte coulissante
+Stone Tile=Carreau en pierre
+Wooden Light Box=Boite lumineuse en bois
diff --git a/mods/xdecor/locale/xdecor.it.tr b/mods/xdecor/locale/xdecor.it.tr
new file mode 100644
index 00000000..70b5c6bb
--- /dev/null
+++ b/mods/xdecor/locale/xdecor.it.tr
@@ -0,0 +1,236 @@
+# textdomain: xdecor
+# author: Zughy (chess)
+# Nota per chi traduce: ho usato italiano inclusivo (ə per il singolare) e usato il participio presente per giocatore e spettatore (=giocante e osservante) come esperimento di inclusività. Per gli apostrofi ho usato ` (un - un' -> un`)
+A libre decoration mod meant to be simple and well-featured.=
+@1 Stair=
+Inner @1 Stair=
+Outer @1 Stair=
+@1 Slab=
+Weak Computer=Computer facile
+Weak Computer 1=Computer facile 1
+Weak Computer 2=Computer facile 2
+Chess=Scacchi
+Chess Debug=Debug scacchi
+Select a game mode=Seleziona modalità di gioco
+Select a mode:=Seleziona una modalità:
+Singleplayer=Giocante singolə
+Multiplayer=Multigiocante
+Bot vs Bot=Bot contro Bot
+check=scacco
+checkmate=scacco matto
+resigned=arresə
+winner=vince
+loser=perde
+draw=patta
+PROMOTION@nFOR BLACK!=PROMOZIONE@nPER IL NERO!
+Promote pawn to:=Promuovi pedone a:
+PROMOTION@nFOR WHITE!=PROMOZIONE@nPER IL BIANCO!
+DRAW CLAIM@nBY WHITE!=PATTA DICHIARATA@nDAL BIANCO!
+DRAW CLAIM@nBY BLACK!=PATTA DICHIARATA@nDAL NERO!
+The player has invoked the 50-move rule for the next move. The next move might draw the game.=Lə giocante ha invocato la regola delle 50 mosse per la prossima mossa. Quest'ultima potrebbe portare a una patta.
+The player has invoked the threefold-repetition rule for the next move. The next move might draw the game.=Lə giocante ha invocato la regola della tripla ripetizione per la prossima mossa. Quest'ultima potrebbe portare a una patta
+New game=Nuova partita
+Resign=Arrenditi
+Select a color:=Seleziona un colore:
+White=Bianco
+Black=Nero
+Invoke the 50-move rule for your next move=Invoca la regola delle 50 mosse per la tua prossima mossa
+Invoke the 50-move rule and draw the game=Invoca la regola delle 50 mosse e chiudi in patta
+Invoke the threefold repetition rule and draw the game=Invoca la regola della tripla ripetizione e chiudi in patta
+Invoke the threefold repetition rule for your next move=Invoca la regola della tripla ripetizione per la tua prossima mossa
+You have checkmated @1. You win!=Scacco matto a @1. Hai vinto!
+You were checkmated by @1. You lose!=Scacco matto da @1. Hai perso!
+The game ended up in a stalemate! It's a draw!=È uno stallo! Patta!
+The game ended up in a dead position! It's a draw!=È una posizione morta! Patta!
+No piece was captured and no pawn was moved for 75 consecutive moves of each player. It's a draw!=Nessun giocante ha catturato pezzi né mosso pedoni per 75 mosse consecutive. Patta!
+You have drawn the game by invoking the 50-move rule.=Hai chiuso in patta con l'invocazione della regola delle 50 mosse.
+@1 has drawn the game by invoking the 50-move rule.=@1 ha chiuso in patta con l'invocazione della regola delle 50 mosse.
+You have failed to make a game-drawing move. The game continues.=Non sei riuscitə a fare una mossa che portasse a una patta. Il gioco continua.
+@1 made a draw claim using the 50-move rule but it was false. The game continues.=@1 ha tentato la patta con la regola delle 50 mosse ma ha fallito. La partita continua.
+The exact same position has occured 5 times. It's a draw!=La stessa identica mossa si è ripetuta per 5 volte. Patta!
+You have drawn the game by invoking the threefold repetition rule.=Hai chiuso in patta con l'invocazione della regola della tripla ripetizione.
+@1 has drawn the game by invoking the threefold repetition rule.=@1 ha chiuso in patta con l'invocazione della regola della tripla ripetizione.
+@1 made a draw claim using the threefold repetition rule but it was false. The game continues.=@1 ha tentato la patta con la regola della tripla ripetizione ma ha fallito. La partita continua.
+Chess Board=Scacchiera
+Someone else plays white pieces!=Qualcun`altrə sta già giocando con il bianco!
+It's not your turn!=Non è il tuo turno!
+Someone else plays black pieces!=Qualcun`altrə sta già giocando con il nero!
+Black cannot move first!=Il nero non può muovere per primo!
+@1 s=@1 s
+@1 min @2 s=@1 min @2 s
+You can't reset the chessboard, a game has been started. Try again in @1.=Non puoi ripristinare la scacchiera, c'è una partita in corso. Riprova tra @1.
+Resigning is not possible yet.=Non ci si può ancora arrendere.
+You have resigned.=Ti sei arresə.
+@1 has resigned. You win!=@1 si è arresə. Hai vinto!
+You can't resign, you're not playing in this game.=Non ti puoi arrendere, non stai giocando in questa partita.
+You can't claim a draw, it's not your turn!=Non puoi chiamare patta, non è il tuo turno!
+You're only a spectator in this game of Chess.=Sei solo un`osservante in questa partita di scacchi.
+This isn't the time for promotion.=Non è il momento per una promozione.
+It's not your turn! This promotion is meant for the other player.=Non è il tuo turno! Questa promozione spetta all'altrə giocante.
+You can't dig the chessboard, a game has been started. Reset it first or dig it again in @1.=Non puoi rimuovere la scacchiera, c'è una partita in corso. Ripristinala prima, o riprova a rimuoverla in @1.
+You can't dig the chessboard, a game has been started. Try it again in @1.=Non puoi rimuovere la scacchiera, c'è una partita in corso. Riprova tra @1.
+Play a game of Chess against another player or the computer=Gioca una partita a scacchi contro un`altrə giocante o contro il computer
+White Pawn=Pedone bianco
+Black Pawn=Pedone nero
+White Rook=Torre bianca
+Black Rook=Torre nera
+White Knight=Cavallo bianco
+Black Knight=Cavallo nero
+White Bishop=Alfiere bianco
+Black Bishop=Alfiere nero
+White Queen=Regina bianca
+Black Queen=Regina nera
+White King=Re bianco
+Black King=Re nero
+Light a fire below to heat it up=
+Use a bowl to eat the soup=Utilizzare una ciotola per mangiare la zuppa
+Drop foods inside to make a soup=Mettere gli ingredienti all'interno per fare una zuppa
+Cauldron (empty)=Calderone (vuoto)
+Cauldron (cold water)=
+Cauldron (cold river water)=
+Cauldron (cold soup)=
+Cauldron (boiling water)=
+Cauldron (boiling river water)=
+Cauldron (boiling soup)=
+No room in your inventory to add a bucket of water.=Non c'è spazio nell'inventario per aggiungere un secchio di acqua.
+No room in your inventory to add a bowl of soup.=Non c'è spazio nell'inventario per aggiungere una ciotola di zuppa.
+Cauldron=Calderone
+For storing water and cooking soup=
+Cauldron with Water (cold)=
+Cauldron with River Water (cold)=
+Cauldron with Soup (cold)=
+Cauldron with Water (boiling)=
+Cauldron with River Water (boiling)=
+Cauldron with Soup (boiling)=
+Bowl=Ciotola
+Bowl of soup=Ciotola di zuppa
+Efficiency=Efficacia
+Durability=Durabilità
+Sharpness=Affilatezza
+@1 (+@2%)=
+Your weapon inflicts more damage=La tua arma infligge più danno
+Your tool lasts longer=Il tuo utensile dura di più
+Your tool digs faster=Il tuo utensile scava più rapidamente
+Enchantment Table=Tavolo per migliorie
+Enchant your tools with mese crystals=
+Enchanted @1@n@2=
+Enchanted @1=
+The bees are busy making honey.=
+The bees are looking for flowers.=
+The bees want to pollinate more flowers.=
+The bees are idle.=
+The bees are resting.=
+Artificial Hive=Favo artificiale
+Bees live here and produce honey=
+Honey=Miele
+Made by bees=
+@1 (owned by @2)=@1 (proprietà di @2)
+Item Frame=Teca
+For presenting a single item=
+× @1=
+Mailbox=Cassetta delle lettere
+Last donators=Ultimi donatori
+Send your goods to@n@1=Invia i tuoi item a@n@1
+@1's Mailbox=Cassetta delle lettere di @1
+The mailbox is full.=La cassetta delle lettere è piena
+Lets other players give you things=
+Opens doors when stepped on=
+Wooden Pressure Plate=Placca di pressione di legno
+Stone Pressure Plate=Placca di pressione di pietra
+Lever=Leva
+Opens doors when pulled=
+Bamboo Frame=Cornice di bambù
+Chainlink=Cotta di maglia
+Rusty Iron Bars=Sbarre di prigione arrugginite
+Wood Frame=Cornice in legno
+Barricade=Barricata
+Barrel=Barile
+Wooden Cabinet=Stipo di legno
+24 inventory slots=
+Half Wooden Cabinet=Stipo di legno a metà
+8 inventory slots=
+Plain Shelf=
+Multi Shelf=Mensole
+Candle=Candela
+Chair=Sedia
+Cobweb=Ragnatela
+Red Curtain=Tenda rossa
+Cushion=Cuscino
+Cushion Block=Blocco di cuscini
+Japanese Door=Porta giapponese
+Prison Door=Porta di prigione
+Rusty Prison Door=Porta di prigione arrugginita
+Screen Door=Porta a schermo
+Paper Door=
+Woodglass Door=Porta di vetro
+Ender Chest=Baule ender
+Interdimensional inventory=
+Ivy=Edera
+Weathercock=
+Lantern=Lanterna
+Hanging Lantern=
+Steel Lattice Light Box=
+Wooden Cross Light Box=
+Wooden Rhombus Light Box=
+Potted White Dandelion=Soffione bianco in vaso
+Potted Yellow Dandelion=Soffione giallo in vaso
+Potted Geranium=Geranio in vaso
+Potted Rose=Rosa in vaso
+Potted Tulip=Tulipano in vaso
+Potted Viola=Violetta in vaso
+Painting=Dipinto
+Garden Stone Path=Sentiero da giardino in pietra
+Cactus Brick=Mattone di cactus
+Coal Stone Tile=Mattonella di pietra di carbone
+Polished Desert Stone Block=
+Hardened Clay=Argilla indurita
+Moon Brick=Mattone lunare
+Runestone=Pietra runica
+Polished Stone Block=
+Packed Ice=Ghiaccio compatto
+Wooden Tile=Mattonella di legno
+Table=Tavolo
+Tatami=Tatami
+Trampoline=Trampolino
+Television=Televisione
+Wood Framed Glass=Cornice in legno con vetro
+Radio=
+Speaker=
+Rope=Corda
+Nanoslab=
+Micropanel=
+Microslab=
+Thin Stair=
+Cube=
+Panel=
+Slab=
+Double Panel=
+Half-Stair=
+Stair=
+Cut=Tagliare
+Repair=Riparare
+Crafting=Fabbricare
+Storage=Conservare
+Back=Indietro
+Work Bench=Banco da lavoro
+For cutting blocks, repairing tools with a hammer, crafting and storing items=
+@1 @2=
+Repairs tools at the work bench=
+Hammer=Martello
+
+
+##### not used anymore #####
+
+Enchanted @1 @2@n@3=@2 su @1 incantesimo@n@3
+Enchanted @1 @2=@2 su @1 incantesimo
+Cauldron (idle)=Calderone (inattivo)
+Cauldron (active) - Drop foods inside to make a soup=Calderone (attivo) - Mettere gli ingredienti all'interno per fare una zuppa.
+Cauldron (active) - Use a bowl to eat the soup=Calderone (actif) - Utilizzare una ciotola per mangiare la zuppa
+Cauldron (active)=Calderone (attivo)
+Bees are busy making honey…=Le api sono occupate a fare il miele…
+Desert Stone Tile=Mattonella di pietra del deserto
+Empty Shelf=Mensola vuota
+Iron Light Box=Scatola luminosa di ferro
+Slide Door=Porta scorrevole
+Stone Tile=Mattonella di pietra
+Wooden Light Box=Mattonella luminosa di legno
diff --git a/mods/xdecor/mod.conf b/mods/xdecor/mod.conf
new file mode 100644
index 00000000..45160510
--- /dev/null
+++ b/mods/xdecor/mod.conf
@@ -0,0 +1,8 @@
+name = xdecor
+title = X-Decor-libre
+description = A libre decoration mod meant to be simple and well-featured.
+depends = default, bucket, doors, farming, stairs, xpanes
+optional_depends = player_api, fire, moreblocks, mesecons, unified_inventory, tt, toolranks
+min_minetest_version = 5.9
+release = 29354
+author = Wuzzy
diff --git a/mods/xdecor/screenshot.png b/mods/xdecor/screenshot.png
new file mode 100644
index 00000000..2ccdaad2
Binary files /dev/null and b/mods/xdecor/screenshot.png differ
diff --git a/mods/xdecor/settingtypes.txt b/mods/xdecor/settingtypes.txt
new file mode 100644
index 00000000..0c64b596
--- /dev/null
+++ b/mods/xdecor/settingtypes.txt
@@ -0,0 +1,31 @@
+# For enabling components of X-Decor-libre.
+
+
+
+# This adds a chessboard on which you can play Chess against other players or the computer.
+enable_xdecor_chess (Enable Chess) bool true
+
+# This enables the cauldron which you can use to store water and cook soup.
+enable_xdecor_cooking (Enable Cooking) bool true
+
+# This adds the enchanting table which you can use to upgrade your tools with mese crystals.
+enable_xdecor_enchanting (Enable Enchanting) bool true
+
+# This adds the artificial beehive which produces honey.
+enable_xdecor_hive (Enable Hive) bool true
+
+# This adds itemframes which are used to show off items on a wall.
+enable_xdecor_itemframe (Enable Itemframe) bool true
+
+# This enables the mailbox which allows players to receive gifts from other players.
+enable_xdecor_mailbox (Enable Mailbox) bool true
+
+# This adds mechanisms like the lever and pressure plate which are used to trigger doors.
+enable_xdecor_mechanisms (Enable Mechanisms) bool true
+
+# This adds ropes which can be used for climbing.
+enable_xdecor_rope (Enable Rope) bool true
+
+# This adds the workbench which allows you to craft, store items, cut blocks and repair tools.
+# Due to block cutting, many new block shapes become available.
+enable_xdecor_workbench (Enable Workbench) bool true
diff --git a/mods/xdecor/sounds/xdecor_boiling_water.ogg b/mods/xdecor/sounds/xdecor_boiling_water.ogg
new file mode 100644
index 00000000..cc1e7a5e
Binary files /dev/null and b/mods/xdecor/sounds/xdecor_boiling_water.ogg differ
diff --git a/mods/xdecor/sounds/xdecor_bouncy.ogg b/mods/xdecor/sounds/xdecor_bouncy.ogg
new file mode 100644
index 00000000..779e825d
Binary files /dev/null and b/mods/xdecor/sounds/xdecor_bouncy.ogg differ
diff --git a/mods/xdecor/sounds/xdecor_enchanting.ogg b/mods/xdecor/sounds/xdecor_enchanting.ogg
new file mode 100644
index 00000000..81f5e51b
Binary files /dev/null and b/mods/xdecor/sounds/xdecor_enchanting.ogg differ
diff --git a/mods/xdecor/src/chess.lua b/mods/xdecor/src/chess.lua
new file mode 100644
index 00000000..6af3053e
--- /dev/null
+++ b/mods/xdecor/src/chess.lua
@@ -0,0 +1,3385 @@
+-- Init stuff
+if minetest.get_modpath("realchess") ~= nil then
+ -- If the 'realchess' mod was found, don't use any of this mod's Chess code
+ minetest.log("action", "[xdecor] 'realchess' mod detected. Disabling X-Decor-libre's Chess module in favor of realchess")
+ return
+end
+
+-- For making some functions available for the chessbot.
+-- It is NOT recommended to use the public chess function outside of this mod!
+xdecor.chess = {}
+
+local realchess = xdecor.chess -- just an alias
+
+-- Load chess bot code
+local chessbot = dofile(minetest.get_modpath(minetest.get_current_modname()).."/src/chessbot.lua")
+
+screwdriver = screwdriver or {}
+
+-- Translation init
+local S = minetest.get_translator("xdecor")
+local NS = function(s) return s end
+local FS = function(...) return minetest.formspec_escape(S(...)) end
+
+-- Chess games are disabled because they are currently too broken.
+-- Set this to true to enable this again and try your luck.
+local ENABLE_CHESS_GAMES = true
+
+-- If true, will show some hidden state for debugging purposes
+-- and enables a "Bot vs Bot" gamemode for testing the bot
+local CHESS_DEBUG = false
+
+-- Number of consecutive halfmoves in which no pawn was moved
+-- and no piece was captured after which a player can claim a draw.
+local DRAWCLAIM_LONGGAME_PLAYER = 100 -- 50-move rule
+-- Number of consecutive halfmoves in which no pawn was moved
+-- and no piece was captured after which the game draw automatically.
+local DRAWCLAIM_LONGGAME_FORCE = 150 -- 75-move rule
+
+-- Bot names
+local BOT_NAME = NS("Weak Computer")
+-- Bot names in Bot vs Bot mode
+local BOT_NAME_1 = NS("Weak Computer 1")
+local BOT_NAME_2 = NS("Weak Computer 2")
+
+-- Timeout in seconds to allow resetting the game or digging the chessboard.
+-- If no move was made for this time, everyone can reset the game
+-- and remove the chessboard.
+local TIMEOUT = 300
+
+local ALPHA_OPAQUE = minetest.features.use_texture_alpha_string_modes and "opaque" or false
+
+-- Returns the player name for the given player color.
+-- In case of a bot player, will return a translated
+-- bot name.
+local function get_display_player_name(meta, playerColor)
+ local botColor = meta:get_string("botColor")
+ local displayName
+ if playerColor == botColor and (botColor == "white" or botColor == "black") then
+ return "*"..S(BOT_NAME).."*"
+ elseif botColor == "both" then
+ if playerColor == "white" then
+ return "*"..S(BOT_NAME_1).."*"
+ else
+ return "*"..S(BOT_NAME_2).."*"
+ end
+ elseif playerColor == "white" then
+ return meta:get_string("playerWhite")
+ elseif playerColor == "black" then
+ return meta:get_string("playerBlack")
+ else
+ return ""
+ end
+end
+
+local function index_to_xy(idx)
+ if not idx then
+ return nil
+ end
+
+ idx = idx - 1
+
+ local x = idx % 8
+ local y = math.floor((idx - x) / 8)
+
+ return x, y
+end
+
+local function xy_to_index(x, y)
+ return x + y * 8 + 1
+end
+
+local function get_square(a, b)
+ return (a * 8) - (8 - b)
+end
+
+-- Given a board index (1..64), returns the color of the square at
+-- this position: "light" or "dark".
+-- Undefined behavior if given an invalid board index
+local function get_square_index_color(idx)
+ local x, y = index_to_xy(idx)
+ if not x then
+ return nil
+ end
+ if x % 2 == 0 then
+ if y % 2 == 0 then
+ return "light"
+ else
+ return "dark"
+ end
+ else
+ if y % 2 == 0 then
+ return "dark"
+ else
+ return "light"
+ end
+ end
+end
+
+local chat_prefix = minetest.colorize("#FFFF00", "["..S("Chess").."] ")
+local chat_prefix_debug = minetest.colorize("#FFFF00", "["..S("Chess Debug").."] ")
+
+-- Send a chat message to a player with a prefix.
+-- If you pass playerColor and botColor, this function will also
+-- check if the player is a bot, in which case the message
+-- is not sent.
+-- If you only pass playerName and message, the message will always
+-- be sent (if the player exists).
+--
+-- Parameters:
+-- * playerName: Name of player to send message to (can be a bot name)
+-- * message: The message text
+-- * playerColor: "white", "black" or nil (if color is irrelevant)
+-- * botColor: optional color of the bot(s):
+-- * "white", "black": white or black
+-- * "both": Both players are bots
+-- * "": No player is a bot (default)
+-- * isDebug: if true, message will only be shown in Chess Debug Mode (default: false)
+local function send_message(playerName, message, playerColor, botColor, isDebug)
+ local prefix
+ if isDebug then
+ if not CHESS_DEBUG then
+ return
+ end
+ prefix = chat_prefix_debug
+ else
+ prefix = chat_prefix
+ end
+ minetest.chat_send_player(playerName, prefix .. message)
+end
+-- Send a message to both players of the chess game (except bots, if botColor is provided)
+-- * playerName1: White player name
+-- * playerName2: Black player name
+-- * message: Message text
+-- * botColor: See `send_message`
+-- * isDebug: See `send_message`
+local function send_message_2(playerName1, playerName2, message, botColor, isDebug)
+ send_message(playerName1, message, "white", botColor, isDebug)
+ if playerName2 ~= playerName1 then
+ send_message(playerName2, message, "black", botColor, isDebug)
+ end
+end
+
+local notation_letters = {'a','b','c','d','e','f','g','h'}
+function realchess.index_to_notation(idx)
+ local x, y = index_to_xy(idx)
+ if not x or not y then
+ return "??"
+ end
+ local xstr = notation_letters[x+1] or "?"
+ local ystr = tostring(9 - (y+1)) or "?"
+ return xstr .. ystr
+end
+
+function realchess.board_to_table(inv)
+ local t = {}
+ for i = 1, 64 do
+ t[#t + 1] = inv:get_stack("board", i):get_name()
+ end
+
+ return t
+end
+
+local piece_values = {
+ pawn = 10,
+ knight = 30,
+ bishop = 30,
+ rook = 50,
+ queen = 90,
+ king = 900
+}
+
+local rowDirs = {-1, -1, -1, 0, 0, 1, 1, 1}
+local colDirs = {-1, 0, 1, -1, 1, -1, 0, 1}
+
+local rowDirsKnight = { 2, 1, 2, 1, -2, -1, -2, -1}
+local colDirsKnight = {-1, -2, 1, 2, 1, 2, -1, -2}
+
+local bishopThreats = {true, false, true, false, false, true, false, true}
+local rookThreats = {false, true, false, true, true, false, true, false}
+local queenThreats = {true, true, true, true, true, true, true, true}
+local kingThreats = {true, true, true, true, true, true, true, true}
+
+function realchess.attacked(color, idx, board)
+ local threatDetected = false
+ local kill = color == "white"
+ local pawnThreats = {kill, false, kill, false, false, not kill, false, not kill}
+
+ for dir = 1, 8 do
+ if not threatDetected then
+ local col, row = index_to_xy(idx)
+ col, row = col + 1, row + 1
+
+ for step = 1, 8 do
+ row = row + rowDirs[dir]
+ col = col + colDirs[dir]
+
+ if row >= 1 and row <= 8 and col >= 1 and col <= 8 then
+ local square = get_square(row, col)
+ local square_name = board[square]
+ local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
+
+ if piece then
+ if pieceColor ~= color then
+ if piece == "bishop" and bishopThreats[dir] then
+ threatDetected = true
+ elseif piece == "rook" and rookThreats[dir] then
+ threatDetected = true
+ elseif piece == "queen" and queenThreats[dir] then
+ threatDetected = true
+ else
+ if step == 1 then
+ if piece == "pawn" and pawnThreats[dir] then
+ threatDetected = true
+ end
+ if piece == "king" and kingThreats[dir] then
+ threatDetected = true
+ end
+ end
+ end
+ end
+ break
+ end
+ end
+ end
+
+ local colK, rowK = index_to_xy(idx)
+ colK, rowK = colK + 1, rowK + 1
+ rowK = rowK + rowDirsKnight[dir]
+ colK = colK + colDirsKnight[dir]
+
+ if rowK >= 1 and rowK <= 8 and colK >= 1 and colK <= 8 then
+ local square = get_square(rowK, colK)
+ local square_name = board[square]
+ local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
+
+ if piece and pieceColor ~= color and piece == "knight" then
+ threatDetected = true
+ end
+ end
+ end
+ end
+
+ return threatDetected
+end
+
+local function get_current_halfmove(meta)
+ local moves_raw = meta:get_string("moves_raw")
+ local mrsplit = string.split(moves_raw, ";")
+ return #mrsplit
+end
+local function get_current_fullmove(meta)
+ local moves_raw = meta:get_string("moves_raw")
+ local mrsplit = string.split(moves_raw, ";")
+ return math.floor(#mrsplit / 2)
+end
+
+-- Returns a FEN-style string to represent castling rights
+-- Will return a sequence of K, Q, k and q, with each letter
+-- representing castling rights:
+-- * K: white kingside
+-- * Q: white queenside
+-- * k: black kingside
+-- * q: black queenside
+-- If all castling rights are gone, will return "-" instead.
+-- The 4 arguments are booleans for each possible castling,
+-- true means the castling is possible.
+local function castling_to_string(white_kingside, white_queenside, black_kingside, black_queenside)
+ local s_castling = ""
+ if white_kingside then
+ s_castling = s_castling .. "K"
+ end
+ if white_queenside then
+ s_castling = s_castling .. "Q"
+ end
+ if black_kingside then
+ s_castling = s_castling .. "k"
+ end
+ if black_queenside then
+ s_castling = s_castling .. "q"
+ end
+ if s_castling == "" then
+ s_castling = "-"
+ end
+ return s_castling
+end
+
+-- Returns a FEN-style string to represent the state of a theoretically
+-- possible en passant capture on the board (even if no pawn can actually
+-- capture). If an en passant capture is possible, returns the square
+-- coordinates in algebraic notation of the square the vulnerable pawn
+-- has just crossed. If no en passant capture is possible, returns "-".
+-- double_step is the board index of the square the vulnerable
+-- pawn has just double-stepped to or 0 if there is no such pawn.
+local function en_passant_to_string(double_step)
+ local s_en_passant = "-"
+ if double_step ~= 0 and double_step ~= nil then
+ -- write the square crossed by the pawn who made
+ -- the double step
+ local dsx, dsy = index_to_xy(double_step)
+ if dsy == 3 then
+ dsy = dsy - 1
+ else
+ dsy = dsy + 1
+ end
+ s_en_passant = realchess.index_to_notation(xy_to_index(dsx, dsy))
+ end
+ return s_en_passant
+end
+
+local function can_castle(board, from_idx, to_idx, castlingRights)
+ local from_x, from_y = index_to_xy(from_idx)
+ local to_x, to_y = index_to_xy(to_idx)
+ local kingPiece = board[from_idx]
+ local kingColor
+ if kingPiece:find("black") then
+ kingColor = "black"
+ else
+ kingColor = "white"
+ end
+ local possible_castles = {
+ -- white queenside
+ { y = 7, to_x = 2, rook_idx = 57, rook_goal = 60, acheck_dir = -1, color = "white", rightName = "castlingWhiteL", rook_id = 1 },
+ -- white kingside
+ { y = 7, to_x = 6, rook_idx = 64, rook_goal = 62, acheck_dir = 1, color = "white", rightName = "castlingWhiteR", rook_id = 2 },
+ -- black queenside
+ { y = 0, to_x = 2, rook_idx = 1, rook_goal = 4, acheck_dir = -1, color = "black", rightName = "castlingBlackL", rook_id = 1 },
+ -- black kingside
+ { y = 0, to_x = 6, rook_idx = 8, rook_goal = 6, acheck_dir = 1, color = "black", rightName = "castlingBlackR", rook_id = 2 },
+ }
+
+ for p=1, #possible_castles do
+ local pc = possible_castles[p]
+ if pc.color == kingColor and pc.to_x == to_x and to_y == pc.y and from_y == pc.y then
+ local castlingRightVal = castlingRights[pc.rightName]
+ local rookPiece = board[pc.rook_idx]
+ if castlingRightVal == 1 and rookPiece == "realchess:rook_"..kingColor.."_"..pc.rook_id then
+ -- Check if all squares between king and rook are empty
+ local empty_start, empty_end
+ if pc.acheck_dir == -1 then
+ -- queenside
+ empty_start = pc.rook_idx + 1
+ empty_end = from_idx - 1
+ else
+ -- kingside
+ empty_start = from_idx + 1
+ empty_end = pc.rook_idx - 1
+ end
+ for i = empty_start, empty_end do
+ if board[i] ~= "" then
+ return false
+ end
+ end
+ -- Check if square of king as well the squares that king must cross and reach
+ -- are NOT attacked
+ for i = from_idx, from_idx + 2 * pc.acheck_dir, pc.acheck_dir do
+ if realchess.attacked(kingColor, i, board) then
+ return false
+ end
+ end
+ return true, pc.rook_idx, pc.rook_goal, "realchess:rook_"..kingColor.."_"..pc.rook_id
+ end
+ end
+ end
+ return false
+
+end
+
+-- Checks if a square to check if there is a piece that can be captured en passant. Returns true if this
+-- is the case, false otherwise.
+-- Parameters:
+-- * board: chessboard table
+-- * victim_color: color of the opponent to capture a piece from. "white" or "black". (so in White's turn, pass "black" here)
+-- * victim_index: board index of the square where you expect the victim to be
+-- * prevDoublePawnStepTo: if a pawn did a double-step in the previous halfmove, this is the board index of the destination.
+-- if no pawn made a double-step in the previous halfmove, this is nil or 0.
+local function can_capture_en_passant(board, victim_color, victim_index, prevDoublePawnStepTo)
+ local victimPiece = board[victim_index]
+ local double_step_index = prevDoublePawnStepTo or 0
+ if double_step_index ~= 0 and double_step_index == victim_index and victimPiece:find(victim_color) and victimPiece:sub(11,14) == "pawn" then
+ return true
+ end
+ return false
+end
+
+-- Returns all theoretically possible moves from a given
+-- square, according to the piece it occupies. Ignores restrictions like check, etc.
+-- If the square is empty, no moves are returned.
+-- Parameters:
+-- * board: chessboard table
+-- * from_idx:
+-- returns: table with the keys used as destination indices
+-- Any key with a numeric value is a possible destination.
+-- The numeric value is a move rating for the bot and is 0 by default.
+-- Example: { [4] = 0, [9] = 0 } -- can move to squares 4 and 9
+local function get_theoretical_moves_from(board, from_idx, prevDoublePawnStepTo, castlingRights)
+ local piece, color = board[from_idx]:match(":(%w+)_(%w+)")
+ if not piece then
+ return {}
+ end
+ local moves = {}
+ local from_x, from_y = index_to_xy(from_idx)
+
+ for i = 1, 64 do
+ local stack_name = board[i]
+ if stack_name:find((color == "black" and "white" or "black")) or
+ stack_name == "" then
+ moves[i] = 0
+ end
+ end
+
+ for to_idx in pairs(moves) do
+ local pieceTo = board[to_idx]
+ local to_x, to_y = index_to_xy(to_idx)
+
+ -- PAWN
+ if piece == "pawn" then
+ if color == "white" then
+ local pawnWhiteMove = board[xy_to_index(from_x, from_y - 1)]
+ local en_passant = false
+ -- white pawns can go up only
+ if from_y - 1 == to_y then
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ moves[to_idx] = nil
+ end
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ local can_capture = false
+ if pieceTo:find("black") then
+ can_capture = true
+ else
+ -- en passant
+ if can_capture_en_passant(board, "black", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
+ can_capture = true
+ en_passant = true
+ end
+ end
+ if not can_capture then
+ moves[to_idx] = nil
+ end
+ else
+ moves[to_idx] = nil
+ end
+ elseif from_y - 2 == to_y then
+ if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
+ moves[to_idx] = nil
+ end
+ else
+ moves[to_idx] = nil
+ end
+
+ --[[
+ if x not changed
+ ensure that destination cell is empty
+ elseif x changed one unit left or right
+ ensure the pawn is killing opponent piece
+ else
+ move is not legal - abort
+ ]]
+
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ moves[to_idx] = nil
+ end
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ if not pieceTo:find("black") and not en_passant then
+ moves[to_idx] = nil
+ end
+ else
+ moves[to_idx] = nil
+ end
+
+ elseif color == "black" then
+ local pawnBlackMove = board[xy_to_index(from_x, from_y + 1)]
+ local en_passant = false
+ -- black pawns can go down only
+ if from_y + 1 == to_y then
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ moves[to_idx] = nil
+ end
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ local can_capture = false
+ if pieceTo:find("white") then
+ can_capture = true
+ else
+ -- en passant
+ if can_capture_en_passant(board, "white", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
+ can_capture = true
+ en_passant = true
+ end
+ end
+ if not can_capture then
+ moves[to_idx] = nil
+ end
+ else
+ moves[to_idx] = nil
+ end
+ elseif from_y + 2 == to_y then
+ if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
+ moves[to_idx] = nil
+ end
+ else
+ moves[to_idx] = nil
+ end
+
+ --[[
+ if x not changed
+ ensure that destination cell is empty
+ elseif x changed one unit left or right
+ ensure the pawn is killing opponent piece
+ else
+ move is not legal - abort
+ ]]
+
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ moves[to_idx] = nil
+ end
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ if not pieceTo:find("white") and not en_passant then
+ moves[to_idx] = nil
+ end
+ else
+ moves[to_idx] = nil
+ end
+ else
+ moves[to_idx] = nil
+ end
+
+ -- ROOK
+ elseif piece == "rook" then
+ if from_x == to_x then
+ -- Moving vertically
+ if from_y < to_y then
+ -- Moving down
+ -- Ensure that no piece disturbs the way
+ for i = from_y + 1, to_y - 1 do
+ if board[xy_to_index(from_x, i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Moving up
+ -- Ensure that no piece disturbs the way
+ for i = to_y + 1, from_y - 1 do
+ if board[xy_to_index(from_x, i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ elseif from_y == to_y then
+ -- Moving horizontally
+ if from_x < to_x then
+ -- moving right
+ -- ensure that no piece disturbs the way
+ for i = from_x + 1, to_x - 1 do
+ if board[xy_to_index(i, from_y)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Moving left
+ -- Ensure that no piece disturbs the way
+ for i = to_x + 1, from_x - 1 do
+ if board[xy_to_index(i, from_y)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ else
+ -- Attempt to move arbitrarily -> abort
+ moves[to_idx] = nil
+ end
+
+ -- KNIGHT
+ elseif piece == "knight" then
+ -- Get relative pos
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+
+ -- Get absolute values
+ if dx < 0 then
+ dx = -dx
+ end
+
+ if dy < 0 then
+ dy = -dy
+ end
+
+ -- Sort x and y
+ if dx > dy then
+ dx, dy = dy, dx
+ end
+
+ -- Ensure that dx == 1 and dy == 2
+ if dx ~= 1 or dy ~= 2 then
+ moves[to_idx] = nil
+ end
+ -- Just ensure that destination cell does not contain friend piece
+ -- ^ It was done already thus everything ok
+
+ -- BISHOP
+ elseif piece == "bishop" then
+ -- Get relative pos
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+
+ -- Get absolute values
+ if dx < 0 then
+ dx = -dx
+ end
+
+ if dy < 0 then
+ dy = -dy
+ end
+
+ -- Ensure dx and dy are equal
+ if dx ~= dy then
+ moves[to_idx] = nil
+ end
+
+ if from_x < to_x then
+ if from_y < to_y then
+ -- Moving right-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x + i, from_y + i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Moving right-up
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x + i, from_y - i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ else
+ if from_y < to_y then
+ -- Moving left-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x - i, from_y + i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Moving left-up
+ -- ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x - i, from_y - i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ end
+
+ -- QUEEN
+ elseif piece == "queen" then
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+
+ -- Get absolute values
+ if dx < 0 then
+ dx = -dx
+ end
+
+ if dy < 0 then
+ dy = -dy
+ end
+
+ -- Ensure valid relative move
+ if dx ~= 0 and dy ~= 0 and dx ~= dy then
+ moves[to_idx] = nil
+ end
+
+ if from_x == to_x then
+ -- Moving vertically
+ if from_y < to_y then
+ -- Moving down
+ -- Ensure that no piece disturbs the way
+ for i = from_y + 1, to_y - 1 do
+ if board[xy_to_index(from_x, i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Moving up
+ -- Ensure that no piece disturbs the way
+ for i = to_y + 1, from_y - 1 do
+ if board[xy_to_index(from_x, i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ elseif from_x < to_x then
+ if from_y == to_y then
+ -- Goes right
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x + i, from_y)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ elseif from_y < to_y then
+ -- Goes right-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x + i, from_y + i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Goes right-up
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x + i, from_y - i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ else
+ if from_y == to_y then
+ -- Moving horizontally
+ if from_x < to_x then
+ -- moving right
+ -- ensure that no piece disturbs the way
+ for i = from_x + 1, to_x - 1 do
+ if board[xy_to_index(i, from_y)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Moving left
+ -- Ensure that no piece disturbs the way
+ for i = to_x + 1, from_x - 1 do
+ if board[xy_to_index(i, from_y)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ elseif from_y < to_y then
+ -- Goes left-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x - i, from_y + i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ else
+ -- Goes left-up
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if board[xy_to_index(from_x - i, from_y - i)] ~= "" then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ end
+
+ -- KING
+ elseif piece == "king" then
+ -- King can't move to any attacked square
+ -- king_board simulates the board with the king moved already.
+ -- Required for the attacked() check to work
+ local king_board = table.copy(board)
+ king_board[to_idx] = king_board[from_idx]
+ king_board[from_idx] = ""
+ if realchess.attacked(color, to_idx, king_board) then
+ moves[to_idx] = nil
+ else
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+
+ if dx < 0 then
+ dx = -dx
+ end
+
+ if dy < 0 then
+ dy = -dy
+ end
+
+ if dx > 1 or dy > 1 then
+ local cc = can_castle(board, from_idx, to_idx, castlingRights)
+ if not cc then
+ moves[to_idx] = nil
+ end
+ end
+ end
+ end
+ end
+
+ if not next(moves) then
+ return {}
+ end
+
+ -- Rate the possible moves depending on its piece value
+ -- TODO: Move this to chessbot.lua
+ for i in pairs(moves) do
+ local stack_name = board[tonumber(i)]
+ if stack_name ~= "" then
+ for p, value in pairs(piece_values) do
+ if stack_name:find(p) then
+ moves[i] = value
+ end
+ end
+ end
+ end
+
+ return moves
+end
+
+-- returns all theoretically possible moves on the board for a player
+-- Parameters:
+-- * board: chessboard table
+-- * player: "black" or "white"
+-- returns: table of this format:
+-- {
+-- [origin_index_1] = { [destination_index_1] = r1, [destination_index_2] = r2 },
+-- [origin_index_2] = { [destination_index_3] = r3 },
+-- ...
+-- }
+-- origin_index is the board index for the square to start the piece from (as string)
+-- and this is the key for a list of destination indixes.
+-- r1, r2, r3 ... are numeric values (normally 0) to "rate" this square for the bot.
+function realchess.get_theoretical_moves_for(board, player, prevDoublePawnStepTo, castlingRights)
+ local moves = {}
+ for i = 1, 64 do
+ local possibleMoves = get_theoretical_moves_from(board, i, prevDoublePawnStepTo, castlingRights)
+ if next(possibleMoves) then
+ local stack_name = board[i]
+ if stack_name:find(player) then
+ moves[tostring(i)] = possibleMoves
+ end
+ end
+ end
+ return moves
+end
+
+function realchess.locate_kings(board)
+ local Bidx, Widx
+ for i = 1, 64 do
+ local piece, color = board[i]:match(":(%w+)_(%w+)")
+ if piece == "king" then
+ if color == "black" then
+ Bidx = i
+ else
+ Widx = i
+ end
+ end
+ end
+
+ return Bidx, Widx
+end
+
+-- Given a table of theoretical moves, returns a table
+-- of moves that are safe for the king, i.e. moves
+-- that neither put or leave the king at risk.
+-- 2nd return value is the number of said safe moves.
+-- * theoretical_moves: moves table returned by realchess.get_theoretical_moves_for()
+-- * board: board table
+-- * player: player color ("white" or "black")
+function realchess.get_king_safe_moves(theoretical_moves, board, player)
+ local safe_moves = {}
+ local safe_moves_count = 0
+ -- create a virtual board
+ local v_board = table.copy(board)
+
+ for from_idx, _ in pairs(theoretical_moves) do
+ for to_idx, value in pairs(_) do
+ from_idx = tonumber(from_idx)
+
+ -- save the old board values before manipulating them
+ local bak_to = v_board[to_idx]
+ local bak_from = v_board[from_idx]
+
+ -- move the piece on the virtual board
+ v_board[to_idx] = v_board[from_idx]
+ v_board[from_idx] = ""
+ local black_king_idx, white_king_idx = realchess.locate_kings(v_board)
+ if not black_king_idx or not white_king_idx then
+ minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
+ return {}, 0
+ end
+ local king_idx
+ if player == "black" then
+ king_idx = black_king_idx
+ else
+ king_idx = white_king_idx
+ end
+ local playerAttacked = realchess.attacked(player, king_idx, v_board)
+ if not playerAttacked then
+ safe_moves[from_idx] = safe_moves[from_idx] or {}
+ safe_moves[from_idx][to_idx] = value
+ safe_moves_count = safe_moves_count + 1
+ end
+
+ -- restore the old state of the virtual board
+ v_board[to_idx] = bak_to
+ v_board[from_idx] = bak_from
+ end
+ end
+
+ return safe_moves, safe_moves_count
+end
+
+-- Given a chessboard, checks whether it is in a "dead position",
+-- i.e. a position in which neither player would be able to checkmate.
+-- This function does not cover all dead positions, but only
+-- the most common ones.
+-- NOT checked are dead posisions in which both sides can still move,
+-- but cannot capture pieces or checkmate the king
+-- Parameters
+-- * board: Chessboard table
+-- Returns true if the board is in a dead position, false otherwise.
+local function is_dead_position(board)
+ -- Dead position by lack of material
+ local mat = {} -- material table to count pieces
+ -- white material
+ mat.w = {
+ -- piece counters
+ pawn = 0,
+ bishop = 0,
+ knight = 0,
+ rook = 0,
+ queen = 0,
+ -- for bishops, also record their square color
+ bishop_square_light = 0,
+ bishop_square_dark = 0,
+ }
+ -- black material
+ mat.b = table.copy(mat.w)
+ -- Count material for both players
+ for b=1, #board do
+ local piece = board[b]
+ if piece ~= "" then
+ local color
+ if piece:find("white") then
+ color = "w"
+ else
+ color = "b"
+ end
+ -- Count all pieces except kings because we can assume
+ -- the board always has 1 white and 1 black king
+ if piece:find("pawn") then
+ mat[color].pawn = mat[color].pawn + 1
+ elseif piece:find("bishop") then
+ mat[color].bishop = mat[color].bishop + 1
+ local sqcolor = get_square_index_color(b)
+ mat[color]["bishop_square_"..sqcolor] = mat[color]["bishop_square_"..sqcolor] + 1
+ elseif piece:find("knight") then
+ mat[color].knight = mat[color].knight + 1
+ elseif piece:find("rook") then
+ mat[color].rook = mat[color].rook + 1
+ elseif piece:find("queen") then
+ mat[color].queen = mat[color].queen + 1
+ end
+ end
+ end
+ -- Check well-known dead positions based on insufficient material.
+ -- If there is any rook, queen or pawn on the board, the material is sufficient.
+ if mat.w.rook == 0 and mat.w.queen == 0 and mat.w.pawn == 0 and
+ mat.b.rook == 0 and mat.b.queen == 0 and mat.b.pawn == 0 then
+ -- King against king
+ if mat.w.knight == 0 and mat.w.bishop == 0 and mat.b.knight == 0 and mat.b.bishop == 0 then
+ return true
+ -- King against king and bishop
+ elseif mat.w.knight == 0 and mat.b.knight == 0 and
+ ((mat.w.bishop == 1 and mat.b.bishop == 0) or
+ (mat.w.bishop == 0 and mat.b.bishop == 1)) then
+ return true
+ -- King against king and knight
+ elseif mat.w.bishop == 0 and mat.b.bishop == 0 and
+ ((mat.w.knight == 1 and mat.b.knight == 0) or
+ (mat.w.knight == 0 and mat.b.knight == 1)) then
+ return true
+ -- King and bishop against king and bishop,
+ -- and both bishops are on squares of the same color
+ elseif mat.w.knight == 0 and mat.b.knight == 0 and
+ (mat.w.bishop == 1 and mat.b.bishop == 1) and
+ (mat.w.bishop_square_color_light == mat.b.bishop_square_color_light) and
+ (mat.w.bishop_square_color_dark == mat.b.bishop_square_color_dark) then
+ return true
+ end
+ end
+
+ return false
+end
+
+-- Base names of all Chess pieces (with color)
+local pieces_basenames = {
+ "pawn_white",
+ "rook_white",
+ "knight_white",
+ "bishop_white",
+ "queen_white",
+ "king_white",
+ "pawn_black",
+ "rook_black",
+ "knight_black",
+ "bishop_black",
+ "queen_black",
+ "king_black",
+}
+
+-- Initial positions of the pieces on the chessboard.
+-- The pieces are specified as item names.
+-- It starts a8, continues with b8, c8, etc. then continues with a7, b7, etc. etc.
+local starting_grid = {
+ -- rank '8'
+ "realchess:rook_black_1", -- a8
+ "realchess:knight_black_1", -- b8
+ "realchess:bishop_black_1", -- c8
+ "realchess:queen_black", -- ...
+ "realchess:king_black",
+ "realchess:bishop_black_2",
+ "realchess:knight_black_2",
+ "realchess:rook_black_2",
+ -- rank '8'
+ -- rank '7'
+ "realchess:pawn_black_1",
+ "realchess:pawn_black_2",
+ "realchess:pawn_black_3",
+ "realchess:pawn_black_4",
+ "realchess:pawn_black_5",
+ "realchess:pawn_black_6",
+ "realchess:pawn_black_7",
+ "realchess:pawn_black_8",
+ -- ranks '6' thru '3'
+ '','','','','','','','',
+ '','','','','','','','',
+ '','','','','','','','',
+ '','','','','','','','',
+ -- rank '2'
+ "realchess:pawn_white_1",
+ "realchess:pawn_white_2",
+ "realchess:pawn_white_3",
+ "realchess:pawn_white_4",
+ "realchess:pawn_white_5",
+ "realchess:pawn_white_6",
+ "realchess:pawn_white_7",
+ "realchess:pawn_white_8",
+ -- rank '1'
+ "realchess:rook_white_1",
+ "realchess:knight_white_1",
+ "realchess:bishop_white_1",
+ "realchess:queen_white",
+ "realchess:king_white",
+ "realchess:bishop_white_2",
+ "realchess:knight_white_2",
+ "realchess:rook_white_2"
+}
+
+-- Figurine image IDs and file names for the chess notation table.
+-- Note: "figurine" refers to the chess notation icon, NOT the chess piece for playing.
+local figurines_str = "", 0
+local figurines_str_cnt = 0
+local MOVES_LIST_SYMBOL_EMPTY = figurines_str_cnt
+figurines_str = figurines_str .. MOVES_LIST_SYMBOL_EMPTY .. "=mailbox_blank16.png"
+for i = 1, #pieces_basenames do
+ figurines_str_cnt = figurines_str_cnt + 1
+ local p = pieces_basenames[i]
+ figurines_str = figurines_str .. "," .. figurines_str_cnt .. "=chess_figurine_" .. p .. ".png"
+end
+
+local function get_figurine_id(piece_itemname)
+ local piece_s = piece_itemname:match(":(%w+_%w+)")
+ if not piece_s then
+ return MOVES_LIST_SYMBOL_EMPTY
+ else
+ return figurines_str:match("(%d+)=chess_figurine_" .. piece_s)
+ end
+end
+
+
+local fs_gamemode_x
+if CHESS_DEBUG then
+ fs_gamemode_x = 10.2
+else
+ fs_gamemode_x = 11.5
+end
+
+local fs_init = [[
+ formspec_version[2]
+ size[16,10.7563;]
+ no_prepend[]
+ ]]
+ .."bgcolor[#00000000;false]"
+ .."background[0,0;16,10.7563;chess_bg.png;true]"
+ .."style_type[button,image_button,item_image_button;bgcolor=#8f3000]"
+ .."label[2.2,0.652;"..minetest.colorize("#404040", FS("Select a game mode")).."]"
+ .."label[2.2,10.21;"..minetest.colorize("#404040", FS("Select a game mode")).."]"
+ .."label[11.2,1.8;"..FS("Select a mode:").."]"
+ .."button[11,2.1;3,0.8;single;"..FS("Singleplayer").."]"
+ .."button[11,3.1;3,0.8;multi;"..FS("Multiplayer").."]"
+ if CHESS_DEBUG then
+ fs_init = fs_init .."button[11,4.1;3,0.8;bot_vs_bot;"..FS("Bot vs Bot").."]"
+ end
+
+local fs = [[
+ formspec_version[2]
+ size[16,10.7563;]
+ no_prepend[]
+ bgcolor[#00000000;false]
+ background[0,0;16,10.7563;chess_bg.png;true]
+ style_type[button,image_button,item_image_button;bgcolor=#8f3000]
+ style_type[list;spacing=0.1;size=0.975]
+ listcolors[#00000000;#00000000;#00000000;#30434C;#FFF]
+ list[context;board;0.47,1.155;8,8;]
+ tableoptions[background=#00000000;highlight=#00000000;border=false]
+ ]]
+ -- table columns for Chess notation.
+ -- Columns: move no.; white piece; white halfmove; white promotion; black piece; black halfmove; black promotion
+ .."tablecolumns[" ..
+ "text,align=right;"..
+ "image," .. figurines_str .. ";text;image," .. figurines_str .. ";" ..
+ --"image,0=mailbox_blank16.png;" ..
+ "image," .. figurines_str .. ";text;image," .. figurines_str .. "]"
+
+local function add_move_to_moves_list(meta, pieceFrom, pieceTo, from_idx, to_idx, special)
+ local moves_raw = meta:get_string("moves_raw")
+ if moves_raw ~= "" then
+ moves_raw = moves_raw .. ";"
+ end
+ if not special then
+ special = ""
+ end
+ moves_raw = moves_raw .. pieceFrom .. "," .. pieceTo .. "," .. from_idx .. "," .. to_idx .. "," .. special
+ meta:set_string("moves_raw", moves_raw)
+end
+
+local function add_special_to_moves_list(meta, special)
+ add_move_to_moves_list(meta, "", "", "", "", special)
+end
+
+
+-- abbreviation for each piece for the string
+-- representation of the board (like in FEN)
+local piece_letters = {
+ white = {
+ pawn = "P",
+ knight = "N",
+ bishop = "B",
+ rook = "R",
+ queen = "Q",
+ king = "K",
+ },
+ black = {
+ pawn = "p",
+ knight = "n",
+ bishop = "b",
+ rook = "r",
+ queen = "q",
+ king = "k",
+ },
+}
+
+local function piece_to_letter(piece)
+ if piece == "" then
+ return "."
+ elseif piece:find("white") then
+ for k,v in pairs(piece_letters.white) do
+ if piece:find(k) then
+ return v
+ end
+ end
+ elseif piece:find("black") then
+ for k,v in pairs(piece_letters.black) do
+ if piece:find(k) then
+ return v
+ end
+ end
+ end
+end
+local function letter_to_piece(letter)
+ if letter == "." then
+ return ""
+ else
+ for k,v in pairs(piece_letters.white) do
+ if v == letter then
+ return v
+ end
+ end
+ for k,v in pairs(piece_letters.black) do
+ if v == letter then
+ return v
+ end
+ end
+ end
+ return nil
+end
+
+-- Returns a list of all positions so far, for the purposes
+-- of determining position equality under the
+-- "3-/5-fold repetition" draw rules.
+-- Each possible position is uniquely identified by a string
+-- so equal positions have the same string and unequal positons
+-- have a different string.
+-- A position string containts the following data:
+-- * position of pieces on the board
+-- * current player
+-- * castling rights
+-- * target coords of the square crossed by the pawn who
+-- made a double step in the prvious turn (if any)
+--
+-- Patemeter: meta is the node metadata of the chessboard
+--
+-- NOTE: The FIDE Laws of Chess (Jan 2023, article 9.2.3.1)
+-- are somewhat unclear about en passant here ... Is it important
+-- the pawn was only theoretically vulnerable to being
+-- captured en passant without being actually threatened
+-- that way, or does it only count if the pawn was actually
+-- threatened by another pawn to be captured that way?
+-- This mod currently interprets the rule in the former way,
+-- i.e. a double step by a pawn
+local function get_positions_history(meta)
+ -- Turns a board table to a string.
+ -- The syntax is inspired by FEN but not identical.
+ -- It iterates through the table from start
+ -- to finish and turns every square to a character,
+ -- representing a piece or an empty square.
+ local function board_to_string(board)
+ local str = ""
+ for b=1, #board do
+ local piece = board[b]
+ local append
+ str = str .. piece_to_letter(piece)
+ end
+ return str
+ end
+
+ -- Record the position no. of the position from which
+ -- all positions before that can be ignored
+ -- when counting same positions.
+ -- This number is updated each time an irreversible
+ -- move is made:
+ -- * pawn move
+ -- * capturing move
+ -- * loss of castling rights
+ -- * loss of (theoretical) en passant availability
+ -- This works cuz when an irreversible move is made,
+ -- it is impossible for this and all positions before
+ -- that to occur again so the "repetition counter"
+ -- for each of these position never increases again.
+ local no_repetitions_before = 1
+
+ local moves_raw = meta:get_string("moves_raw")
+ local moves_split = string.split(moves_raw, ";")
+ local positions_list = ""
+ local board = table.copy(starting_grid)
+ local castling_state = { true, true, true, true }
+ local castling_str = castling_to_string(unpack(castling_state))
+ local pawn_double_step_index
+ positions_list = {}
+
+ local current_player = "w"
+ local position_string = board_to_string(board) .. " " ..
+ current_player .. " " ..
+ castling_str .. " " ..
+ en_passant_to_string(nil)
+ table.insert(positions_list, position_string)
+ for m=1, #moves_split do
+ local move_split = string.split(moves_split[m], ",", true)
+ local pieceFrom = move_split[1]
+ local pieceTo = move_split[2]
+ local from_idx = tonumber(move_split[3])
+ local to_idx = tonumber(move_split[4])
+ local special = move_split[5]
+ if special == "" or special:sub(1,7) == "promo__" then
+
+ if current_player == "w" then
+ current_player = "b"
+ else
+ current_player = "w"
+ end
+ -- Reset repetition counter on piece capture
+ if pieceTo ~= "" then
+ no_repetitions_before = m
+ end
+
+ -- Piece movement
+ board[to_idx] = board[from_idx]
+ board[from_idx] = ""
+
+ -- Pawn promotion
+ if special:sub(1, 7) == "promo__" then
+ local promoSym = special:sub(8)
+ board[to_idx] = promoSym
+ end
+
+ local from_x, from_y = index_to_xy(from_idx)
+ local to_x, to_y = index_to_xy(to_idx)
+ if pieceFrom:sub(11,14) == "king" then
+ -- Castling (move rook)
+ if (from_y == 7 and to_y == 7) then
+ if (from_x == 4 and to_x == 2) then
+ board[60] = board[57]
+ board[57] = ""
+ no_repetitions_before = m
+ elseif (from_x == 4 and to_x == 6) then
+ board[62] = board[64]
+ board[64] = ""
+ no_repetitions_before = m
+ end
+ elseif (from_y == 0 and to_y == 0) then
+ if (from_x == 4 and to_x == 2) then
+ board[4] = board[1]
+ board[1] = ""
+ no_repetitions_before = m
+ elseif (from_x == 4 and to_x == 6) then
+ board[6] = board[8]
+ board[8] = ""
+ no_repetitions_before = m
+ end
+ end
+ -- Lose castling rights on any king move
+ if pieceFrom:find("white") then
+ if castling_state[1] or castling_state[2] then
+ no_repetitions_before = m
+ end
+ castling_state[1] = false
+ castling_state[2] = false
+ else
+ if castling_state[3] or castling_state[4] then
+ no_repetitions_before = m
+ end
+ castling_state[3] = false
+ castling_state[4] = false
+ end
+
+ -- Lose castling rights on lone rook move
+ elseif pieceFrom:sub(11,14) == "rook" then
+ if from_idx == 57 then
+ -- white queenside
+ if castling_state[2] then
+ castling_state[2] = false
+ no_repetitions_before = m
+ end
+ elseif from_idx == 64 then
+ -- white kingside
+ if castling_state[1] then
+ castling_state[1] = false
+ no_repetitions_before = m
+ end
+ elseif from_idx == 1 then
+ -- black queenside
+ if castling_state[4] then
+ castling_state[4] = false
+ no_repetitions_before = m
+ end
+ elseif from_idx == 8 then
+ -- black kingside
+ if castling_state[3] then
+ castling_state[3] = false
+ no_repetitions_before = m
+ end
+ end
+ end
+
+ -- Pawn movement resets repetition counter
+ if pieceFrom:sub(11,14) == "pawn" then
+ no_repetitions_before = m
+ end
+
+ -- Loss of (theoretical) en passant capture availability
+ -- resets repetition counter
+ if pawn_double_step_index then
+ no_repetitions_before = m
+ pawn_double_step_index = nil
+ end
+
+ if pieceTo == "" and pieceFrom:sub(11,14) == "pawn" then
+ -- En passant (remove captured pawn)
+ if from_x ~= to_x then
+ local epp_y
+ if pieceFrom:find("white") then
+ epp_y = to_y + 1
+ else
+ epp_y = to_y - 1
+ end
+ board[xy_to_index(to_x, epp_y)] = ""
+ -- Double pawn step (record pos)
+ elseif math.abs(from_y-to_y) == 2 then
+ pawn_double_step_index = to_idx
+ end
+ end
+
+ castling_str = castling_to_string(unpack(castling_state))
+ local position_string = board_to_string(board) .. " " ..
+ current_player .. " " ..
+ castling_str .. " " ..
+ en_passant_to_string(pawn_double_step_index)
+ table.insert(positions_list, position_string)
+ end
+ end
+ local p=#positions_list
+ return positions_list, no_repetitions_before
+end
+
+-- Returns the highest number of positions that are repeated
+-- in the given positions history list as well as the number
+-- of occurrances of the last position.
+--
+-- Arguments:
+-- * positions: positions history list returned by get_position_history()
+-- * first_position_no: index of first position to check (default: 1)
+--
+-- Returns ,
+local function count_repeated_positions(positions, first_position_no)
+ -- Count how often each position occurred
+ local positions_counter = {}
+ local maxRepeatedPositions = 0
+ first_position_no = first_position_no or 1
+ for p = first_position_no, #positions do
+ local position = positions[p]
+ if positions_counter[position] == nil then
+ positions_counter[position] = 1
+ else
+ positions_counter[position] = positions_counter[position] + 1
+ end
+ if positions_counter[position] > maxRepeatedPositions then
+ maxRepeatedPositions = positions_counter[position]
+ end
+ end
+ local lastPosition = positions[#positions]
+ local lastPositionOccurredTimes = positions_counter[lastPosition] or 0
+ return maxRepeatedPositions, lastPositionOccurredTimes
+end
+
+-- Create the full formspec string for the sequence of moves.
+-- Uses Figurine Algebraic Notation.
+local function get_moves_formstring(meta)
+ local moves_raw = meta:get_string("moves_raw")
+ if moves_raw == "" then
+ return "", 0
+ end
+
+ local moves_split = string.split(moves_raw, ";")
+ local moves_out = ""
+ local move_no = 0
+ for m=1, #moves_split do
+ local move_split = string.split(moves_split[m], ",", true)
+ local pieceFrom = move_split[1]
+ local pieceTo = move_split[2]
+ local from_idx = tonumber(move_split[3])
+ local to_idx = tonumber(move_split[4])
+ local special = move_split[5]
+
+ -- true if White plays, false if Black plays
+ local curPlayerIsWhite = m % 2 == 1
+
+ if special == "whiteWon" or special == "blackWon" or special == "draw" then
+ if not curPlayerIsWhite then
+ moves_out = moves_out .. ""..MOVES_LIST_SYMBOL_EMPTY..",," .. MOVES_LIST_SYMBOL_EMPTY .. ","
+ end
+ end
+ if special == "whiteWon" then
+ moves_out = moves_out .. ","..MOVES_LIST_SYMBOL_EMPTY..",1–0,"..MOVES_LIST_SYMBOL_EMPTY
+ move_no = move_no + 1
+ elseif special == "blackWon" then
+ moves_out = moves_out .. ","..MOVES_LIST_SYMBOL_EMPTY..",0–1,"..MOVES_LIST_SYMBOL_EMPTY
+ move_no = move_no + 1
+ elseif special == "draw" then
+ moves_out = moves_out .. ","..MOVES_LIST_SYMBOL_EMPTY..",½–½,"..MOVES_LIST_SYMBOL_EMPTY
+ move_no = move_no + 1
+ else
+ local from_x, from_y = index_to_xy(from_idx)
+ local to_x, to_y = index_to_xy(to_idx)
+ local pieceFrom_si_id
+ -- Show no piece icon for pawn
+ if pieceFrom:sub(11,14) == "pawn" then
+ pieceFrom_si_id = MOVES_LIST_SYMBOL_EMPTY
+ else
+ pieceFrom_si_id = get_figurine_id(pieceFrom)
+ end
+ local pieceTo_si_id = pieceTo ~= "" and get_figurine_id(pieceTo)
+
+ local coordFrom = realchess.index_to_notation(from_idx)
+ local coordTo = realchess.index_to_notation(to_idx)
+
+ if curPlayerIsWhite then
+ move_no = move_no + 1
+ -- Add move number (e.g. "3.")
+ moves_out = moves_out .. string.format("%d.", move_no) .. ","
+ end
+ local betweenCoordsSymbol = "–" -- to be inserted between source and destination coords
+ -- dash for normal moves, × for capturing moves
+ local enPassantSymbol = "" -- symbol for en passant captures
+ if pieceTo ~= "" then
+ -- normal capture
+ betweenCoordsSymbol = "×"
+ elseif pieceTo == "" and pieceFrom:sub(11,14) == "pawn" and from_x ~= to_x then
+ -- 'en passant' capture
+ betweenCoordsSymbol = "×"
+ enPassantSymbol = " e.p."
+ end
+
+ ---- Add halfmove of current player
+ -- Castling
+ local castling = false
+ if pieceFrom:sub(11,14) == "king" and ((curPlayerIsWhite and from_y == 7 and to_y == 7) or (not curPlayerIsWhite and from_y == 0 and to_y == 0)) then
+ -- queenside castling
+ if from_x == 4 and to_x == 2 then
+ -- write "0–0–0"
+ moves_out = moves_out .. MOVES_LIST_SYMBOL_EMPTY .. ",0–0–0," .. MOVES_LIST_SYMBOL_EMPTY
+ castling = true
+ -- kingside castling
+ elseif from_x == 4 and to_x == 6 then
+ -- write "0–0"
+ moves_out = moves_out .. MOVES_LIST_SYMBOL_EMPTY .. ",0–0," .. MOVES_LIST_SYMBOL_EMPTY
+ castling = true
+ end
+ end
+ -- Normal halfmove
+ if not castling then
+ moves_out = moves_out ..
+ pieceFrom_si_id .. "," .. -- piece image ID
+ coordFrom .. betweenCoordsSymbol .. coordTo .. -- coords in long algebraic notation, e.g. "e2e3"
+ enPassantSymbol .. "," -- written in case of an 'en passant' capture
+
+ -- Promotion?
+ if special:sub(1, 7) == "promo__" then
+ local promoSym = special:sub(8)
+ moves_out = moves_out .. get_figurine_id(promoSym)
+ else
+ moves_out = moves_out .. MOVES_LIST_SYMBOL_EMPTY
+ end
+ end
+
+ -- If White moved, fill up the rest of the row with empty space.
+ -- Required for validity of the table
+ if curPlayerIsWhite and m == #moves_split then
+ moves_out = moves_out .. "," .. MOVES_LIST_SYMBOL_EMPTY .. ",," .. MOVES_LIST_SYMBOL_EMPTY
+ end
+ end
+
+ if m ~= #moves_split then
+ moves_out = moves_out .. ","
+ end
+ end
+ return moves_out, move_no
+end
+
+-- Verify eaten list
+local verify_eaten_list
+if CHESS_DEBUG then
+ verify_eaten_list = function(meta)
+ local inv = meta:get_inventory()
+ local board = realchess.board_to_table(inv)
+ local whitePiecesLeft = 0
+ local whitePiecesEaten = 0
+ local blackPiecesLeft = 0
+ local blackPiecesEaten = 0
+ for b=1, 64 do
+ local piece = board[b]
+ if piece:find("white") then
+ whitePiecesLeft = whitePiecesLeft + 1
+ elseif piece:find("black") then
+ blackPiecesLeft = blackPiecesLeft + 1
+ end
+ end
+ local eaten = meta:get_string("eaten")
+ local eaten_split = string.split(eaten, ",")
+ for e=1, #eaten_split do
+ local piece = eaten_split[e]
+ if piece:find("white") then
+ whitePiecesEaten = whitePiecesEaten + 1
+ elseif piece:find("black") then
+ blackPiecesEaten = blackPiecesEaten + 1
+ end
+ end
+ local eatenError = false
+ if whitePiecesLeft + whitePiecesEaten ~= 16 then
+ minetest.log("error", "[xdecor] Chess: Incorrect number of white pieces in eaten list! pieces="..whitePiecesLeft.."; eaten="..whitePiecesEaten)
+ eatenError = true
+ elseif blackPiecesLeft + blackPiecesEaten ~= 16 then
+ minetest.log("error", "[xdecor] Chess: Incorrect number of black pieces in eaten list! pieces="..blackPiecesLeft.."; eaten="..blackPiecesEaten)
+ eatenError = true
+ end
+ if eatenError then
+ -- halt bots
+ local mode = meta:get_string("mode")
+ if mode == "bot_vs_bot" then
+ meta:set_string("botColor", "")
+ end
+ end
+ end
+end
+
+-- Reports that a piece was "eaten" (=captured).
+-- Must be called right after the board inventory was updated
+-- on which the piece is already removed
+-- * meta: Chessboard node metadata
+-- * piece: The itemname of the piece that was captured
+local function add_to_eaten_list(meta, piece)
+ if piece ~= "" then
+ local eaten = meta:get_string("eaten")
+ if eaten ~= "" then
+ eaten = eaten .. ","
+ end
+ local piece_s = piece:match(":(%w+_%w+)") or ""
+ eaten = eaten .. piece_s
+ meta:set_string("eaten", eaten)
+ if CHESS_DEBUG then
+ verify_eaten_list(meta)
+ end
+ end
+end
+
+local function get_eaten_formstring(meta)
+ local eaten = meta:get_string("eaten")
+ local eaten_t = string.split(eaten, ",")
+ local eaten_img = ""
+ local a, b = 0, 0
+ for i = 1, #eaten_t do
+ local is_white = eaten_t[i]:sub(-5,-1) == "white"
+ local X = (is_white and a or b) % 4
+ local Y = ((is_white and a or b) % 16 - X) / 4
+
+ if is_white then
+ a = a + 1
+ else
+ b = b + 1
+ end
+
+ eaten_img = eaten_img ..
+ "image[" .. ((X + (is_white and 12.82 or 9.72)) - (X * 0.44)) .. "," ..
+ ((Y + 6) - (Y * 0.12)) .. ";1,1;" .. eaten_t[i] .. ".png]"
+ end
+ return eaten_img
+end
+
+local function update_formspec(meta)
+ local black_king_attacked = meta:get_string("blackAttacked") == "true"
+ local white_king_attacked = meta:get_string("whiteAttacked") == "true"
+ local botColor = meta:get_string("botColor")
+
+ local playerWhite = meta:get_string("playerWhite")
+ local playerBlack = meta:get_string("playerBlack")
+
+ -- Translate the bot names for the display
+ -- (internally, the bot names are still English-only)
+ local playerWhiteDisplay = get_display_player_name(meta, "white")
+ local playerBlackDisplay = get_display_player_name(meta, "black")
+
+ local moves_raw = meta:get_string("moves_raw")
+ local moves, mlistlen = get_moves_formstring(meta)
+ local eaten_img = get_eaten_formstring(meta)
+ local lastMove = meta:get_string("lastMove")
+ local gameResult = meta:get_string("gameResult")
+ local grReason = meta:get_string("gameResultReason")
+ local mode = meta:get_string("mode")
+
+ -- arrow to show whose turn it is
+ local blackArr = (gameResult == "" and lastMove == "white" and "image[1.2,0.252;0.7,0.7;chess_turn_black.png]") or ""
+ local whiteArr = (gameResult == "" and (lastMove == "" or lastMove == "black") and "image[1.2,9.81;0.7,0.7;chess_turn_white.png]") or ""
+ local turnBlack = minetest.colorize("#000001", playerBlackDisplay)
+ local turnWhite = minetest.colorize("#000001", playerWhiteDisplay)
+
+ -- several status words for the player
+ --~ Chess: player is in check
+ local check_s = minetest.colorize("#FF8000", "["..S("check").."]")
+ --~ Chess: player has been checkmated
+ local mate_s = minetest.colorize("#FF0000", "["..S("checkmate").."]")
+ --~ Chess: player has resigned
+ local resign_s = minetest.colorize("#FF0000", "["..S("resigned").."]")
+ --~ Chess: player has won
+ local win_s = minetest.colorize("#26AB2B", "["..S("winner").."]")
+ --~ Chess: player has lost
+ local lose_s = minetest.colorize("#FF0000", "["..S("loser").."]")
+ --~ Chess: player has a draw
+ local draw_s = minetest.colorize("#FF00FF", "["..S("draw").."]")
+
+ local status_black = ""
+ local status_white = ""
+ if gameResult == "blackWon" then
+ if grReason == "resign" then
+ status_white = " " .. resign_s
+ elseif grReason == "checkmate" then
+ status_white = " " .. mate_s
+ else
+ status_white = " " .. lose_s
+ end
+ status_black = " " .. win_s
+ elseif gameResult == "draw" then
+ status_black = " " .. draw_s
+ status_white = " " .. draw_s
+ elseif gameResult == "whiteWon" then
+ if grReason == "resign" then
+ status_black = " " .. resign_s
+ elseif grReason == "checkmate" then
+ status_black = " " .. mate_s
+ else
+ status_black = " " .. lose_s
+ end
+ status_white = " " .. win_s
+ else
+ if black_king_attacked then
+ status_black = " " .. check_s
+ end
+ if white_king_attacked then
+ status_white = " " .. check_s
+ end
+ end
+
+ local promotion = ""
+ if gameResult == "" then
+ promotion = meta:get_string("promotionActive")
+ end
+ local promotion_formstring = ""
+
+ -- Show promotion prompt to ask player to choose to which piece to promote a pawn to
+ if promotion == "black" then
+ eaten_img = ""
+ promotion_formstring =
+ --~ Chess: Shown when black player can promote a pawn. Space for text is limited.
+ "label[10.1,6.35;"..FS("PROMOTION\nFOR BLACK!").."]" ..
+ "animated_image[10.05,7.2;2,2;p_img_white;pawn_black_promo_anim.png;5;100]"
+ if botColor ~= "black" and botColor ~= "both" then
+ -- Hide buttons if computer player promotes
+ promotion_formstring = promotion_formstring ..
+ "label[13.15,6.35;"..FS("Promote pawn to:").."]" ..
+ "item_image_button[13.15,7.2;1,1;realchess:queen_black;p_queen_black;]" ..
+ "item_image_button[14.15,7.2;1,1;realchess:rook_black_1;p_rook_black;]" ..
+ "item_image_button[13.15,8.2;1,1;realchess:bishop_black_1;p_bishop_black;]" ..
+ "item_image_button[14.15,8.2;1,1;realchess:knight_black_1;p_knight_black;]"
+ end
+
+ elseif promotion == "white" then
+ eaten_img = ""
+ promotion_formstring =
+ --~ Chess: Shown when white player can promote a pawn. Space for text is limited.
+ "label[10.1,6.35;"..FS("PROMOTION\nFOR WHITE!").."]" ..
+ "animated_image[10.05,7.2;2,2;p_img_white;pawn_white_promo_anim.png;5;100]"
+ if botColor ~= "white" and botColor ~= "both" then
+ -- Hide buttons if computer player promotes
+ promotion_formstring = promotion_formstring ..
+ "label[13.15,6.35;"..FS("Promote pawn to:").."]" ..
+ "item_image_button[13.15,7.2;1,1;realchess:queen_white;p_queen_white;]" ..
+ "item_image_button[14.15,7.2;1,1;realchess:rook_white_1;p_rook_white;]" ..
+ "item_image_button[13.15,8.2;1,1;realchess:bishop_white_1;p_bishop_white;]" ..
+ "item_image_button[14.15,8.2;1,1;realchess:knight_white_1;p_knight_white;]"
+ end
+ end
+
+ local drawClaim = meta:get_string("drawClaim")
+ local draw_claim_formstring = ""
+ if drawClaim ~= "" and gameResult == "" then
+ if lastMove == "black" or lastMove == "" then
+ --~ Chess: Shown when white player wants to claim a draw. Space for text is limited.
+ draw_claim_formstring = "label[10.1,6.35;"..FS("DRAW CLAIM\nBY WHITE!").."]"
+ else
+ --~ Chess: Shown when black player wants to claim a draw. Space for text is limited.
+ draw_claim_formstring = "label[10.1,6.35;"..FS("DRAW CLAIM\nBY BLACK!").."]"
+ end
+ if drawClaim == "50_move_rule" then
+ eaten_img = ""
+ draw_claim_formstring = draw_claim_formstring ..
+ "image[10.05,7.2;2,2;chess_draw_50move_next.png]"..
+ "textarea[13,6.35;2.2,3.2;;;"..FS("The player has invoked the 50-move rule for the next move. The next move might draw the game.").."]"
+ elseif drawClaim == "same_position_3" then
+ eaten_img = ""
+ draw_claim_formstring = draw_claim_formstring ..
+ "image[10.05,7.2;2,2;chess_draw_repeat3_next.png]"..
+ "textarea[13,6.35;2.2,3.2;;;"..FS("The player has invoked the threefold-repetition rule for the next move. The next move might draw the game.").."]"
+ end
+ end
+
+ -- Resign / Start new game
+ local game_buttons = ""
+ game_buttons = game_buttons .. "button[13.36,0.26;2,0.8;new;"..FS("New game").."]"
+
+ local playerActionsAvailable = mode ~= "bot_vs_bot" and gameResult == ""
+
+ if playerActionsAvailable and (playerWhite ~= "" and playerBlack ~= "") then
+ game_buttons = game_buttons .. "image_button[14.56,9.7;0.8,0.8;chess_resign.png;resign;]" ..
+ --~ Resign in Chess
+ "tooltip[resign;"..FS("Resign").."]"
+ end
+
+ -- Let player choose with which to play singleplayer
+ if lastMove == "" and gameResult == "" and mode == "single" and playerWhite == "" then
+ game_buttons = game_buttons .. "label[11.2,1.8;"..FS("Select a color:").."]"
+ .."style[single_black;bgcolor=#000000FF;textcolor=#FFFFFFFF]"
+ .."style[single_white;bgcolor=#FFFFFFFF;textcolor=#000000FF]"
+ .."button[11,2.1;3,0.8;single_white;"..FS("White").."]"
+ .."button[11,3.1;3,0.8;single_black;"..FS("Black").."]"
+ end
+
+ if playerActionsAvailable and drawClaim == "" then
+ -- 50-move rule
+ local halfmoveClock = meta:get_int("halfmoveClock")
+ if halfmoveClock == DRAWCLAIM_LONGGAME_PLAYER - 1 then
+ -- When the 50 moves without capture / pawn move is about to occur.
+ -- Will trigger "draw claim" mode in which player must do the final move that triggers the draw
+ game_buttons = game_buttons .. "image_button[13.36,9.7;0.8,0.8;chess_draw_50move_next.png;draw_50_moves;]"..
+ "tooltip[draw_50_moves;"..
+ --~ Chess
+ FS("Invoke the 50-move rule for your next move").."]"
+ elseif halfmoveClock >= DRAWCLAIM_LONGGAME_PLAYER then
+ -- When the 50 moves without capture / pawn move have occured occur.
+ -- Will insta-draw.
+ game_buttons = game_buttons .. "image_button[13.36,9.7;0.8,0.8;chess_draw_50move.png;draw_50_moves;]"..
+ "tooltip[draw_50_moves;"..
+ --~ Chess
+ FS("Invoke the 50-move rule and draw the game").."]"
+ end
+
+ -- Threefold repetition rule
+ -- Count how often each position occurred
+ local positions, first_p = get_positions_history(meta)
+ local maxRepeatedPositions, lastOccurred = count_repeated_positions(positions, first_p)
+ if lastOccurred >= 3 then
+ -- If the same position has already occured 3 times
+ -- Will insta-draw.
+ game_buttons = game_buttons .. "image_button[12.36,9.7;0.8,0.8;chess_draw_repeat3.png;draw_repeat_3;]"..
+ "tooltip[draw_repeat_3;"..
+ --~ Chess
+ FS("Invoke the threefold repetition rule and draw the game").."]"
+ elseif maxRepeatedPositions >= 2 then
+ -- If the same position may be about to occur 3 times.
+ -- Will trigger "draw claim" mode in which player must do the final move that triggers the draw.
+ game_buttons = game_buttons .. "image_button[12.36,9.7;0.8,0.8;chess_draw_repeat3_next.png;draw_repeat_3;]"..
+ "tooltip[draw_repeat_3;"..
+ --~ Chess
+ FS("Invoke the threefold repetition rule for your next move").."]"
+ end
+ end
+
+ local debug_formstring = ""
+ if CHESS_DEBUG then
+ -- Write a debug string in the formspec based on FEN
+ -- to show some hidden state.
+ -- It uses FEN syntax but without the piece positions
+
+ -- current player: b or w
+ local d_turn = "-"
+ if lastMove == "white" then
+ d_turn = "b"
+ elseif lastMove == "black" or lastMove == "" then
+ d_turn = "w"
+ end
+ -- castling rights
+ local d_castling = castling_to_string(
+ meta:get_int("castlingWhiteR") == 1,
+ meta:get_int("castlingWhiteL") == 1,
+ meta:get_int("castlingBlackR") == 1,
+ meta:get_int("castlingBlackL") == 1)
+ -- en passant possible?
+ local double_step = meta:get_int("prevDoublePawnStepTo")
+ local d_en_passant = en_passant_to_string(double_step)
+
+ -- The halfmove clock counts for how many consecutive halfmoves
+ -- have been made with no pawn advancing and no piece being captured
+ local d_halfmove_clock = meta:get_int("halfmoveClock")
+
+ -- fullmove starts at 1 and should count up every time black moves
+ local d_fullmove = tostring(get_current_fullmove(meta) + 1)
+
+ local debug_str = d_turn .. " " .. d_castling .. " " .. d_en_passant .. " " .. d_halfmove_clock .. " " .. d_fullmove
+ debug_formstring = "label[7.9,10.2;DEBUG: "..debug_str.."]"
+ end
+
+ local moves_list = ""
+ if mlistlen > 0 then
+ -- Moves list (show only if not empty)
+ moves_list = "table[9.9,1.25;5.45,4;moves;" .. moves .. ";"..mlistlen.."]"
+ end
+
+ local formspec = fs ..
+ "label[2.2,0.652;" .. turnBlack .. minetest.formspec_escape(status_black) .. "]" ..
+ blackArr ..
+ "label[2.2,10.21;" .. turnWhite .. minetest.formspec_escape(status_white) .. "]" ..
+ whiteArr ..
+ moves_list ..
+ promotion_formstring ..
+ draw_claim_formstring ..
+ eaten_img ..
+ game_buttons ..
+ debug_formstring
+
+ meta:set_string("formspec", formspec)
+end
+
+local function update_game_result(meta, lastMove)
+ local inv = meta:get_inventory()
+ local board_t = realchess.board_to_table(inv)
+
+ local playerWhite = meta:get_string("playerWhite")
+ local playerBlack = meta:get_string("playerBlack")
+ local prevDoublePawnStepTo = meta:get_int("prevDoublePawnStepTo")
+ local castlingRights = {
+ castlingWhiteR = meta:get_int("castlingWhiteR"),
+ castlingWhiteL = meta:get_int("castlingWhiteL"),
+ castlingBlackR = meta:get_int("castlingBlackR"),
+ castlingBlackL = meta:get_int("castlingBlackL"),
+ }
+
+ update_formspec(meta)
+ local blackCanMove = false
+ local whiteCanMove = false
+
+ local blackMoves = realchess.get_theoretical_moves_for(board_t, "black", prevDoublePawnStepTo, castlingRights)
+ local whiteMoves = realchess.get_theoretical_moves_for(board_t, "white", prevDoublePawnStepTo, castlingRights)
+ if next(blackMoves) then
+ blackCanMove = true
+ end
+ if next(whiteMoves) then
+ whiteCanMove = true
+ end
+
+ local black_king_idx, white_king_idx = realchess.locate_kings(board_t)
+ if not black_king_idx or not white_king_idx then
+ minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
+ return
+ end
+
+ local checkPlayer, king_idx, checkMoves
+ if lastMove == "black" or lastMove == "" then
+ checkPlayer = "white"
+ checkMoves = whiteMoves
+ king_idx = white_king_idx
+ else
+ checkPlayer = "black"
+ checkMoves = blackMoves
+ king_idx = black_king_idx
+ end
+
+ -- King attacked?
+ local isKingAttacked = realchess.attacked(checkPlayer, king_idx, board_t)
+ if isKingAttacked then
+ meta:set_string(checkPlayer.."Attacked", "true")
+ end
+
+ -- If not safe moves left, player can't move
+ local safe_moves, safe_moves_count = realchess.get_king_safe_moves(checkMoves, board_t, checkPlayer)
+ if safe_moves_count == 0 then
+ if checkPlayer == "black" then
+ blackCanMove = false
+ else
+ whiteCanMove = false
+ end
+ end
+
+ local playerWhiteDisplay = get_display_player_name(meta, "white")
+ local playerBlackDisplay = get_display_player_name(meta, "black")
+ local botColor = meta:get_string("botColor")
+
+ if lastMove == "white" and not blackCanMove then
+ if meta:get_string("blackAttacked") == "true" then
+ -- black was checkmated
+ meta:set_string("gameResult", "whiteWon")
+ meta:set_string("gameResultReason", "checkmate")
+ add_special_to_moves_list(meta, "whiteWon")
+ send_message(playerWhite, S("You have checkmated @1. You win!", playerBlackDisplay), "white", botColor)
+ send_message(playerBlack, S("You were checkmated by @1. You lose!", playerWhiteDisplay), "black", botColor)
+ minetest.log("action", "[xdecor] Chess: "..playerWhite.." won against "..playerBlack.." by checkmate")
+ return
+ else
+ -- stalemate
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "stalemate")
+ add_special_to_moves_list(meta, "draw")
+ --~ Chess message
+ send_message_2(playerWhite, playerBlack, S("The game ended up in a stalemate! It's a draw!"), botColor)
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw by stalemate")
+ return
+ end
+ end
+ if lastMove == "black" and not whiteCanMove then
+ if meta:get_string("whiteAttacked") == "true" then
+ -- white was checkmated
+ meta:set_string("gameResult", "blackWon")
+ meta:set_string("gameResultReason", "checkmate")
+ add_special_to_moves_list(meta, "blackWon")
+ send_message(playerBlack, S("You have checkmated @1. You win!", playerWhiteDisplay), "white", botColor)
+ send_message(playerWhite, S("You were checkmated by @1. You lose!", playerBlackDisplay), "white", botColor)
+ minetest.log("action", "[xdecor] Chess: "..playerBlack .." won against "..playerWhite.." by checkmate")
+ return
+ else
+ -- stalemate
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "stalemate")
+ add_special_to_moves_list(meta, "draw")
+ --~ Chess message
+ send_message_2(playerWhite, playerBlack, S("The game ended up in a stalemate! It's a draw!"), botColor)
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw by stalemate")
+ return
+ end
+ end
+
+ -- Is this a dead position?
+ if is_dead_position(board_t) then
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "dead_position")
+ add_special_to_moves_list(meta, "draw")
+ --~ Chess message
+ send_message_2(playerWhite, playerBlack, S("The game ended up in a dead position! It's a draw!"), botColor)
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw by dead position")
+ end
+
+ local drawClaim = meta:get_string("drawClaim")
+ -- 75-move rule. Automatically draw game if the last 75 moves of EACH player (thus 150 halfmoves)
+ -- neither moved a pawn or captured a piece.
+ -- Important: This rule MUST be checked AFTER checkmate because checkmate takes precedence.
+ if meta:get_int("halfmoveClock") >= DRAWCLAIM_LONGGAME_FORCE then
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "75_move_rule")
+ add_special_to_moves_list(meta, "draw")
+ local msg = S("No piece was captured and no pawn was moved for 75 consecutive moves of each player. It's a draw!")
+ send_message_2(playerWhite, playerBlack, msg, botColor)
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw via the 75-move rule")
+ -- 50-move rule, after a player issued a draw claim for their next move.
+ -- If no pawn moved nor a piece was captured for >= 100 halfmoves, the game is drawn.
+ elseif meta:get_int("halfmoveClock") >= DRAWCLAIM_LONGGAME_PLAYER and drawClaim == "50_move_rule" then
+ meta:set_string("drawClaim", "")
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "50_move_rule")
+ add_special_to_moves_list(meta, "draw")
+ update_formspec(meta)
+ local claimer, other
+ if lastMove == "black" or lastMove == "" then
+ claimer = playerWhite
+ other = playerBlack
+ else
+ claimer = playerBlack
+ other = playerWhite
+ end
+ send_message(claimer, S("You have drawn the game by invoking the 50-move rule."), botColor)
+ if claimer ~= other then
+ send_message(other, S("@1 has drawn the game by invoking the 50-move rule.", claimer), botColor)
+ end
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw because "..claimer.." has invoked the 50-move rule")
+ elseif drawClaim == "50_move_rule" then
+ local claimer, other
+ if lastMove == "black" or lastMove == "" then
+ claimer = playerWhite
+ other = playerBlack
+ else
+ claimer = playerBlack
+ other = playerWhite
+ end
+ send_message(claimer, S("You have failed to make a game-drawing move. The game continues."), botColor)
+ if claimer ~= other then
+ send_message(other, S("@1 made a draw claim using the 50-move rule but it was false. The game continues.", claimer), botColor)
+ end
+ end
+
+ -- Draw if same position appeared >= 5 times
+ -- or if it appeared >= 3 times and the player claimed the draw previously
+ -- First, generate the position history
+ local forceRepetitionDraw = false
+ local chosenRepetitionDraw = false
+ local positions, first_p = get_positions_history(meta)
+ -- Then count the repeated positions
+ local _, lastOccurred = count_repeated_positions(positions, first_p)
+ if lastOccurred >= 3 then
+ chosenRepetitionDraw = true
+ end
+ if lastOccurred >= 5 then
+ forceRepetitionDraw = true
+ end
+ if CHESS_DEBUG then
+ -- Show last position
+ local last_position = positions[#positions]
+ local msg = "Current position: \"" .. last_position .. "\""
+ send_message_2(playerWhite, playerBlack, msg, botColor)
+
+ -- Compare the last position with the actual chessboard
+ -- to automatically test if the position history is still valid
+ local pos_split = last_position:split(" ")
+ local p_board = pos_split[1]
+ local p_player = pos_split[2]
+ local p_castling = pos_split[3]
+ local p_en_passant = pos_split[4]
+ local errors = 0
+ for b=1, #board_t do
+ local piece = board_t[b]
+ local letter_real = piece_to_letter(piece)
+ local letter_pos = string.sub(p_board, b, b)
+ if letter_real ~= letter_pos then
+ minetest.log("error", "[xdecor] Chess: Position history inconsistency on board index "..b..": '"..letter_pos.."' seen but '"..letter_real.."' expected")
+ end
+ end
+ -- Compare current player
+ if (lastMove == "black" or lastMove == "") and p_player ~= "w" then
+ minetest.log("error", "[xdecor] Chess: Position history inconsistency: Wrong player! '"..p_player.."' seen but 'w' expected")
+ elseif (lastMove == "white") and p_player ~= "b" then
+ minetest.log("error", "[xdecor] Chess: Position history inconsistency: Wrong player! '"..p_player.."' seen but 'b' expected")
+ end
+ -- Compare castling rights
+ local d_castling = castling_to_string(
+ meta:get_int("castlingWhiteR") == 1,
+ meta:get_int("castlingWhiteL") == 1,
+ meta:get_int("castlingBlackR") == 1,
+ meta:get_int("castlingBlackL") == 1)
+ if d_castling ~= p_castling then
+ minetest.log("error", "[xdecor] Chess: Position history inconsistency: Castling rights mismatch! '"..p_castling.."' seen but '"..d_castling.."' expected")
+ end
+ -- Compare en passant status
+ local double_step = meta:get_int("prevDoublePawnStepTo")
+ local d_en_passant = en_passant_to_string(double_step)
+ if d_en_passant ~= p_en_passant then
+ minetest.log("error", "[xdecor] Chess: Position history inconsistency: En passant status mismatch! '"..p_en_passant.."' seen but '"..d_en_passant.."' expected")
+ end
+ end
+
+ -- fivefold repetition
+ if forceRepetitionDraw then
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "same_position_5")
+ add_special_to_moves_list(meta, "draw")
+ --~ Chess message when the fivefold repetition has happened
+ local msg = S("The exact same position has occured 5 times. It's a draw!")
+ send_message_2(playerWhite, playerBlack, msg, botColor)
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw because the same position has appeared 5 times")
+
+ -- threefold repetition
+ elseif chosenRepetitionDraw and drawClaim == "same_position_3" then
+ meta:set_string("drawClaim", "")
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "same_position_3")
+ add_special_to_moves_list(meta, "draw")
+ update_formspec(meta)
+ local claimer, other
+ if lastMove == "black" or lastMove == "" then
+ claimer = playerWhite
+ other = playerBlack
+ else
+ claimer = playerBlack
+ other = playerWhite
+ end
+ send_message(claimer, S("You have drawn the game by invoking the threefold repetition rule."), botColor)
+ if claimer ~= other then
+ send_message(other, S("@1 has drawn the game by invoking the threefold repetition rule.", claimer), botColor)
+ end
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw because "..claimer.." has invoked the threefold repetition rule")
+ elseif drawClaim == "same_position_3" then
+ local claimer, other
+ if lastMove == "black" or lastMove == "" then
+ claimer = playerWhite
+ other = playerBlack
+ else
+ claimer = playerBlack
+ other = playerWhite
+ end
+ send_message(claimer, S("You have failed to make a game-drawing move. The game continues."), botColor)
+ if claimer ~= other then
+ send_message(other, S("@1 made a draw claim using the threefold repetition rule but it was false. The game continues.", claimer), botColor)
+ end
+ end
+
+ meta:set_string("drawClaim", "")
+end
+
+
+function realchess.init(pos)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+
+ meta:set_string("formspec", fs_init)
+ meta:set_string("infotext", S("Chess Board"))
+ meta:set_string("playerBlack", "")
+ meta:set_string("playerWhite", "")
+ meta:set_string("botColor", "")
+ meta:set_string("lastMove", "")
+ meta:set_string("gameResult", "")
+ meta:set_string("gameResultReason", "")
+ meta:set_string("drawClaim", "")
+ meta:set_string("blackAttacked", "")
+ meta:set_string("whiteAttacked", "")
+ meta:set_string("promotionActive", "")
+
+ meta:set_int("lastMoveTime", 0)
+ meta:set_int("castlingBlackL", 1)
+ meta:set_int("castlingBlackR", 1)
+ meta:set_int("castlingWhiteL", 1)
+ meta:set_int("castlingWhiteR", 1)
+ meta:set_int("promotionPawnFromIdx", 0)
+ meta:set_int("promotionPawnToIdx", 0)
+ meta:set_int("prevDoublePawnStepTo", 0)
+ meta:set_int("halfmoveClock", 0)
+
+ meta:set_string("moves_raw", "")
+ meta:set_string("eaten", "")
+ meta:set_string("mode", "")
+
+ inv:set_list("board", starting_grid)
+ inv:set_size("board", 64)
+
+ -- Clear legacy metadata
+ meta:set_string("moves", "")
+ meta:set_string("eaten_img", "")
+end
+
+-- The move logic of Chess.
+-- This is meant to be called when a player *ATTEMPTS* to move a piece
+-- from one slot of the inventory to another one and reacts accordingly.
+-- If the move is valid, the inventory is changed to reflect the new
+-- situation, and the game state and UI is upated as well and true
+-- is returned.
+-- If the move is invalid, nothing happens and false is returned.
+-- Note: The move can also be done by a computer player.
+--
+-- * meta: Chessboard node metadata
+-- * from_list: Inventory list of source square
+-- * from_index: Inventory index of source square
+-- * to_list: Inventory list of destination square
+-- * to_index: Inventory list of destination square
+-- * playerName: Name of player to move
+function realchess.move(meta, from_list, from_index, to_list, to_index, playerName)
+ if from_list ~= "board" and to_list ~= "board" then
+ return false
+ end
+
+ local promo = meta:get_string("promotionActive")
+ if promo ~= "" then
+ -- Can't move when waiting for selecting a pawn promotion
+ return false
+ end
+ local gameResult = meta:get_string("gameResult")
+ if gameResult ~= "" then
+ -- No moves if game is over
+ return false
+ end
+
+ local inv = meta:get_inventory()
+ local pieceFrom = inv:get_stack(from_list, from_index):get_name()
+ local pieceTo = inv:get_stack(to_list, to_index):get_name()
+ local lastMove = meta:get_string("lastMove")
+ local playerWhite = meta:get_string("playerWhite")
+ local playerBlack = meta:get_string("playerBlack")
+ local prevDoublePawnStepTo = meta:get_int("prevDoublePawnStepTo")
+ local kingMoved = false
+ local thisMove -- Will replace lastMove when move is legal
+
+ if pieceFrom:find("white") then
+ if playerWhite ~= "" and playerWhite ~= playerName then
+ send_message(playerName, S("Someone else plays white pieces!"))
+ return false
+ end
+
+ if pieceTo:find("white") then
+ -- Don't replace pieces of same color
+ return false
+ end
+
+ if lastMove == "white" then
+ send_message(playerName, S("It's not your turn!"))
+ return
+ end
+
+ playerWhite = playerName
+ thisMove = "white"
+
+ elseif pieceFrom:find("black") then
+ if playerBlack ~= "" and playerBlack ~= playerName then
+ send_message(playerName, S("Someone else plays black pieces!"))
+ return false
+ end
+
+ if pieceTo:find("black") then
+ -- Don't replace pieces of same color
+ return false
+ end
+
+ if lastMove == "black" then
+ send_message(playerName, S("It's not your turn!"))
+ return false
+ end
+
+ if lastMove == "" then
+ -- Nobody has moved yet, and Black cannot move first
+ send_message(playerName, S("Black cannot move first!"))
+ return false
+ end
+
+ playerBlack = playerName
+ thisMove = "black"
+ end
+
+ -- MOVE LOGIC
+
+ local from_x, from_y = index_to_xy(from_index)
+ local to_x, to_y = index_to_xy(to_index)
+
+ local promotion = false
+ local doublePawnStep = nil
+ local en_passant_target = nil
+ local lostCastlingRightRook = nil
+ local resetHalfmoveClock = false
+
+ -- PAWN
+ if pieceFrom:sub(11,14) == "pawn" then
+ if thisMove == "white" then
+ local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
+ -- white pawns can go up only
+ if from_y - 1 == to_y then
+ -- single step
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ return false
+ elseif to_index >= 1 and to_index <= 8 then
+ -- activate promotion
+ promotion = true
+ end
+ resetHalfmoveClock = true
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ if to_index >= 1 and to_index <= 8 and pieceTo:find("black") then
+ -- activate promotion
+ promotion = true
+ end
+ resetHalfmoveClock = true
+ else
+ return false
+ end
+ elseif from_y - 2 == to_y then
+ -- double step
+ if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
+ return false
+ end
+ -- store the destination of this double step in meta (needed for en passant check)
+ doublePawnStep = to_index
+ resetHalfmoveClock = true
+ else
+ return false
+ end
+
+ --[[
+ if x not changed
+ ensure that destination cell is empty
+ elseif x changed one unit left or right
+ ensure the pawn is killing opponent piece
+ else
+ move is not legal - abort
+ ]]
+
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ return false
+ end
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ -- capture
+ local can_capture = false
+ if pieceTo:find("black") then
+ -- normal capture
+ can_capture = true
+ else
+ -- en passant
+ local board = realchess.board_to_table(inv)
+ if can_capture_en_passant(board, "black", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
+ can_capture = true
+ en_passant_target = xy_to_index(to_x, from_y)
+ end
+ end
+ if not can_capture then
+ return false
+ end
+ resetHalfmoveClock = true
+ else
+ return false
+ end
+
+ elseif thisMove == "black" then
+ local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
+ -- black pawns can go down only
+ if from_y + 1 == to_y then
+ -- single step
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ return false
+ elseif to_index >= 57 and to_index <= 64 then
+ -- activate promotion
+ promotion = true
+ end
+ resetHalfmoveClock = true
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ if to_index >= 57 and to_index <= 64 and pieceTo:find("white") then
+ -- activate promotion
+ promotion = true
+ end
+ resetHalfmoveClock = true
+ else
+ return false
+ end
+ elseif from_y + 2 == to_y then
+ -- double step
+ if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
+ return false
+ end
+ -- store the destination of this double step in meta (needed for en passant check)
+ doublePawnStep = to_index
+ resetHalfmoveClock = true
+ else
+ return false
+ end
+
+ --[[
+ if x not changed
+ ensure that destination cell is empty
+ elseif x changed one unit left or right
+ ensure the pawn is killing opponent piece
+ else
+ move is not legal - abort
+ ]]
+
+ if from_x == to_x then
+ if pieceTo ~= "" then
+ return false
+ end
+ elseif from_x - 1 == to_x or from_x + 1 == to_x then
+ -- capture
+ local can_capture = false
+ if pieceTo:find("white") then
+ -- normal capture
+ can_capture = true
+ else
+ -- en passant
+ local board = realchess.board_to_table(inv)
+ if can_capture_en_passant(board, "white", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
+ can_capture = true
+ en_passant_target = xy_to_index(to_x, from_y)
+ end
+ end
+ if not can_capture then
+ return false
+ end
+ resetHalfmoveClock = true
+ else
+ return false
+ end
+ else
+ return false
+ end
+
+ -- ROOK
+ elseif pieceFrom:sub(11,14) == "rook" then
+ if from_x == to_x then
+ -- Moving vertically
+ if from_y < to_y then
+ -- Moving down
+ -- Ensure that no piece disturbs the way
+ for i = from_y + 1, to_y - 1 do
+ if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
+ return false
+ end
+ end
+ else
+ -- Moving up
+ -- Ensure that no piece disturbs the way
+ for i = to_y + 1, from_y - 1 do
+ if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
+ return false
+ end
+ end
+ end
+ elseif from_y == to_y then
+ -- Moving horizontally
+ if from_x < to_x then
+ -- moving right
+ -- ensure that no piece disturbs the way
+ for i = from_x + 1, to_x - 1 do
+ if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
+ return false
+ end
+ end
+ else
+ -- Moving left
+ -- Ensure that no piece disturbs the way
+ for i = to_x + 1, from_x - 1 do
+ if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
+ return false
+ end
+ end
+ end
+ else
+ -- Attempt to move arbitrarily -> abort
+ return false
+ end
+
+ -- Lose castling right when moving rook
+ if thisMove == "white" then
+ if from_index == 57 then
+ -- queenside white rook
+ lostCastlingRightRook = "castlingWhiteL"
+ elseif from_index == 64 then
+ -- kingside white rook
+ lostCastlingRightRook = "castlingWhiteR"
+ end
+ elseif thisMove == "black" then
+ if from_index == 1 then
+ -- queenside black rook
+ lostCastlingRightRook = "castlingBlackL"
+ elseif from_index == 8 then
+ -- kingside black rook
+ lostCastlingRightRook = "castlingBlackR"
+ end
+ end
+
+ -- KNIGHT
+ elseif pieceFrom:sub(11,16) == "knight" then
+ -- Get relative pos
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+
+ -- Get absolute values
+ if dx < 0 then dx = -dx end
+ if dy < 0 then dy = -dy end
+
+ -- Sort x and y
+ if dx > dy then dx, dy = dy, dx end
+
+ -- Ensure that dx == 1 and dy == 2
+ if dx ~= 1 or dy ~= 2 then
+ return false
+ end
+ -- Just ensure that destination cell does not contain friend piece
+ -- ^ It was done already thus everything ok
+
+ -- BISHOP
+ elseif pieceFrom:sub(11,16) == "bishop" then
+ -- Get relative pos
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+
+ -- Get absolute values
+ if dx < 0 then dx = -dx end
+ if dy < 0 then dy = -dy end
+
+ -- Ensure dx and dy are equal
+ if dx ~= dy then return false end
+
+ if from_x < to_x then
+ if from_y < to_y then
+ -- Moving right-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
+ return false
+ end
+ end
+ else
+ -- Moving right-up
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
+ return false
+ end
+ end
+ end
+ else
+ if from_y < to_y then
+ -- Moving left-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
+ return false
+ end
+ end
+ else
+ -- Moving left-up
+ -- ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
+ return false
+ end
+ end
+ end
+ end
+
+ -- QUEEN
+ elseif pieceFrom:sub(11,15) == "queen" then
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+
+ -- Get absolute values
+ if dx < 0 then dx = -dx end
+ if dy < 0 then dy = -dy end
+
+ -- Ensure valid relative move
+ if dx ~= 0 and dy ~= 0 and dx ~= dy then
+ return false
+ end
+
+ if from_x == to_x then
+ if from_y < to_y then
+ -- Goes down
+ -- Ensure that no piece disturbs the way
+ for i = from_y + 1, to_y - 1 do
+ if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
+ return false
+ end
+ end
+ else
+ -- Goes up
+ -- Ensure that no piece disturbs the way
+ for i = to_y + 1, from_y - 1 do
+ if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
+ return false
+ end
+ end
+ end
+ elseif from_x < to_x then
+ if from_y == to_y then
+ -- Goes right
+ -- Ensure that no piece disturbs the way
+ for i = from_x + 1, to_x - 1 do
+ if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
+ return false
+ end
+ end
+ elseif from_y < to_y then
+ -- Goes right-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
+ return false
+ end
+ end
+ else
+ -- Goes right-up
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
+ return false
+ end
+ end
+ end
+ else
+ if from_y == to_y then
+ -- Goes left
+ -- Ensure that no piece disturbs the way and destination cell does
+ for i = to_x + 1, from_x - 1 do
+ if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
+ return false
+ end
+ end
+ elseif from_y < to_y then
+ -- Goes left-down
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
+ return false
+ end
+ end
+ else
+ -- Goes left-up
+ -- Ensure that no piece disturbs the way
+ for i = 1, dx - 1 do
+ if inv:get_stack(
+ from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
+ return false
+ end
+ end
+ end
+ end
+
+ -- KING
+ elseif pieceFrom:sub(11,14) == "king" then
+ local dx = from_x - to_x
+ local dy = from_y - to_y
+ local check = true
+ local inv = meta:get_inventory()
+ local board = realchess.board_to_table(inv)
+ local castlingRights = {
+ castlingWhiteR = meta:get_int("castlingWhiteR"),
+ castlingWhiteL = meta:get_int("castlingWhiteL"),
+ castlingBlackR = meta:get_int("castlingBlackR"),
+ castlingBlackL = meta:get_int("castlingBlackL"),
+ }
+
+ -- Castling
+ local cc, rook_start, rook_goal, rook_name = can_castle(board, from_index, to_index, castlingRights)
+ if cc then
+ inv:set_stack(from_list, rook_goal, rook_name)
+ inv:set_stack(from_list, rook_start, "")
+ check = false
+ kingMoved = true
+ end
+
+ if check then
+ if dx < 0 then
+ dx = -dx
+ end
+
+ if dy < 0 then
+ dy = -dy
+ end
+
+ if dx > 1 or dy > 1 then
+ return false
+ end
+ end
+ kingMoved = true
+
+ end
+
+ local board = realchess.board_to_table(inv)
+ board[to_index] = board[from_index]
+ board[from_index] = ""
+
+ local black_king_idx, white_king_idx = realchess.locate_kings(board)
+ if not black_king_idx or not white_king_idx then
+ minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
+ return false
+ end
+ local blackAttacked = realchess.attacked("black", black_king_idx, board)
+ local whiteAttacked = realchess.attacked("white", white_king_idx, board)
+
+ -- Refuse to move if it would put or leave the own king
+ -- under attack
+ if blackAttacked and thisMove == "black" then
+ return false
+ end
+ if whiteAttacked and thisMove == "white" then
+ return false
+ end
+
+ if pieceTo ~= "" then
+ resetHalfmoveClock = true
+ end
+
+ -- The halfmove clock counts the number of consecutive halfmoves
+ -- in which neither a pawn was moved nor a piece was captured.
+ if resetHalfmoveClock then
+ meta:set_int("halfmoveClock", 0)
+ else
+ meta:set_int("halfmoveClock", meta:get_int("halfmoveClock") + 1)
+ end
+
+ if en_passant_target then
+ -- Capture pawn en passant
+ local capturedPiece = inv:get_stack(to_list, en_passant_target):get_name()
+ inv:set_stack(to_list, en_passant_target, "")
+ add_to_eaten_list(meta, capturedPiece)
+ end
+
+ if kingMoved and thisMove == "white" then
+ meta:set_int("castlingWhiteL", 0)
+ meta:set_int("castlingWhiteR", 0)
+ elseif kingMoved and thisMove == "black" then
+ meta:set_int("castlingBlackL", 0)
+ meta:set_int("castlingBlackR", 0)
+ elseif lostCastlingRightRook then
+ meta:set_int(lostCastlingRightRook, 0)
+ end
+
+ if promotion then
+ meta:set_string("promotionActive", thisMove)
+ meta:set_int("promotionPawnFromIdx", from_index)
+ meta:set_int("promotionPawnToIdx", to_index)
+ else
+ realchess.update_state(meta, from_index, to_index, thisMove)
+ end
+
+ if doublePawnStep then
+ meta:set_int("prevDoublePawnStepTo", doublePawnStep)
+ else
+ meta:set_int("prevDoublePawnStepTo", 0)
+ end
+
+ if meta:get_string("playerWhite") == "" then
+ meta:set_string("playerWhite", playerWhite)
+ -- If in singleplayer, the player moved a white piece first without
+ -- explicitly selecting a color, interpret this as the player wanting
+ -- to play as white
+ if meta:get_string("mode") == "single" and lastMove == "" and meta:get_string("gameResult") == "" then
+ meta:set_string("playerBlack", "*"..BOT_NAME.."*")
+ meta:set_string("botColor", "black")
+ end
+ elseif meta:get_string("playerBlack") == "" then
+ meta:set_string("playerBlack", playerBlack)
+ end
+
+ realchess.move_piece(meta, pieceFrom, from_list, from_index, to_list, to_index)
+
+ return true
+end
+
+-- Causes the player ("white" or "blue") to resign
+function realchess.resign(meta, playerColor)
+ if playerColor == "black" then
+ meta:set_string("gameResult", "whiteWon")
+ meta:set_string("gameResultReason", "resign")
+ add_special_to_moves_list(meta, "whiteWon")
+ update_formspec(meta)
+ elseif playerColor == "white" then
+ meta:set_string("gameResult", "blackWon")
+ meta:set_string("gameResultReason", "resign")
+ add_special_to_moves_list(meta, "blackWon")
+ update_formspec(meta)
+ end
+end
+
+local function timeout_format(timeout_limit)
+ local time_remaining = timeout_limit - minetest.get_gametime()
+ local minutes = math.floor(time_remaining / 60)
+ local seconds = time_remaining % 60
+
+ if minutes == 0 then
+ --~ number of seconds
+ return S("@1 s", seconds)
+ end
+
+ --~ number of minutes and seconds
+ return S("@1 min @2 s", minutes, seconds)
+end
+
+function realchess.fields(pos, _, fields, sender)
+ local playerName = sender:get_player_name()
+ local meta = minetest.get_meta(pos)
+ local timeout_limit = meta:get_int("lastMoveTime") + TIMEOUT
+ local playerWhite = meta:get_string("playerWhite")
+ local playerBlack = meta:get_string("playerBlack")
+ local lastMoveTime = meta:get_int("lastMoveTime")
+ local gameResult = meta:get_int("gameResult")
+ if fields.quit then return end
+
+ if fields.single or fields.multi or fields.bot_vs_bot then
+ if fields.bot_vs_bot then
+ if not CHESS_DEBUG then
+ -- Bot vs Bot only allowed in Chess Debug Mode
+ return
+ end
+ meta:set_string("mode", "bot_vs_bot")
+ meta:set_string("botColor", "both")
+ -- Add asterisk to bot names so it can't collide with a player name
+ -- (asterisk is forbidden in player names)
+ meta:set_string("playerWhite", "*"..BOT_NAME_1.."*")
+ meta:set_string("playerBlack", "*"..BOT_NAME_2.."*")
+ local inv = meta:get_inventory()
+ chessbot.move(inv, meta)
+ elseif fields.single then
+ meta:set_string("mode", "single")
+ elseif fields.multi then
+ meta:set_string("mode", "multi")
+ end
+ update_formspec(meta)
+ return
+ end
+
+ local mode = meta:get_string("mode")
+ -- "Play as White/Black" button in Singleplayer when nobody has moved yet
+ if (fields.single_black or fields.single_white) and mode == "single" and meta:get_string("gameResult") == "" and meta:get_string("lastMove") == "" then
+ if fields.single_white then
+ meta:set_string("botColor", "black")
+ meta:set_string("playerWhite", playerName)
+ meta:set_string("playerBlack", "*"..BOT_NAME.."*")
+ update_formspec(meta)
+ else
+ meta:set_string("botColor", "white")
+ meta:set_string("playerWhite", "*"..BOT_NAME.."*")
+ meta:set_string("playerBlack", playerName)
+ update_formspec(meta)
+ local inv = meta:get_inventory()
+ chessbot.move(inv, meta)
+ end
+ return
+ end
+
+ -- If the game is ongoing and no move was made for TIMEOUT seconds,
+ -- the game can be aborted by everyone.
+ -- Also allow instant reset before White and Black moved,
+ -- as well as in Bot vs Bot mode, as well
+ -- when the game ended.
+ if fields.new then
+ if mode == "bot_vs_bot" or (playerWhite == playerName or playerBlack == playerName or playerWhite == "" or playerBlack == "") or meta:get_string("gameResult") ~= "" then
+ realchess.init(pos)
+
+ elseif lastMoveTime > 0 then
+ if minetest.get_gametime() >= timeout_limit and
+ (playerWhite ~= playerName or playerBlack ~= playerName) then
+ realchess.init(pos)
+ else
+ send_message(playerName,
+ S("You can't reset the chessboard, a game has been started. Try again in @1.",
+ timeout_format(timeout_limit)))
+ end
+ end
+ return
+ end
+
+ if fields.resign and mode ~= "bot_vs_bot" then
+ local lastMove = meta:get_string("lastMove")
+ if (playerName == playerWhite and playerWhite == "") or (playerName == playerBlack and playerBlack == "") then
+ -- Can't resign before the player name has been recorded
+ --~ Chess message when player tried to resign too early
+ send_message(playerName, S("Resigning is not possible yet."))
+ return
+ end
+ local winner, loser, whiteWon
+ if playerWhite == playerBlack and playerWhite == playerName and playerWhite ~= "" then
+ if lastMove == "black" then
+ winner = playerBlack
+ loser = playerWhite
+ whiteWon = false
+ else
+ winner = playerWhite
+ loser = playerBlack
+ whiteWon = true
+ end
+ elseif playerName == playerWhite and playerWhite ~= "" then
+ winner = playerBlack
+ loser = playerWhite
+ whiteWon = false
+ elseif playerName == playerBlack and playerBlack ~= "" then
+ winner = playerWhite
+ loser = playerBlack
+ whiteWon = true
+ end
+ if winner and loser then
+ if whiteWon then
+ realchess.resign(meta, "black")
+ else
+ realchess.resign(meta, "white")
+ end
+
+ send_message(loser, S("You have resigned."))
+ if playerWhite ~= playerBlack then
+ send_message(winner, S("@1 has resigned. You win!", loser))
+ end
+ minetest.log("action", "[xdecor] Chess: "..loser.." has resigned from the game against "..winner)
+ update_formspec(meta)
+ else
+ send_message(playerName, S("You can't resign, you're not playing in this game."))
+ end
+ return
+ end
+
+ -- Claim or declare draw via the 50-move rule
+ if fields.draw_50_moves then
+ local botColor = meta:get_string("botColor")
+ local lastMove = meta:get_string("lastMove")
+ if playerWhite == "" and playerBlack == "" or lastMove == "" then
+ return
+ end
+ local currentPlayer
+ if lastMove == "black" or lastMove == "" then
+ currentPlayer = "white"
+ else
+ currentPlayer = "black"
+ end
+
+ local claimer, other
+ if (currentPlayer == "white" and playerWhite == playerName) then
+ claimer = playerWhite
+ other = playerBlack
+ elseif (currentPlayer == "black" and playerBlack == playerName) then
+ claimer = playerBlack
+ other = playerWhite
+ else
+ send_message(playerName, S("You can't claim a draw, it's not your turn!"))
+ return
+ end
+
+ local halfmoveClock = meta:get_int("halfmoveClock")
+ if halfmoveClock >= DRAWCLAIM_LONGGAME_PLAYER then
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "50_move_rule")
+ add_special_to_moves_list(meta, "draw")
+ update_formspec(meta)
+ send_message(claimer, S("You have drawn the game by invoking the 50-move rule."), botColor)
+ if claimer ~= other then
+ send_message(other, S("@1 has drawn the game by invoking the 50-move rule.", claimer), botColor)
+ end
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw because "..claimer.." has invoked the 50-move rule")
+ else
+ meta:set_string("drawClaim", "50_move_rule")
+ update_formspec(meta)
+ end
+
+ return
+ end
+
+ -- Claim or declare draw via the threefold repetition rule (same position occured >= 3 times)
+ if fields.draw_repeat_3 then
+ local botColor = meta:get_string("botColor")
+ local lastMove = meta:get_string("lastMove")
+ if playerWhite == "" and playerBlack == "" or lastMove == "" then
+ return
+ end
+ local currentPlayer
+ if lastMove == "black" or lastMove == "" then
+ currentPlayer = "white"
+ else
+ currentPlayer = "black"
+ end
+
+ local claimer, other
+ if (currentPlayer == "white" and playerWhite == playerName) then
+ claimer = playerWhite
+ other = playerBlack
+ elseif (currentPlayer == "black" and playerBlack == playerName) then
+ claimer = playerBlack
+ other = playerWhite
+ else
+ send_message(playerName, S("You can't claim a draw, it's not your turn!"))
+ return
+ end
+
+ local positions, first_p = get_positions_history(meta)
+ local _, lastOccurred = count_repeated_positions(positions, first_p)
+ if lastOccurred >= 3 then
+ meta:set_string("gameResult", "draw")
+ meta:set_string("gameResultReason", "same_position_3")
+ add_special_to_moves_list(meta, "draw")
+ update_formspec(meta)
+ send_message(claimer, S("You have drawn the game by invoking the threefold repetition rule."), botColor)
+ if claimer ~= other then
+ send_message(other, S("@1 has drawn the game by invoking the threefold repetition rule.", claimer), botColor)
+ end
+ minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw because "..claimer.." has invoked the threefold repetition rule")
+ else
+ meta:set_string("drawClaim", "same_position_3")
+ update_formspec(meta)
+ end
+ return
+ end
+
+ local promotions = {
+ "queen_white", "rook_white", "bishop_white", "knight_white",
+ "queen_black", "rook_black", "bishop_black", "knight_black",
+ }
+ for p=1, #promotions do
+ local gameResult = meta:get_string("gameResult")
+ if gameResult ~= "" then
+ return
+ end
+ local promo = promotions[p]
+ if fields["p_"..promo] then
+ if not (playerName == playerWhite or playerName == playerBlack) then
+ send_message(playerName, S("You're only a spectator in this game of Chess."))
+ return
+ end
+ local pcolor = promo:sub(-5)
+ local activePromo = meta:get_string("promotionActive")
+ if activePromo == "" then
+ --~ Chess message
+ send_message(playerName, S("This isn't the time for promotion."))
+ return
+ elseif activePromo ~= pcolor then
+ --~ Chess message
+ send_message(playerName, S("It's not your turn! This promotion is meant for the other player."))
+ return
+ end
+ if pcolor == "white" and playerName == playerWhite or pcolor == "black" and playerName == playerBlack then
+ realchess.promote_pawn(meta, pcolor, promo:sub(1, -7))
+ return
+ else
+ --~ Chess message
+ send_message(playerName, S("It's not your turn! This promotion is meant for the other player."))
+ return
+ end
+ end
+ end
+end
+
+function realchess.can_dig(pos, player)
+ if not player or not player:is_player() then
+ return false
+ end
+ -- Protection_bypass priv guarantees digging rights
+ if minetest.check_player_privs(player, "protection_bypass") then
+ return true
+ end
+
+ local meta = minetest.get_meta(pos)
+ local playerName = player:get_player_name()
+ local timeout_limit = meta:get_int("lastMoveTime") + TIMEOUT
+ local lastMoveTime = meta:get_int("lastMoveTime")
+ local playerWhite = meta:get_string("playerWhite")
+ local playerBlack = meta:get_string("playerBlack")
+ local botColor = meta:get_string("botColor")
+
+ -- Bot matches always allow dig
+ if (meta:get_string("mode") == "bot_vs_bot") then
+ return true
+ elseif (meta:get_string("gameResult") ~= "") then
+ -- If the game was completed, the board is free to be dug
+ return true
+ -- If the game is ongoing and no move was made for TIMEOUT seconds,
+ -- the board is free to be dug
+ elseif (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) then
+ return true
+ else
+ if playerName == playerWhite or playerName == playerBlack or botColor == "both" then
+ send_message(playerName,
+ S("You can't dig the chessboard, a game has been started. " ..
+ "Reset it first or dig it again in @1.",
+ timeout_format(timeout_limit)))
+ else
+ send_message(playerName,
+ S("You can't dig the chessboard, a game has been started. " ..
+ "Try it again in @1.",
+ timeout_format(timeout_limit)))
+ end
+ return false
+ end
+end
+
+-- Helper function for realchess.move.
+-- To be called when a valid normal move should be taken.
+-- Will also update the state for the Chessboard.
+function realchess.move_piece(meta, pieceFrom, from_list, from_index, to_list, to_index)
+ local inv = meta:get_inventory()
+ local pieceTo = inv:get_stack(to_list, to_index):get_name()
+
+ -- Update inventory slots
+ inv:set_stack(from_list, from_index, "")
+ inv:set_stack(to_list, to_index, pieceFrom)
+
+ -- Report the eaten piece
+ if pieceTo ~= "" then
+ add_to_eaten_list(meta, pieceTo)
+ end
+
+ local lastMove = meta:get_string("lastMove")
+ if lastMove == "" then lastMove = "black" end
+
+ local promo = meta:get_string("promotionActive") ~= ""
+ if not promo then
+ update_game_result(meta, lastMove)
+ lastMove = meta:get_string("lastMove")
+ if lastMove == "" then lastMove = "black" end
+ end
+ update_formspec(meta)
+
+ local botColor = meta:get_string("botColor")
+ if botColor == "" then botColor = "black" end
+ local mode = meta:get_string("mode")
+ local gameResult = meta:get_string("gameResult")
+ -- Let the bot play when it its turn
+ if (mode == "bot_vs_bot" or (mode == "single" and lastMove ~= botColor)) and gameResult == "" then
+ if not promo then
+ chessbot.move(inv, meta)
+ else
+ chessbot.promote(inv, meta, to_index)
+ end
+ end
+end
+
+function realchess.update_state(meta, from_index, to_index, thisMove, promoteFrom, promoteTo)
+ local inv = meta:get_inventory()
+ local board = realchess.board_to_table(inv)
+ local pieceTo = board[to_index]
+ local pieceFrom = promoteFrom or board[from_index]
+
+ if not promoteFrom then
+ board[to_index] = board[from_index]
+ board[from_index] = ""
+ end
+
+ local black_king_idx, white_king_idx = realchess.locate_kings(board)
+ if not black_king_idx or not white_king_idx then
+ minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
+ return
+ end
+ local blackAttacked = realchess.attacked("black", black_king_idx, board)
+ local whiteAttacked = realchess.attacked("white", white_king_idx, board)
+
+ if blackAttacked then
+ meta:set_string("blackAttacked", "true")
+ else
+ meta:set_string("blackAttacked", "")
+ end
+
+ if whiteAttacked then
+ meta:set_string("whiteAttacked", "true")
+ else
+ meta:set_string("whiteAttacked", "")
+ end
+
+ local lastMove = thisMove
+ meta:set_string("lastMove", lastMove)
+ meta:set_int("lastMoveTime", minetest.get_gametime())
+
+ local special
+ if promoteTo then
+ special = "promo__"..promoteTo
+ end
+ add_move_to_moves_list(meta, pieceFrom, pieceTo, from_index, to_index, special)
+end
+
+function realchess.promote_pawn(meta, color, promoteTo)
+ local inv = meta:get_inventory()
+ local pstr = promoteTo .. "_" .. color
+ local promoted = false
+ local to_idx = meta:get_int("promotionPawnToIdx")
+ local from_idx = meta:get_int("promotionPawnFromIdx")
+ if to_idx < 1 or from_idx < 1 then
+ return
+ end
+ if promoteTo ~= "queen" then
+ pstr = pstr .. "_1"
+ end
+ pstr = "realchess:" .. pstr
+
+ local promoteFrom
+ if color == "white" then
+ promoteFrom = inv:get_stack("board", to_idx)
+ if promoteFrom:get_name():sub(11,14) == "pawn" then
+ inv:set_stack("board", to_idx, pstr)
+ promoted = true
+ end
+ elseif color == "black" then
+ promoteFrom = inv:get_stack("board", to_idx)
+ if promoteFrom:get_name():sub(11,14) == "pawn" then
+ inv:set_stack("board", to_idx, pstr)
+ promoted = true
+ end
+ end
+ if promoted then
+ meta:set_string("promotionActive", "")
+ meta:set_int("promotionPawnFromIdx", 0)
+ meta:set_int("promotionPawnToIdx", 0)
+ realchess.update_state(meta, from_idx, to_idx, color, promoteFrom:get_name(), pstr)
+ update_game_result(meta, color)
+ update_formspec(meta)
+
+ local botColor = meta:get_string("botColor")
+ if botColor == "" then botColor = "black" end
+ local lastMove = meta:get_string("lastMove")
+ if lastMove == "" then lastMove = "black" end
+
+ local mode = meta:get_string("mode")
+ local gameResult = meta:get_string("gameResult")
+ if (mode == "bot_vs_bot" or (mode == "single" and lastMove ~= botColor)) and gameResult == "" then
+ chessbot.move(inv, meta)
+ end
+ else
+ minetest.log("error", "[xdecor] Chess: Could not find pawn to promote!")
+ end
+end
+
+function realchess.blast(pos)
+ minetest.remove_node(pos)
+end
+
+local chessboarddef = {
+ description = S("Chess Board"),
+ drawtype = "nodebox",
+ paramtype = "light",
+ paramtype2 = "facedir",
+ inventory_image = "chessboard_top.png",
+ wield_image = "chessboard_top.png",
+ tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
+ use_texture_alpha = ALPHA_OPAQUE,
+ groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
+ sunlight_propagates = true,
+ on_rotate = screwdriver.rotate_simple,
+}
+if ENABLE_CHESS_GAMES then
+ -- Extend chess board node definition if chess games are enabled
+ chessboarddef._tt_help = S("Play a game of Chess against another player or the computer")
+ chessboarddef.on_blast = realchess.blast
+ chessboarddef.can_dig = realchess.can_dig
+ chessboarddef.on_construct = realchess.init
+ chessboarddef.on_receive_fields = realchess.fields
+ -- The move logic of Chess is here (at least for players)
+ chessboarddef.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, _, player)
+ -- Normally, the allow function is meant to just check if an inventory move
+ -- is allowed but instead we abuse it to detect where the player is *attempting*
+ -- to move pieces to.
+ -- This function may manipulate the inventory. This is a bit dirty
+ -- because this is not really what the allow function is meant to do.
+ local meta = minetest.get_meta(pos)
+ local playerName
+ if player and player:is_player() then
+ playerName = player:get_player_name()
+ else
+ playerName = ""
+ minetest.log("error", "[xdecor] Chess: An unknown player tried to move a piece in the chessboard inventory")
+ end
+ realchess.move(meta, from_list, from_index, to_list, to_index, playerName)
+ -- We always return 0 to disable all *builtin* inventory moves, since
+ -- we do it ourselves. This should be fine because there shouldn't be a
+ -- conflict between this mod and Luanti then.
+ return 0
+ end
+ chessboarddef.allow_metadata_inventory_take = function() return 0 end
+ chessboarddef.allow_metadata_inventory_put = function() return 0 end
+ -- Note: There is no on_move function because we put the entire move handling
+ -- into the allow function above. The reason for this is of Luanti's
+ -- awkward behavior when swapping items.
+
+ minetest.register_lbm({
+ label = "Re-initialize chessboard (enable Chess games)",
+ name = "xdecor:chessboard_reinit",
+ nodenames = {"realchess:chessboard"},
+ run_at_every_load = true,
+ action = function(pos, node)
+ -- Init chessboard only if neccessary
+ local meta = minetest.get_meta(pos)
+ if meta:get_string("formspec", "") then
+ realchess.init(pos)
+ end
+ end,
+ })
+else
+ minetest.register_lbm({
+ label = "Clear chessboard formspec+infotext+inventory (disable Chess games)",
+ name = "xdecor:chessboard_clear",
+ nodenames = {"realchess:chessboard"},
+ run_at_every_load = true,
+ action = function(pos, node)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", "")
+ meta:set_string("infotext", "")
+ local inv = meta:get_inventory()
+ inv:set_size("board", 0)
+ end,
+ })
+end
+minetest.register_node(":realchess:chessboard", chessboarddef)
+
+local function register_piece(name, white_desc, black_desc, count)
+ for _, color in pairs({"black", "white"}) do
+ if not count then
+ minetest.register_craftitem(":realchess:" .. name .. "_" .. color, {
+ description = (color == "black") and black_desc or white_desc,
+ inventory_image = name .. "_" .. color .. ".png",
+ stack_max = 1,
+ groups = {not_in_creative_inventory=1}
+ })
+ else
+ for i = 1, count do
+ minetest.register_craftitem(":realchess:" .. name .. "_" .. color .. "_" .. i, {
+ description = (color == "black") and black_desc or white_desc,
+ inventory_image = name .. "_" .. color .. ".png",
+ stack_max = 1,
+ groups = {not_in_creative_inventory=1}
+ })
+ end
+ end
+ end
+end
+
+register_piece("pawn", S("White Pawn"), S("Black Pawn"), 8)
+register_piece("rook", S("White Rook"), S("Black Rook"), 2)
+register_piece("knight", S("White Knight"), S("Black Knight"), 2)
+register_piece("bishop", S("White Bishop"), S("Black Bishop"), 2)
+register_piece("queen", S("White Queen"), S("Black Queen"))
+register_piece("king", S("White King"), S("Black King"))
+
+-- Recipes
+
+minetest.register_craft({
+ output = "realchess:chessboard",
+ recipe = {
+ {"dye:black", "dye:white", "dye:black"},
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}
+ }
+})
diff --git a/mods/xdecor/src/chessbot.lua b/mods/xdecor/src/chessbot.lua
new file mode 100644
index 00000000..6e0161c6
--- /dev/null
+++ b/mods/xdecor/src/chessbot.lua
@@ -0,0 +1,200 @@
+local chessbot = {}
+
+local realchess = xdecor.chess
+
+-- Delay in seconds for a bot moving a piece (excluding choosing a promotion)
+local BOT_DELAY_MOVE = 1.0
+-- Delay in seconds for a bot promoting a piece
+local BOT_DELAY_PROMOTE = 1.0
+
+local function best_move(moves)
+ local value, choices = 0, {}
+
+ for from, _ in pairs(moves) do
+ for to, val in pairs(_) do
+ if val > value then
+ value = val
+ choices = {{
+ from = from,
+ to = to
+ }}
+ elseif val == value then
+ choices[#choices + 1] = {
+ from = from,
+ to = to
+ }
+ end
+ end
+ end
+
+ if #choices == 0 then
+ return nil
+ end
+ local random = math.random(1, #choices)
+ local choice_from, choice_to = choices[random].from, choices[random].to
+
+ return tonumber(choice_from), choice_to
+end
+
+function chessbot.choose_move(board_t, meta_t)
+ local lastMove = meta_t["lastMove"]
+ local gameResult = meta_t["gameResult"]
+ local botColor = meta_t["botColor"]
+ local prevDoublePawnStepTo = meta_t["prevDoublePawnStepTo"]
+ local castlingRights = {
+ castlingWhiteR = meta_t["castlingWhiteR"],
+ castlingWhiteL = meta_t["castlingWhiteL"],
+ castlingBlackR = meta_t["castlingBlackR"],
+ castlingBlackL = meta_t["castlingBlackL"],
+ }
+
+ if botColor == "" then
+ return
+ end
+ local currentBotColor, opponentColor
+ if botColor == "black" then
+ currentBotColor = "black"
+ opponentColor = "white"
+ elseif botColor == "white" then
+ currentBotColor = "white"
+ opponentColor = "black"
+ elseif botColor == "both" then
+ opponentColor = lastMove
+ if lastMove == "black" or lastMove == "" then
+ currentBotColor = "white"
+ else
+ currentBotColor = "black"
+ end
+ end
+ if (lastMove == opponentColor or ((botColor == "white" or botColor == "both") and lastMove == "")) and gameResult == "" then
+
+ local moves = realchess.get_theoretical_moves_for(board_t, currentBotColor, prevDoublePawnStepTo, castlingRights)
+ local safe_moves, safe_moves_count = realchess.get_king_safe_moves(moves, board_t, currentBotColor)
+ if safe_moves_count == 0 then
+ -- No safe move: stalemate or checkmate
+ end
+ local choice_from, choice_to = best_move(safe_moves)
+ if choice_from == nil then
+ -- No best move: stalemate or checkmate
+ return
+ end
+
+ return choice_from, choice_to
+ else
+ minetest.log("error", "[xdecor] Chess: chessbot.choose_move was apparently called in an invalid game state!")
+ return
+ end
+end
+
+chessbot.perform_move = function(choice_from, choice_to, meta)
+ local lastMove = meta:get_string("lastMove")
+ local botColor = meta:get_string("botColor")
+ local currentBotColor, opponentColor
+ local botName
+ if botColor == "black" then
+ currentBotColor = "black"
+ opponentColor = "white"
+ elseif botColor == "white" then
+ currentBotColor = "white"
+ opponentColor = "black"
+ elseif botColor == "both" then
+ opponentColor = lastMove
+ if lastMove == "black" or lastMove == "" then
+ currentBotColor = "white"
+ else
+ currentBotColor = "black"
+ end
+ end
+
+ -- Bot resigns if no move chosen
+ if not choice_from or not choice_to then
+ realchess.resign(meta, currentBotColor)
+ return
+ end
+
+ if currentBotColor == "white" then
+ botName = meta:get_string("playerWhite")
+ else
+ botName = meta:get_string("playerBlack")
+ end
+
+ local gameResult = meta:get_string("gameResult")
+ if gameResult ~= "" then
+ return
+ end
+ local botColor = meta:get_string("botColor")
+ if botColor == "" then
+ minetest.log("error", "[xdecor] Chess: chessbot.perform_move: botColor in meta string was empty!")
+ return
+ end
+ local lastMove = meta:get_string("lastMove")
+ local lastMoveTime = meta:get_int("lastMoveTime")
+ if lastMoveTime > 0 or lastMove == "" then
+ -- Set the bot name if not set already
+ if currentBotColor == "black" and meta:get_string("playerBlack") == "" then
+ meta:set_string("playerBlack", botName)
+ elseif currentBotColor == "white" and meta:get_string("playerWhite") == "" then
+ meta:set_string("playerWhite", botName)
+ end
+
+ -- Make a move
+ local moveOK = realchess.move(meta, "board", choice_from, "board", choice_to, botName)
+ if not moveOK then
+ minetest.log("error", "[xdecor] Chess: Bot tried to make an invalid move from "..
+ realchess.index_to_notation(choice_from).." to "..realchess.index_to_notation(choice_to))
+ end
+ -- Bot resigns if it tried to make an invalid move
+ if not moveOK then
+ realchess.resign(meta, currentBotColor)
+ end
+ else
+ minetest.log("error", "[xdecor] Chess: chessbot.perform_move: No last move!")
+ end
+end
+
+function chessbot.choose_promote(board_t, pawnIndex)
+ -- Bot always promotes to queen
+ return "queen"
+end
+
+function chessbot.perform_promote(meta, promoteTo)
+ minetest.after(BOT_DELAY_PROMOTE, function()
+ local lastMove = meta:get_string("lastMove")
+ local color
+ if lastMove == "black" or lastMove == "" then
+ color = "white"
+ else
+ color = "black"
+ end
+ realchess.promote_pawn(meta, color, promoteTo)
+ end)
+end
+
+function chessbot.move(inv, meta)
+ local board_t = realchess.board_to_table(inv)
+ local meta_t = {
+ lastMove = meta:get_string("lastMove"),
+ gameResult = meta:get_string("gameResult"),
+ botColor = meta:get_string("botColor"),
+ prevDoublePawnStepTo = meta:get_int("prevDoublePawnStepTo"),
+ castlingWhiteL = meta:get_int("castlingWhiteL"),
+ castlingWhiteR = meta:get_int("castlingWhiteR"),
+ castlingBlackL = meta:get_int("castlingBlackL"),
+ castlingBlackR = meta:get_int("castlingBlackR"),
+ }
+ local choice_from, choice_to = chessbot.choose_move(board_t, meta_t)
+ minetest.after(BOT_DELAY_MOVE, function()
+ chessbot.perform_move(choice_from, choice_to, meta)
+ end)
+end
+
+function chessbot.promote(inv, meta, pawnIndex)
+ local board_t = realchess.board_to_table(inv)
+ local promoteTo = chessbot.choose_promote(board_t, pawnIndex)
+ if not promoteTo then
+ promoteTo = "queen"
+ end
+ chessbot.perform_promote(meta, promoteTo)
+end
+
+return chessbot
diff --git a/mods/xdecor/src/cooking.lua b/mods/xdecor/src/cooking.lua
new file mode 100644
index 00000000..ff1d62d4
--- /dev/null
+++ b/mods/xdecor/src/cooking.lua
@@ -0,0 +1,517 @@
+local cauldron, sounds = {}, {}
+local S = minetest.get_translator("xdecor")
+
+-- Set to true to print soup ingredients and fire nodes to console
+local DEBUG_RECOGNIZED_ITEMS = false
+
+--~ cauldron hint
+local hint_fire = S("Light a fire below to heat it up")
+--~ cauldron hint
+local hint_eat = S("Use a bowl to eat the soup")
+--~ cauldron hint
+local hint_recipe = S("Drop foods inside to make a soup")
+
+local infotexts = {
+ ["xdecor:cauldron_empty"] = S("Cauldron (empty)"),
+ ["xdecor:cauldron_idle"] = S("Cauldron (cold water)").."\n"..hint_fire,
+ ["xdecor:cauldron_idle_river_water"] = S("Cauldron (cold river water)").."\n"..hint_fire,
+ ["xdecor:cauldron_idle_soup"] = S("Cauldron (cold soup)").."\n"..hint_eat,
+ ["xdecor:cauldron_boiling"] = S("Cauldron (boiling water)").."\n"..hint_recipe,
+ ["xdecor:cauldron_boiling_river_water"] = S("Cauldron (boiling river water)").."\n"..hint_recipe,
+ ["xdecor:cauldron_soup"] = S("Cauldron (boiling soup)").."\n"..hint_eat,
+}
+
+local function set_infotext(meta, node)
+ if infotexts[node.name] then
+ meta:set_string("infotext", infotexts[node.name])
+ end
+end
+
+-- HACKY list of soup ingredients.
+-- The cauldron will check if any of these strings are contained in the itemname
+-- after the ":".
+local ingredients_list = {
+ "apple", "mushroom", "honey", "pumpkin", "egg", "bread", "meat",
+ "chicken", "carrot", "potato", "melon", "rhubarb", "cucumber",
+ "corn", "beans", "berries", "grapes", "tomato", "wheat"
+}
+
+-- List of items that can never be soup ingredients. Overwrites anything else.
+local non_ingredients = {
+ -- xdecor
+ "xdecor:bowl_soup",
+ -- Minetest Game: default
+ "default:apple_mark", "default:blueberry_bush_leaves_with_berries",
+ -- Minetest Game: farming
+ "farming:seed_wheat",
+ "farming:wheat_1", "farming:wheat_2", "farming:wheat_3", "farming:wheat_4",
+ "farming:wheat_5", "farming:wheat_6", "farming:wheat_7", "farming:wheat_8",
+}
+local non_ingredients_keyed = table.key_value_swap(non_ingredients)
+
+cauldron.cbox = {
+ {0, 0, 0, 16, 16, 0},
+ {0, 0, 16, 16, 16, 0},
+ {0, 0, 0, 0, 16, 16},
+ {16, 0, 0, 0, 16, 16},
+ {0, 0, 0, 16, 8, 16}
+}
+
+-- Returns true is given item is a fire
+local function is_fire(itemstring)
+ return minetest.get_item_group(itemstring, "fire") ~= 0
+end
+
+-- Returns true if the node at pos is above fire
+local function is_heated(pos)
+ local below_node = {x = pos.x, y = pos.y - 1, z = pos.z}
+ local nn = minetest.get_node(below_node).name
+ -- Check fire group
+ if is_fire(nn) then
+ return true
+ else
+ return false
+ end
+end
+
+function cauldron.stop_sound(pos)
+ local spos = minetest.hash_node_position(pos)
+ if sounds[spos] then
+ minetest.sound_stop(sounds[spos])
+ sounds[spos] = nil
+ end
+end
+
+function cauldron.start_sound(pos)
+ local spos = minetest.hash_node_position(pos)
+ -- Stop sound if one already exists.
+ -- Only 1 sound per position at maximum allowed.
+ if sounds[spos] then
+ cauldron.stop_sound(pos)
+ end
+ sounds[spos] = minetest.sound_play("xdecor_boiling_water", {
+ pos = pos,
+ max_hear_distance = 5,
+ gain = 0.8,
+ loop = true
+ })
+end
+
+function cauldron.idle_construct(pos)
+ local timer = minetest.get_node_timer(pos)
+ local node = minetest.get_node(pos)
+ local meta = minetest.get_meta(pos)
+ set_infotext(meta, node)
+ timer:start(10.0)
+ cauldron.stop_sound(pos)
+end
+
+function cauldron.boiling_construct(pos)
+ cauldron.start_sound(pos)
+
+ local meta = minetest.get_meta(pos)
+ local node = minetest.get_node(pos)
+ set_infotext(meta, node)
+
+ local timer = minetest.get_node_timer(pos)
+ timer:start(5.0)
+end
+
+
+function cauldron.filling(pos, node, clicker, itemstack)
+ local inv = clicker:get_inventory()
+ local wield_item = clicker:get_wielded_item():get_name()
+
+ do
+ if wield_item == "bucket:bucket_empty" and node.name:sub(-6) ~= "_empty" then
+ local bucket_item
+ if node.name:sub(-11) == "river_water" then
+ bucket_item = "bucket:bucket_river_water 1"
+ else
+ bucket_item = "bucket:bucket_water 1"
+ end
+ if itemstack:get_count() > 1 then
+ if inv:room_for_item("main", bucket_item) then
+ itemstack:take_item()
+ inv:add_item("main", bucket_item)
+ else
+ minetest.chat_send_player(clicker:get_player_name(),
+ S("No room in your inventory to add a bucket of water."))
+ return itemstack
+ end
+ else
+ itemstack:replace(bucket_item)
+ end
+ minetest.set_node(pos, {name = "xdecor:cauldron_empty", param2 = node.param2})
+
+ elseif minetest.get_item_group(wield_item, "water_bucket") == 1 and node.name:sub(-6) == "_empty" then
+ local newnode
+ if wield_item == "bucket:bucket_river_water" then
+ newnode = "xdecor:cauldron_idle_river_water"
+ else
+ newnode = "xdecor:cauldron_idle"
+ end
+ minetest.set_node(pos, {name = newnode, param2 = node.param2})
+ itemstack:replace("bucket:bucket_empty")
+ end
+
+ return itemstack
+ end
+end
+
+function cauldron.idle_timer(pos)
+ if not is_heated(pos) then
+ return true
+ end
+
+ local node = minetest.get_node(pos)
+ if node.name:sub(-4) == "soup" then
+ node.name = "xdecor:cauldron_soup"
+ elseif node.name:sub(-11) == "river_water" then
+ node.name = "xdecor:cauldron_boiling_river_water"
+ else
+ node.name = "xdecor:cauldron_boiling"
+ end
+ minetest.set_node(pos, node)
+ return true
+end
+
+-- Ugly hack to determine if an item has the function `minetest.item_eat` in its definition.
+local function eatable(itemstring)
+ local item = itemstring:match("[%w_:]+")
+ local on_use_def = minetest.registered_items[item].on_use
+ if not on_use_def then return end
+
+ return string.format("%q", string.dump(on_use_def)):find("item_eat")
+end
+
+-- Checks if the given item can be used as ingredient for the soup
+local function is_ingredient(itemstring)
+ if non_ingredients_keyed[itemstring] then
+ return false
+ end
+ local basename = itemstring:match(":([%w_]+)")
+ if not basename then
+ return false
+ end
+ for _, ingredient in ipairs(ingredients_list) do
+ if eatable(itemstring) or basename:find(ingredient) then
+ return true
+ end
+ end
+ return false
+end
+
+function cauldron.boiling_timer(pos)
+ -- Cool down cauldron if there is no fire
+ local node = minetest.get_node(pos)
+ if not is_heated(pos) then
+ local newnode
+ if node.name:sub(-4) == "soup" then
+ newnode = "xdecor:cauldron_idle_soup"
+ elseif node.name:sub(-11) == "river_water" then
+ newnode = "xdecor:cauldron_idle_river_water"
+ else
+ newnode = "xdecor:cauldron_idle"
+ end
+ minetest.set_node(pos, {name = newnode, param2 = node.param2})
+ return true
+ end
+
+ if node.name:sub(-4) == "soup" then
+ return true
+ end
+
+ -- Cooking:
+
+ -- Count the ingredients in the cauldron
+ local objs = minetest.get_objects_inside_radius(pos, 0.5)
+
+ if not next(objs) then
+ return true
+ end
+
+ local ingredients = {}
+ for _, obj in pairs(objs) do
+ if obj and not obj:is_player() and obj:get_luaentity().itemstring then
+ local itemstring = obj:get_luaentity().itemstring
+ local item = ItemStack(itemstring)
+ local itemname = item:get_name()
+
+ if is_ingredient(itemname) then
+ local basename = itemstring:match(":([%w_]+)")
+ table.insert(ingredients, basename)
+ end
+ end
+ end
+
+ -- Remove ingredients and turn liquid into soup
+ if #ingredients >= 2 then
+ for _, obj in pairs(objs) do
+ obj:remove()
+ end
+
+ minetest.set_node(pos, {name = "xdecor:cauldron_soup", param2 = node.param2})
+ end
+
+
+ return true
+end
+
+function cauldron.take_soup(pos, node, clicker, itemstack)
+ local inv = clicker:get_inventory()
+ local wield_item = clicker:get_wielded_item()
+ local item_name = wield_item:get_name()
+
+ if item_name == "xdecor:bowl" or item_name == "farming:bowl" then
+ if wield_item:get_count() > 1 then
+ if inv:room_for_item("main", "xdecor:bowl_soup 1") then
+ itemstack:take_item()
+ inv:add_item("main", "xdecor:bowl_soup 1")
+ else
+ minetest.chat_send_player(clicker:get_player_name(),
+ S("No room in your inventory to add a bowl of soup."))
+ return itemstack
+ end
+ else
+ itemstack:replace("xdecor:bowl_soup 1")
+ end
+
+ minetest.set_node(pos, {name = "xdecor:cauldron_empty", param2 = node.param2})
+ end
+
+ return itemstack
+end
+
+xdecor.register("cauldron_empty", {
+ description = S("Cauldron"),
+ _tt_help = S("For storing water and cooking soup"),
+ groups = {cracky=2, oddly_breakable_by_hand=1,cauldron=1},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {"xdecor_cauldron_top_empty.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
+ sounds = default.node_sound_metal_defaults(),
+ collision_box = xdecor.pixelbox(16, cauldron.cbox),
+ on_rightclick = cauldron.filling,
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ local node = minetest.get_node(pos)
+ set_infotext(meta, node)
+ cauldron.stop_sound(pos)
+ end,
+})
+
+xdecor.register("cauldron_idle", {
+ description = S("Cauldron with Water (cold)"),
+ groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=2},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {"xdecor_cauldron_top_idle.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
+ sounds = default.node_sound_metal_defaults(),
+ drop = "xdecor:cauldron_empty",
+ collision_box = xdecor.pixelbox(16, cauldron.cbox),
+ on_rightclick = cauldron.filling,
+ on_construct = cauldron.idle_construct,
+ on_timer = cauldron.idle_timer,
+})
+
+xdecor.register("cauldron_idle_river_water", {
+ description = S("Cauldron with River Water (cold)"),
+ groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=2},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {"xdecor_cauldron_top_idle_river_water.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
+ sounds = default.node_sound_metal_defaults(),
+ drop = "xdecor:cauldron_empty",
+ collision_box = xdecor.pixelbox(16, cauldron.cbox),
+ on_rightclick = cauldron.filling,
+ on_construct = cauldron.idle_construct,
+ on_timer = cauldron.idle_timer,
+})
+
+xdecor.register("cauldron_idle_soup", {
+ description = S("Cauldron with Soup (cold)"),
+ groups = {cracky = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1,cauldron=2},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ drop = "xdecor:cauldron_empty",
+ tiles = {"xdecor_cauldron_top_idle_soup.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
+ sounds = default.node_sound_metal_defaults(),
+ collision_box = xdecor.pixelbox(16, cauldron.cbox),
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ local node = minetest.get_node(pos)
+ set_infotext(meta, node)
+ local timer = minetest.get_node_timer(pos)
+ timer:start(10.0)
+ cauldron.stop_sound(pos)
+ end,
+ on_timer = cauldron.idle_timer,
+ on_rightclick = cauldron.take_soup,
+})
+
+xdecor.register("cauldron_boiling", {
+ description = S("Cauldron with Water (boiling)"),
+ groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=3},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ drop = "xdecor:cauldron_empty",
+ damage_per_second = 2,
+ tiles = {
+ {
+ name = "xdecor_cauldron_top_anim_boiling_water.png",
+ animation = {type = "vertical_frames", length = 3.0}
+ },
+ "xdecor_cauldron_bottom.png",
+ "xdecor_cauldron_sides.png"
+ },
+ sounds = default.node_sound_metal_defaults(),
+ collision_box = xdecor.pixelbox(16, cauldron.cbox),
+ on_rightclick = cauldron.filling,
+ on_construct = cauldron.boiling_construct,
+ on_timer = cauldron.boiling_timer,
+ on_destruct = function(pos)
+ cauldron.stop_sound(pos)
+ end,
+})
+
+xdecor.register("cauldron_boiling_river_water", {
+ description = S("Cauldron with River Water (boiling)"),
+ groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=3},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ drop = "xdecor:cauldron_empty",
+ damage_per_second = 2,
+ tiles = {
+ {
+ name = "xdecor_cauldron_top_anim_boiling_river_water.png",
+ animation = {type = "vertical_frames", length = 3.0}
+ },
+ "xdecor_cauldron_bottom.png",
+ "xdecor_cauldron_sides.png"
+ },
+ sounds = default.node_sound_metal_defaults(),
+ collision_box = xdecor.pixelbox(16, cauldron.cbox),
+ on_rightclick = cauldron.filling,
+ on_construct = cauldron.boiling_construct,
+ on_timer = cauldron.boiling_timer,
+ on_destruct = function(pos)
+ cauldron.stop_sound(pos)
+ end,
+})
+
+
+
+xdecor.register("cauldron_soup", {
+ description = S("Cauldron with Soup (boiling)"),
+ groups = {cracky = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1,cauldron=3},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ drop = "xdecor:cauldron_empty",
+ damage_per_second = 2,
+ tiles = {
+ {
+ name = "xdecor_cauldron_top_anim_soup.png",
+ animation = {type = "vertical_frames", length = 3.0}
+ },
+ "xdecor_cauldron_bottom.png",
+ "xdecor_cauldron_sides.png"
+ },
+ sounds = default.node_sound_metal_defaults(),
+ collision_box = xdecor.pixelbox(16, cauldron.cbox),
+ on_construct = function(pos)
+ cauldron.start_sound(pos)
+ local meta = minetest.get_meta(pos)
+ local node = minetest.get_node(pos)
+ set_infotext(meta, node)
+
+ local timer = minetest.get_node_timer(pos)
+ timer:start(5.0)
+ end,
+ on_timer = cauldron.boiling_timer,
+ on_rightclick = cauldron.take_soup,
+ on_destruct = function(pos)
+ cauldron.stop_sound(pos)
+ end,
+})
+
+-- Craft items
+
+minetest.register_craftitem("xdecor:bowl", {
+ description = S("Bowl"),
+ inventory_image = "xdecor_bowl.png",
+ wield_image = "xdecor_bowl.png",
+ groups = {food_bowl = 1, flammable = 2},
+})
+
+minetest.register_craftitem("xdecor:bowl_soup", {
+ description = S("Bowl of soup"),
+ inventory_image = "xdecor_bowl_soup.png",
+ wield_image = "xdecor_bowl_soup.png",
+ groups = {},
+ stack_max = 1,
+ on_use = minetest.item_eat(30, "xdecor:bowl")
+})
+
+-- Recipes
+
+minetest.register_craft({
+ output = "xdecor:bowl 3",
+ recipe = {
+ {"group:wood", "", "group:wood"},
+ {"", "group:wood", ""}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:cauldron_empty",
+ recipe = {
+ {"default:iron_lump", "", "default:iron_lump"},
+ {"default:iron_lump", "", "default:iron_lump"},
+ {"default:iron_lump", "default:iron_lump", "default:iron_lump"}
+ }
+})
+
+minetest.register_lbm({
+ label = "Restart boiling cauldron sounds",
+ name = "xdecor:restart_boiling_cauldron_sounds",
+ nodenames = {"xdecor:cauldron_boiling", "xdecor:cauldron_boiling_river_water", "xdecor:cauldron_soup"},
+ run_at_every_load = true,
+ action = function(pos, node)
+ cauldron.start_sound(pos)
+ end,
+})
+
+minetest.register_lbm({
+ label = "Update cauldron infotexts",
+ name = "xdecor:update_cauldron_infotexts",
+ nodenames = {"group:cauldron"},
+ run_at_every_load = false,
+ action = function(pos, node)
+ local meta = minetest.get_meta(pos)
+ set_infotext(meta, node)
+ end,
+})
+
+if DEBUG_RECOGNIZED_ITEMS then
+ -- Print all soup ingredients and fire nodes
+ -- in console
+ minetest.register_on_mods_loaded(function()
+ local ingredients = {}
+ local fires = {}
+ for k,v in pairs(minetest.registered_items) do
+ if is_ingredient(k) then
+ table.insert(ingredients, k)
+ end
+ if is_fire(k) then
+ table.insert(fires, k)
+ end
+ end
+ table.sort(ingredients)
+ table.sort(fires)
+ local str_i = table.concat(ingredients, ", ")
+ local str_f = table.concat(fires, ", ")
+ print("[xdecor] List of ingredients for soup: "..str_i)
+ print("[xdecor] List of nodes that can heat cauldron: "..str_f)
+ end)
+end
diff --git a/mods/xdecor/src/enchanted_tools.lua b/mods/xdecor/src/enchanted_tools.lua
new file mode 100644
index 00000000..dc7df6ae
--- /dev/null
+++ b/mods/xdecor/src/enchanted_tools.lua
@@ -0,0 +1,115 @@
+-- Register enchanted tools.
+
+local S = minetest.get_translator("xdecor")
+
+-- Number of uses for the (normal) steel hoe from Minetest Game (as of 01/12/20224)
+-- This is technically redundant because we cannot access that number
+-- directly, but it's unlikely to change in future because Minetest Game is
+-- unlikely to change.
+local STEEL_HOE_USES = 500
+
+-- Modifier of the steel hoe uses for the enchanted steel hoe
+local STEEL_HOE_USES_MODIFIER = 2.2
+
+-- Modifier of the bug net uses for the enchanted bug net
+local BUG_NET_USES_MODIFIER = 4
+
+-- Multiplies by much faster the fast hammer repairs
+local HAMMER_FAST_MODIFIER = 1.3
+
+-- Reduces the wear taken by the hammer for a single repair step
+-- (absolute value)
+local HAMMER_DURABLE_MODIFIER = 100
+
+-- Register enchantments for default tools from Minetest Game
+local materials = {"steel", "bronze", "mese", "diamond"}
+local tooltypes = {
+ { "axe", { "durable", "fast" }, "choppy" },
+ { "pick", { "durable", "fast" }, "cracky" },
+ { "shovel", { "durable", "fast" }, "crumbly" },
+ { "sword", { "sharp" }, nil },
+}
+for t=1, #tooltypes do
+for m=1, #materials do
+ local tooltype = tooltypes[t][1]
+ local enchants = tooltypes[t][2]
+ local dig_group = tooltypes[t][3]
+ local material = materials[m]
+ xdecor.register_enchantable_tool("default:"..tooltype.."_"..material, {
+ enchants = enchants,
+ dig_group = dig_group,
+ })
+end
+end
+
+-- Register enchantment for bug net
+xdecor.register_enchantable_tool("fireflies:bug_net", {
+ enchants = { "durable" },
+ dig_group = "catchable",
+ bonuses = {
+ uses = BUG_NET_USES_MODIFIER,
+ }
+})
+
+-- Register enchanted steel hoe (more durability)
+if farming.register_hoe then
+ local percent = math.round((STEEL_HOE_USES_MODIFIER - 1) * 100)
+ local hitem = ItemStack("farming:hoe_steel")
+ local hdesc = hitem:get_short_description() or "farming:hoe_steel"
+ local ehdesc, ehsdesc = xdecor.enchant_description(hdesc, "durable", percent)
+ farming.register_hoe(":farming:enchanted_hoe_steel_durable", {
+ description = ehdesc,
+ short_description = ehsdesc,
+ inventory_image = xdecor.enchant_texture("farming_tool_steelhoe.png"),
+ max_uses = STEEL_HOE_USES * STEEL_HOE_USES_MODIFIER,
+ groups = {hoe = 1, not_in_creative_inventory = 1}
+ })
+
+ xdecor.register_custom_enchantable_tool("farming:hoe_steel", {
+ durable = "farming:enchanted_hoe_steel_durable",
+ })
+end
+
+-- Register enchanted hammer (more durbility and efficiency)
+local hammerdef = minetest.registered_items["xdecor:hammer"]
+if hammerdef then
+ local hitem = ItemStack("xdecor:hammer")
+ local hdesc = hitem:get_short_description() or "xdecor:hammer"
+ local repair = hammerdef._xdecor_hammer_repair
+ local repair_cost = hammerdef._xdecor_hammer_repair_cost
+
+ -- Durable hammer (reduces wear taken by each repair step)
+ local d_repair_cost_modified = repair_cost - HAMMER_DURABLE_MODIFIER
+ local d_percent = math.round(100 - d_repair_cost_modified/repair_cost * 100)
+ local d_ehdesc, d_ehsdesc = xdecor.enchant_description(hdesc, "durable", d_percent)
+
+ xdecor.register_hammer("xdecor:enchanted_hammer_durable", {
+ description = d_ehdesc,
+ short_description = d_ehsdesc,
+ image = xdecor.enchant_texture("xdecor_hammer.png"),
+ repair_cost = d_repair_cost_modified,
+ groups = {repair_hammer = 1, not_in_creative_inventory = 1}
+ })
+
+ -- Fast hammer (increases both repair amount and repair cost per
+ -- repair step by an equal amount)
+ local f_repair_modified = math.round(repair * HAMMER_FAST_MODIFIER)
+ local repair_diff = f_repair_modified - repair
+ local f_repair_cost_modified = repair_cost + repair_diff
+ local f_percent = math.round(HAMMER_FAST_MODIFIER * 100 - 100)
+ local f_ehdesc, f_ehsdesc = xdecor.enchant_description(hdesc, "fast", f_percent)
+
+ xdecor.register_hammer("xdecor:enchanted_hammer_fast", {
+ description = f_ehdesc,
+ short_description = f_ehsdesc,
+ image = xdecor.enchant_texture("xdecor_hammer.png"),
+ repair = f_repair_modified,
+ repair_cost = f_repair_cost_modified,
+ groups = {repair_hammer = 1, not_in_creative_inventory = 1}
+ })
+
+ xdecor.register_custom_enchantable_tool("xdecor:hammer", {
+ durable = "xdecor:enchanted_hammer_durable",
+ fast = "xdecor:enchanted_hammer_fast",
+ })
+end
diff --git a/mods/xdecor/src/enchanting.lua b/mods/xdecor/src/enchanting.lua
new file mode 100644
index 00000000..670a326c
--- /dev/null
+++ b/mods/xdecor/src/enchanting.lua
@@ -0,0 +1,506 @@
+local enchanting = {}
+
+screwdriver = screwdriver or {}
+local S = minetest.get_translator("xdecor")
+local NS = function(s) return s end
+local FS = function(...) return minetest.formspec_escape(S(...)) end
+local ceil, abs, random = math.ceil, math.abs, math.random
+local reg_tools = minetest.registered_tools
+local reg_enchantable_tools = {}
+local available_tool_enchants = {}
+
+-- Cost in Mese crystal(s) for enchanting.
+local MESE_COST = 1
+
+-- Default strenth of the enchantments
+local DEFAULT_ENCHANTING_USES = 1.2 -- Durability
+local DEFAULT_ENCHANTING_TIMES = 0.1 -- Efficiency
+local DEFAULT_ENCHANTING_DAMAGES = 1 -- Sharpness
+
+local function to_percent(orig_value, final_value)
+ return abs(ceil(((final_value - orig_value) / orig_value) * 100))
+end
+
+function enchanting:get_tooltip_raw(enchant, percent)
+ local specs = {
+ durable = "#00baff",
+ fast = "#74ff49",
+ sharp = "#ffff00",
+ }
+ local enchant_loc = {
+ --~ Enchantment
+ fast = S("Efficiency"),
+ --~ Enchantment
+ durable = S("Durability"),
+ --~ Enchantment
+ sharp = S("Sharpness"),
+ }
+
+ if minetest.colorize then
+ --~ Tooltip in format " (+%)", e.g. "Efficiency (+5%)"
+ return minetest.colorize(specs[enchant], S("@1 (+@2%)", enchant_loc[enchant], percent))
+ else
+ return S("@1 (+@2%)", enchant_loc[enchant], percent)
+ end
+
+end
+
+function enchanting:get_tooltip(enchant, orig_caps, fleshy, bonus_defs)
+ local bonus = {durable = 0, efficiency = 0, damages = 0}
+
+ if orig_caps then
+ bonus.durable = to_percent(orig_caps.uses, orig_caps.uses * bonus_defs.uses)
+ local sum_caps_times = 0
+ for i=1, #orig_caps.times do
+ sum_caps_times = sum_caps_times + orig_caps.times[i]
+ end
+ local average_caps_time = sum_caps_times / #orig_caps.times
+ bonus.efficiency = to_percent(average_caps_time, average_caps_time -
+ bonus_defs.times)
+ end
+
+ if fleshy then
+ bonus.damages = to_percent(fleshy, fleshy + bonus_defs.damages)
+ end
+
+ local specs = {
+ durable = bonus.durable,
+ fast = bonus.efficiency,
+ sharp = bonus.damages,
+ }
+ local percent = specs[enchant]
+ return enchanting:get_tooltip_raw(enchant, percent)
+end
+
+local enchant_buttons = {
+ fast = "image_button[3.6,0.67;4.75,0.85;bg_btn.png;fast;"..FS("Efficiency").."]",
+ durable = "image_button[3.6,1.65;4.75,1.05;bg_btn.png;durable;"..FS("Durability").."]",
+ sharp = "image_button[3.6,2.8;4.75,0.85;bg_btn.png;sharp;"..FS("Sharpness").."]",
+}
+
+function enchanting.formspec(pos, enchants)
+ local meta = minetest.get_meta(pos)
+ local formspec = [[
+ size[9,8.6;]
+ no_prepend[]
+ bgcolor[#080808BB;true]
+ listcolors[#00000069;#5A5A5A;#141318;#30434C;#FFF]
+ background9[0,0;9,9;ench_ui.png;6]
+ list[context;tool;0.9,2.9;1,1;]
+ list[context;mese;2,2.9;1,1;]
+ list[current_player;main;0.55,4.5;8,4;]
+ listring[current_player;main]
+ listring[context;tool]
+ listring[current_player;main]
+ listring[context;mese]
+ image[2,2.9;1,1;mese_layout.png]
+ ]]
+ --~ Sharpness enchantment
+ .."tooltip[sharp;"..FS("Your weapon inflicts more damage").."]"
+ --~ Durability enchantment
+ .."tooltip[durable;"..FS("Your tool lasts longer").."]"
+ --~ Efficiency enchantment
+ .."tooltip[fast;"..FS("Your tool digs faster").."]"
+ ..default.gui_slots .. default.get_hotbar_bg(0.55, 4.5)
+
+ if enchants then
+ for e=1, #enchants do
+ formspec = formspec .. enchant_buttons[enchants[e]]
+ end
+ end
+ meta:set_string("formspec", formspec)
+end
+
+function enchanting.on_put(pos, listname, _, stack)
+ if listname == "tool" then
+ local stackname = stack:get_name()
+ local enchants = available_tool_enchants[stackname]
+ if enchants then
+ enchanting.formspec(pos, enchants)
+ end
+ end
+end
+
+function enchanting.fields(pos, _, fields, sender)
+ if not next(fields) or fields.quit then return end
+ local inv = minetest.get_meta(pos):get_inventory()
+ local tool = inv:get_stack("tool", 1)
+ local mese = inv:get_stack("mese", 1)
+ local orig_wear = tool:get_wear()
+ local mod, name = tool:get_name():match("(.*):(.*)")
+ local enchanted_tool = (mod or "") .. ":enchanted_" .. (name or "") .. "_" .. next(fields)
+
+ if mese:get_count() >= MESE_COST and reg_tools[enchanted_tool] then
+ minetest.sound_play("xdecor_enchanting", {
+ to_player = sender:get_player_name(),
+ gain = 0.8
+ })
+
+ tool:replace(enchanted_tool)
+ tool:add_wear(orig_wear)
+ mese:take_item(MESE_COST)
+ inv:set_stack("mese", 1, mese)
+ inv:set_stack("tool", 1, tool)
+ end
+end
+
+function enchanting.dig(pos)
+ local inv = minetest.get_meta(pos):get_inventory()
+ return inv:is_empty("tool") and inv:is_empty("mese")
+end
+
+function enchanting.blast(pos)
+ local drops = xdecor.get_inventory_drops(pos, {"tool", "mese"})
+ minetest.remove_node(pos)
+ return drops
+end
+
+local function allowed(tool)
+ if not tool then
+ return false
+ end
+ if reg_enchantable_tools[tool] then
+ return true
+ else
+ return false
+ end
+end
+
+function enchanting.put(_, listname, _, stack)
+ local stackname = stack:get_name()
+ if listname == "mese" and (stackname == "default:mese_crystal" or
+ stackname == "imese:industrial_mese_crystal") then
+ return stack:get_count()
+ elseif listname == "tool" and allowed(stackname) then
+ return 1
+ end
+
+ return 0
+end
+
+function enchanting.on_take(pos, listname)
+ if listname == "tool" then
+ enchanting.formspec(pos)
+ end
+end
+
+function enchanting.construct(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", S("Enchantment Table"))
+ enchanting.formspec(pos)
+
+ local inv = meta:get_inventory()
+ inv:set_size("tool", 1)
+ inv:set_size("mese", 1)
+
+ minetest.add_entity({x = pos.x, y = pos.y + 0.85, z = pos.z}, "xdecor:book_open")
+ local timer = minetest.get_node_timer(pos)
+ timer:start(0.5)
+end
+
+function enchanting.destruct(pos)
+ for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.9)) do
+ if obj and obj:get_luaentity() and
+ obj:get_luaentity().name == "xdecor:book_open" then
+ obj:remove()
+ break
+ end
+ end
+end
+
+function enchanting.timer(pos)
+ local minp = {x = pos.x - 2, y = pos.y, z = pos.z - 2}
+ local maxp = {x = pos.x + 2, y = pos.y + 1, z = pos.z + 2}
+
+ local bookshelves = minetest.find_nodes_in_area(minp, maxp, "default:bookshelf")
+ if #bookshelves == 0 then
+ return true
+ end
+
+ local bookshelf_pos = bookshelves[random(1, #bookshelves)]
+ local x = pos.x - bookshelf_pos.x
+ local y = bookshelf_pos.y - pos.y
+ local z = pos.z - bookshelf_pos.z
+
+ if tostring(x .. z):find(2) then
+ minetest.add_particle({
+ pos = bookshelf_pos,
+ velocity = {x = x, y = 2 - y, z = z},
+ acceleration = {x = 0, y = -2.2, z = 0},
+ expirationtime = 1,
+ size = 1.5,
+ glow = 5,
+ texture = "xdecor_glyph" .. random(1,18) .. ".png"
+ })
+ end
+
+ return true
+end
+
+xdecor.register("enchantment_table", {
+ description = S("Enchantment Table"),
+ _tt_help = S("Enchant your tools with mese crystals"),
+ tiles = {
+ "xdecor_enchantment_top.png", "xdecor_enchantment_bottom.png",
+ "xdecor_enchantment_side.png", "xdecor_enchantment_side.png",
+ "xdecor_enchantment_side.png", "xdecor_enchantment_side.png"
+ },
+ groups = {cracky = 1, level = 1},
+ is_ground_content = false,
+ light_source = 6,
+ sounds = default.node_sound_stone_defaults(),
+ on_rotate = screwdriver.rotate_simple,
+ can_dig = enchanting.dig,
+ on_blast = enchanting.blast,
+ on_timer = enchanting.timer,
+ on_construct = enchanting.construct,
+ on_destruct = enchanting.destruct,
+ on_receive_fields = enchanting.fields,
+ on_metadata_inventory_put = enchanting.on_put,
+ on_metadata_inventory_take = enchanting.on_take,
+ allow_metadata_inventory_put = enchanting.put,
+ allow_metadata_inventory_move = function()
+ return 0
+ end,
+})
+
+minetest.register_entity("xdecor:book_open", {
+ initial_properties = {
+ visual = "sprite",
+ visual_size = {x=0.75, y=0.75},
+ collisionbox = {0,0,0,0,0,0},
+ pointable = false,
+ physical = false,
+ textures = {"xdecor_book_open.png"},
+ static_save = false,
+ },
+})
+
+minetest.register_lbm({
+ label = "recreate book entity",
+ name = "xdecor:create_book_entity",
+ nodenames = {"xdecor:enchantment_table"},
+ run_at_every_load = true,
+ action = function(pos, node)
+ local objs = minetest.get_objects_inside_radius(pos, 0.9)
+
+ for _, obj in ipairs(objs) do
+ local e = obj:get_luaentity()
+ if e and e.name == "xdecor:book_open" then
+ return
+ end
+ end
+
+ minetest.add_entity({x = pos.x, y = pos.y + 0.85, z = pos.z}, "xdecor:book_open")
+ end,
+})
+
+function enchanting:enchant_texture(img)
+ if img == nil or img == "" or type(img) ~= "string" then
+ return "no_texture.png"
+ else
+ return "("..img.. ")^[colorize:violet:50"
+ end
+end
+
+function enchanting:register_tool(original_tool_name, def)
+ local original_tool = reg_tools[original_tool_name]
+ if not original_tool then
+ minetest.log("error", "[xdecor] Called enchanting:register_tool for non-existing tool: "..original_tool_name)
+ return
+ end
+ local original_toolcaps = original_tool.tool_capabilities
+ if not original_toolcaps then
+ minetest.log("error", "[xdecor] Called enchanting:register_tool for tool without tool_capabilities: "..original_tool_name)
+ return
+ end
+ local original_damage_groups = original_toolcaps.damage_groups
+ local original_groupcaps = original_toolcaps.groupcaps
+ local original_basename = original_tool_name:match(".*:(.*)")
+ local toolitem = ItemStack(original_tool_name)
+ local original_desc = toolitem:get_short_description() or original_tool_name
+ local groups
+ if def.groups then
+ groups = table.copy(def.groups)
+ elseif original_tool.groups then
+ groups = table.copy(original_tool.groups)
+ else
+ groups = {}
+ end
+ groups.not_in_creative_inventory = 1
+ for _, enchant in ipairs(def.enchants) do
+ local groupcaps = table.copy(original_groupcaps)
+ local full_punch_interval = original_toolcaps.full_punch_interval
+ local max_drop_level = original_toolcaps.max_drop_level
+ local dig_group = def.dig_group
+ local fleshy
+
+ if not def.bonuses then
+ def.bonuses = {}
+ end
+ local bonus_defs = {
+ uses = def.bonuses.uses or DEFAULT_ENCHANTING_USES,
+ times = def.bonuses.times or DEFAULT_ENCHANTING_TIMES,
+ damages = def.bonuses.damages or DEFAULT_ENCHANTING_DAMAGES,
+ }
+
+ if enchant == "durable" then
+ groupcaps[dig_group].uses = ceil(original_groupcaps[dig_group].uses *
+ bonus_defs.uses)
+ elseif enchant == "fast" then
+ for i, time in pairs(original_groupcaps[dig_group].times) do
+ groupcaps[dig_group].times[i] = time - bonus_defs.times
+ end
+ elseif enchant == "sharp" then
+ fleshy = original_damage_groups.fleshy
+ fleshy = fleshy + bonus_defs.damages
+ else
+ minetest.log("error", "[xdecor] Called enchanting:register_tool with unsupported enchant: "..tostring(enchant))
+ return
+ end
+
+ local arg1 = original_desc
+ local arg2 = self:get_tooltip(enchant, original_groupcaps[dig_group], fleshy, bonus_defs)
+ local enchantedTool = original_tool.mod_origin .. ":enchanted_" .. original_basename .. "_" .. enchant
+
+ local invimg = original_tool.inventory_image
+ invimg = enchanting:enchant_texture(invimg)
+ local wieldimg = original_tool.wield_image
+ if wieldimg == nil or wieldimg == "" then
+ wieldimg = invimg
+ end
+ minetest.register_tool(":" .. enchantedTool, {
+ --~ Enchanted tool description, e.g. "Enchanted Diamond Sword". @1 is the original tool name, @2 is the enchantment text, e.g. "Durability (+20%)"
+ description = S("Enchanted @1\n@2", arg1, arg2),
+ --~ Enchanted tool description, e.g. "Enchanted Diamond Sword"
+ short_description = S("Enchanted @1", arg1),
+ inventory_image = invimg,
+ wield_image = wieldimg,
+ groups = groups,
+ tool_capabilities = {
+ groupcaps = groupcaps, damage_groups = {fleshy = fleshy},
+ full_punch_interval = full_punch_interval,
+ max_drop_level = max_drop_level
+ },
+ pointabilities = original_tool.pointabilities,
+ })
+ if minetest.get_modpath("toolranks") then
+ toolranks.add_tool(enchantedTool)
+ end
+ end
+ available_tool_enchants[original_tool_name] = table.copy(def.enchants)
+ reg_enchantable_tools[original_tool_name] = true
+end
+
+function enchanting:register_custom_tool(original_tool_name, enchanted_tools)
+ if not available_tool_enchants[original_tool_name] then
+ available_tool_enchants[original_tool_name] = {}
+ end
+ for enchant, v in pairs(enchanted_tools) do
+ table.insert(available_tool_enchants[original_tool_name], enchant)
+ end
+ reg_enchantable_tools[original_tool_name] = true
+end
+
+-- Recipes
+
+minetest.register_craft({
+ output = "xdecor:enchantment_table",
+ recipe = {
+ {"", "default:book", ""},
+ {"default:diamond", "default:obsidian", "default:diamond"},
+ {"default:obsidian", "default:obsidian", "default:obsidian"}
+ }
+})
+
+--[[ API FUNCTIONS ]]
+
+--[[
+Register one or more enchantments for an already defined tool.
+This will register a new tool for each enchantment. The new tools will
+have the following changes over the original:
+* New description and short_description
+* Apply a purple glow on wield_image and inventory_image using
+ "()^[colorize:purple"
+* Change tool_capabilities and damage_groups, depending on
+ enchantments.
+* Have groups set to { not_in_creative_inventory = 1 }
+
+The new tools will follow this naming scheme:
+
+ :enchanted__
+
+e.g. example:sword_diamond with the enchantment "sharp" will
+have "example:enchanted_sword_diamond_sharp" added.
+
+You must make sure this name is available before calling this
+function.
+
+Arguments:
+* toolname: Itemstring of original tool to enchant
+* def: Definition table with the following fields:
+ * enchants: a list of strings, one for each enchantment to add.
+ there must be at least one enchantment.
+ Available enchantments:
+ * "durable": Durability (tool lasts longer)
+ * "fast": Efficiency (tool digs faster)
+ * "sharp": Sharpness (more damage using the damage group "fleshy")
+ * dig_group: Must be specified if Durability or Efficiency is used.
+ This defines the tool's digging group that enchantment will improve.
+ * bonuses: optional table to customize the enchantment "strengths":
+ * uses: multiplies number of uses (Durability) (default: 1.2)
+ * times: subtracts from digging time; higher = faster (Efficiency) (default: 0.1)
+ * damages: adds to damage (Sharpness) (default: 1)
+ * groups: optional table specifying all item groups. If specified,
+ this should at least contain `not_in_creative_inventory=1`.
+ If unspecified (recommended), the enchanted tools will inherit all
+ groups from the original tool, plus they receive `not_in_creative_inventory=1`
+]]
+xdecor.register_enchantable_tool = function(toolname, def)
+ enchanting:register_tool(toolname, def)
+end
+
+--[[ Registers a custom tool enchantment.
+Here, you are fully free to design the tool yourself.
+
+The enchanted tools should follow these guidelines:
+
+1) Use xdecor.enchant_description to generate the description and short_description
+2) Use xdecor.enchant_texture to generate the inventory_image and wield_image
+3) Set groups to { not_in_creative_inventory = 1 }
+
+Arguments:
+* toolname: Itemstring of original tool to enchant
+* enchanted_tools: Table of enchanted tools.
+ * The keys are enchantment names from "enchants" in xdecor.register_enchantable_tool
+ * The values are the itemstrings of the enchanted tools for those
+ enchantments
+]]
+xdecor.register_custom_enchantable_tool = function(toolname, enchanted_tools)
+ enchanting:register_custom_tool(toolname, enchanted_tools)
+end
+
+-- Takes a texture (string) and applies an "enchanting" modifier on it.
+-- Useful when you want to register custom tool enchantments.
+xdecor.enchant_texture = function(texture)
+ return enchanting:enchant_texture(texture)
+end
+
+--[[
+Takes a description of a normal tool and modifies it for the enchanted tool variant.
+Arguments:
+* description: Original description to modify
+* enchant: Enchantment type. One of the enchantment names from "enchants" in xdecor.register_enchantable_tool
+* percent: Percentage to display
+
+Returns: ,
+
+-- Useful when you want to register custom tool enchantments.
+]]
+xdecor.enchant_description = function(description, enchant, percent)
+ local append = enchanting:get_tooltip_raw(enchant, percent)
+ local desc = S("Enchanted @1\n@2", description, append)
+ local short_desc S("Enchanted @1", description)
+ return desc, short_desc
+end
+
diff --git a/mods/xdecor/src/hive.lua b/mods/xdecor/src/hive.lua
new file mode 100644
index 00000000..660cf846
--- /dev/null
+++ b/mods/xdecor/src/hive.lua
@@ -0,0 +1,204 @@
+local hive = {}
+local S = minetest.get_translator("xdecor")
+local FS = function(...) return minetest.formspec_escape(S(...)) end
+local HONEY_MAX = 16
+local NEEDED_FLOWERS = 3
+local TIMER_MIN = 64
+local TIMER_MAX = 128
+
+local text_busy = FS("The bees are busy making honey.")
+local text_noflowers = FS("The bees are looking for flowers.")
+local text_fewflowers = FS("The bees want to pollinate more flowers.")
+local text_idle = FS("The bees are idle.")
+local text_sleep = FS("The bees are resting.")
+
+function hive.set_formspec(meta, status)
+ local statustext
+ if status == "busy" then
+ statustext = text_busy
+ elseif status == "noflowers" then
+ statustext = text_noflowers
+ elseif status == "fewflowers" then
+ statustext = text_fewflowers
+ elseif status == "idle" then
+ statustext = text_idle
+ elseif status == "sleep" then
+ statustext = text_sleep
+ end
+
+ local formspec = "size[8,6;]"
+ .."label[0.5,0;"..statustext.."]"
+ ..[[ image[6,1;1,1;hive_bee.png]
+ image[5,1;1,1;hive_layout.png]
+ list[context;honey;5,1;1,1;]
+ list[current_player;main;0,2.35;8,4;]
+ listring[current_player;main]
+ listring[context;honey] ]] ..
+ xdecor.xbg .. default.get_hotbar_bg(0,2.35)
+ meta:set_string("formspec", formspec)
+end
+
+local function count_flowers(pos)
+ local radius = 4
+ local minp = vector.add(pos, -radius)
+ local maxp = vector.add(pos, radius)
+ local flowers = minetest.find_nodes_in_area_under_air(minp, maxp, "group:flower")
+ return #flowers
+end
+
+local function is_sleeptime()
+ local time = (minetest.get_timeofday() or 0) * 24000
+ if time < 5500 or time > 18500 then
+ return true
+ else
+ return false
+ end
+end
+
+function hive.construct(pos)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+
+ local status = "idle"
+ local flowers = count_flowers(pos)
+ if is_sleeptime() then
+ status = "sleep"
+ elseif flowers >= NEEDED_FLOWERS then
+ status = "busy"
+ elseif flowers > 0 then
+ status = "fewflowers"
+ else
+ status = "noflowers"
+ end
+ hive.set_formspec(meta, status)
+ meta:set_string("infotext", S("Artificial Hive"))
+ inv:set_size("honey", 1)
+
+ local timer = minetest.get_node_timer(pos)
+ timer:start(math.random(TIMER_MIN, TIMER_MAX))
+end
+
+function hive.timer(pos)
+ local meta = minetest.get_meta(pos)
+ if is_sleeptime() then
+ hive.set_formspec(meta, "sleep")
+ return true
+ end
+
+ local inv = minetest.get_meta(pos):get_inventory()
+ local honeystack = inv:get_stack("honey", 1)
+ local honey = honeystack:get_count()
+
+ local flowers = count_flowers(pos)
+
+ if flowers >= NEEDED_FLOWERS and honey < HONEY_MAX then
+ if honey == HONEY_MAX - 1 then
+ hive.set_formspec(meta, "idle")
+ else
+ hive.set_formspec(meta, "busy")
+ end
+ inv:add_item("honey", "xdecor:honey")
+ elseif honey == HONEY_MAX then
+ hive.set_formspec(meta, "idle")
+ local timer = minetest.get_node_timer(pos)
+ timer:stop()
+ return true
+ end
+ if flowers == 0 then
+ hive.set_formspec(meta, "noflowers")
+ elseif flowers < NEEDED_FLOWERS then
+ hive.set_formspec(meta, "fewflowers")
+ end
+
+ return true
+end
+
+function hive.blast(pos)
+ local drops = xdecor.get_inventory_drops(pos, {"honey"})
+ minetest.remove_node(pos)
+ return drops
+end
+
+xdecor.register("hive", {
+ description = S("Artificial Hive"),
+ --~ Tooltip of artificial hive
+ _tt_help = S("Bees live here and produce honey"),
+ tiles = {"xdecor_hive_top.png", "xdecor_hive_top.png",
+ "xdecor_hive_side.png", "xdecor_hive_side.png",
+ "xdecor_hive_side.png", "xdecor_hive_front.png"},
+ groups = {choppy=3, oddly_breakable_by_hand=2, flammable=1},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ on_construct = hive.construct,
+ on_timer = hive.timer,
+ on_blast = hive.blast,
+
+ can_dig = function(pos)
+ local inv = minetest.get_meta(pos):get_inventory()
+ return inv:is_empty("honey")
+ end,
+
+ on_punch = function(_, _, puncher)
+ puncher:set_hp(puncher:get_hp() - 2)
+ end,
+
+ allow_metadata_inventory_put = function()
+ return 0
+ end,
+
+ on_metadata_inventory_take = function(pos, list, index, stack)
+ local inv = minetest.get_inventory({type="node", pos=pos})
+ local remainstack = inv:get_stack(list, index)
+ -- Trigger if taking anything from full honey slot
+ if remainstack:get_count() + stack:get_count() >= HONEY_MAX then
+ local timer = minetest.get_node_timer(pos)
+ timer:start(math.random(TIMER_MIN, TIMER_MAX))
+ if not is_sleeptime() and count_flowers(pos) >= NEEDED_FLOWERS then
+ local meta = minetest.get_meta(pos)
+ hive.set_formspec(meta, "busy")
+ end
+ end
+ end
+})
+
+-- Craft items
+
+minetest.register_craftitem("xdecor:honey", {
+ description = S("Honey"),
+ inventory_image = "xdecor_honey.png",
+ wield_image = "xdecor_honey.png",
+ on_use = minetest.item_eat(2),
+ groups = {
+ food_honey = 1,
+ food_sugar = 1,
+ flammable = 2,
+ },
+})
+
+-- Recipes
+
+minetest.register_craft({
+ output = "xdecor:hive",
+ recipe = {
+ {"group:stick", "group:stick", "group:stick"},
+ {"default:paper", "default:paper", "default:paper"},
+ {"group:stick", "group:stick", "group:stick"}
+ }
+})
+
+if minetest.get_modpath("unified_inventory") then
+ unified_inventory.register_craft_type("xdecor:hive", {
+ description = S("Made by bees"),
+ icon = "hive_bee.png",
+ width = 1,
+ height = 1,
+ uses_crafting_grid = false
+ })
+
+ unified_inventory.register_craft({
+ output = "xdecor:honey",
+ type = "xdecor:hive",
+ items = {"xdecor:hive"},
+ width = 1
+ })
+end
diff --git a/mods/xdecor/src/itemframe.lua b/mods/xdecor/src/itemframe.lua
new file mode 100644
index 00000000..fd7ec0e3
--- /dev/null
+++ b/mods/xdecor/src/itemframe.lua
@@ -0,0 +1,249 @@
+-- Item frames.
+
+-- Hint:
+-- If your item appears behind or too far in front of the item frame, add
+-- _xdecor_itemframe_offset =
+-- to your item definition to fix it.
+
+local itemframe, tmp = {}, {}
+local S = minetest.get_translator("xdecor")
+screwdriver = screwdriver or {}
+
+local function remove_item(pos, node)
+ local objs = minetest.get_objects_inside_radius(pos, 0.5)
+ if not objs then return end
+
+ for _, obj in pairs(objs) do
+ local ent = obj:get_luaentity()
+ if obj and ent and ent.name == "xdecor:f_item" then
+ obj:remove() break
+ end
+ end
+end
+
+local facedir = {
+ [0] = {x = 0, y = 0, z = 1},
+ {x = 1, y = 0, z = 0},
+ {x = 0, y = 0, z = -1},
+ {x = -1, y = 0, z = 0}
+}
+
+local function update_item(pos, node)
+ remove_item(pos, node)
+ local meta = minetest.get_meta(pos)
+ local itemstring = meta:get_string("item")
+ local posad = facedir[node.param2]
+ if not posad or itemstring == "" then return end
+
+ local itemdef = ItemStack(itemstring):get_definition()
+ local offset_plus = 0
+ if itemdef and itemdef._xdecor_itemframe_offset then
+ offset_plus = itemdef._xdecor_itemframe_offset
+ offset_plus = math.min(6, math.max(-6, offset_plus))
+ end
+ local offset = (6.5+offset_plus)/16
+
+ pos = vector.add(pos, vector.multiply(posad, offset))
+ tmp.nodename = node.name
+ tmp.texture = ItemStack(itemstring):get_name()
+
+ local entity = minetest.add_entity(pos, "xdecor:f_item")
+ local yaw = (math.pi * 2) - node.param2 * (math.pi / 2)
+ entity:set_yaw(yaw)
+
+ local timer = minetest.get_node_timer(pos)
+ timer:start(15.0)
+end
+
+local function drop_item(pos, node)
+ local meta = minetest.get_meta(pos)
+ local item = meta:get_string("item")
+ if item == "" then return end
+
+ minetest.add_item(pos, item)
+ meta:set_string("item", "")
+ remove_item(pos, node)
+
+ local timer = minetest.get_node_timer(pos)
+ timer:stop()
+end
+
+function itemframe.set_infotext(meta)
+ local itemstring = meta:get_string("item")
+ local owner = meta:get_string("owner")
+ if itemstring == "" then
+ if owner ~= "" then
+ --~ Item frame infotext. @1 = item frame name, @2 = owner name (player)
+ meta:set_string("infotext", S("@1 (owned by @2)",
+ S("Item Frame"), owner))
+ else
+ meta:set_string("infotext", S("Item Frame"))
+ end
+ else
+ local itemstack = ItemStack(itemstring)
+ local tooltip = itemstack:get_short_description()
+ if tooltip == "" then
+ tooltip = itemstack:get_name()
+ end
+ if itemstring == "" then
+ tooltip = S("Item Frame")
+ end
+ if owner ~= "" then
+ meta:set_string("infotext", S("@1 (owned by @2)", tooltip, owner))
+ else
+ meta:set_string("infotext", tooltip)
+ end
+ end
+end
+
+function itemframe.after_place(pos, placer, itemstack)
+ local meta = minetest.get_meta(pos)
+ local name = placer:get_player_name()
+ meta:set_string("owner", name)
+ itemframe.set_infotext(meta)
+end
+
+function itemframe.timer(pos)
+ local node = minetest.get_node(pos)
+ local meta = minetest.get_meta(pos)
+ local num = #minetest.get_objects_inside_radius(pos, 0.5)
+
+ if num == 0 and meta:get_string("item") ~= "" then
+ update_item(pos, node)
+ end
+
+ return true
+end
+
+function itemframe.rightclick(pos, node, clicker, itemstack)
+ local meta = minetest.get_meta(pos)
+ local player_name = clicker:get_player_name()
+ local owner = meta:get_string("owner")
+ local admin = minetest.check_player_privs(player_name, "protection_bypass")
+
+ if not admin and (player_name ~= owner or not itemstack) then
+ return itemstack
+ end
+
+ drop_item(pos, node)
+ local itemstring = itemstack:take_item():to_string()
+ meta:set_string("item", itemstring)
+ itemframe.set_infotext(meta)
+ update_item(pos, node)
+ return itemstack
+end
+
+function itemframe.punch(pos, node, puncher)
+ local meta = minetest.get_meta(pos)
+ local player_name = puncher:get_player_name()
+ local owner = meta:get_string("owner")
+ local admin = minetest.check_player_privs(player_name, "protection_bypass")
+
+ if admin and player_name == owner then
+ drop_item(pos, node)
+ end
+end
+
+function itemframe.dig(pos, player)
+ if not player then return end
+ local meta = minetest.get_meta(pos)
+ local player_name = player and player:get_player_name()
+ local owner = meta:get_string("owner")
+ local admin = minetest.check_player_privs(player_name, "protection_bypass")
+
+ return admin or player_name == owner
+end
+
+function itemframe.blast(pos)
+ return
+end
+
+xdecor.register("itemframe", {
+ description = S("Item Frame"),
+ --~ Item frame tooltip
+ _tt_help = S("For presenting a single item"),
+ groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ on_rotate = screwdriver.disallow,
+ sunlight_propagates = true,
+ inventory_image = "xdecor_itemframe.png",
+ node_box = xdecor.nodebox.slab_z(0.9375),
+ tiles = {
+ "xdecor_wood.png", "xdecor_wood.png", "xdecor_wood.png",
+ "xdecor_wood.png", "xdecor_wood.png", "xdecor_itemframe.png"
+ },
+ after_place_node = itemframe.after_place,
+ on_timer = itemframe.timer,
+ on_rightclick = itemframe.rightclick,
+ on_punch = itemframe.punch,
+ can_dig = itemframe.dig,
+ on_blast = itemframe.blast,
+ after_destruct = remove_item,
+ _xdecor_itemframe_offset = -3.5,
+})
+
+minetest.register_entity("xdecor:f_item", {
+ initial_properties = {
+ visual = "wielditem",
+ visual_size = {x = 0.33, y = 0.33},
+ collisionbox = {0,0,0,0,0,0},
+ pointable = false,
+ physical = false,
+ textures = {"air"},
+ },
+ on_activate = function(self, staticdata)
+ local pos = self.object:get_pos()
+ if minetest.get_node(pos).name ~= "xdecor:itemframe" then
+ self.object:remove()
+ end
+
+ if tmp.nodename and tmp.texture then
+ self.nodename = tmp.nodename
+ tmp.nodename = nil
+ self.texture = tmp.texture
+ tmp.texture = nil
+ elseif staticdata and staticdata ~= "" then
+ local data = staticdata:split(";")
+ if data and data[1] and data[2] then
+ self.nodename = data[1]
+ self.texture = data[2]
+ end
+ end
+ if self.texture then
+ self.object:set_properties({
+ textures = {self.texture}
+ })
+ end
+ end,
+ get_staticdata = function(self)
+ if self.nodename and self.texture then
+ return self.nodename .. ";" .. self.texture
+ end
+
+ return ""
+ end
+})
+
+-- Recipes
+
+minetest.register_craft({
+ output = "xdecor:itemframe",
+ recipe = {
+ {"group:stick", "group:stick", "group:stick"},
+ {"group:stick", "default:paper", "group:stick"},
+ {"group:stick", "group:stick", "group:stick"}
+ }
+})
+
+minetest.register_lbm({
+ label = "Update itemframe infotexts",
+ name = "xdecor:update_itemframe_infotexts",
+ nodenames = {"xdecor:itemframe"},
+ run_at_every_load = true,
+ action = function(pos, node)
+ local meta = minetest.get_meta(pos)
+ itemframe.set_infotext(meta)
+ end,
+})
+
diff --git a/mods/xdecor/src/mailbox.lua b/mods/xdecor/src/mailbox.lua
new file mode 100644
index 00000000..4fe7f6fe
--- /dev/null
+++ b/mods/xdecor/src/mailbox.lua
@@ -0,0 +1,211 @@
+local mailbox = {}
+screwdriver = screwdriver or {}
+local S = minetest.get_translator("xdecor")
+local FS = function(...) return minetest.formspec_escape(S(...)) end
+
+-- Max. length of the list of givers in mailbox formspec
+local GIVER_LIST_LENGTH = 7
+
+local function get_img(img)
+ if not img then return end
+ local img_name = img:match("(.*)%.png")
+
+ if img_name then
+ return img_name .. ".png"
+ end
+end
+
+local function img_col(stack)
+ local def = minetest.registered_items[stack]
+ if not def then
+ return ""
+ end
+
+ if def.inventory_image ~= "" then
+ local img = get_img(def.inventory_image)
+ if img then
+ return img
+ end
+ end
+
+ if def.tiles then
+ local tile, img = def.tiles[1]
+ if type(tile) == "table" then
+ img = get_img(tile.name)
+ elseif type(tile) == "string" then
+ img = get_img(tile)
+ end
+
+ if img then
+ return img
+ end
+ end
+
+ return ""
+end
+
+function mailbox:formspec(pos, owner, is_owner)
+ local spos = pos.x .. "," .. pos.y .. "," .. pos.z
+ local meta = minetest.get_meta(pos)
+ local giver, img = "", ""
+
+ if is_owner then
+ for i = 1, GIVER_LIST_LENGTH do
+ local giving = meta:get_string("giver" .. i)
+ if giving ~= "" then
+ local stack = meta:get_string("stack" .. i)
+ local giver_name = giving:sub(1,12)
+ local stack_name = stack:match("[%w_:]+")
+ local stack_count = stack:match("%s(%d+)") or 1
+
+ -- List of donors. A line looks like this:
+ -- - ×
-
+ giver = giver .. "#FFFF00," .. giver_name .. "," .. i ..
+ --~ Used in the mailbox donor list. Will be displayed as item icon followed by this string. @1 = item count
+ ",#FFFFFF," .. FS("× @1", stack_count) .. ","
+
+ img = img .. i .. "=" ..
+ img_col(stack_name) .. "^\\[resize:16x16,"
+ end
+ end
+
+ return "size[9.5,9]"
+ .."label[0,0;"..FS("Mailbox").."]"
+ .."label[6,0;"..FS("Last donators").."]"
+ ..[[ box[6,0.72;3.3,3.9;#555555]
+ listring[current_player;main]
+ list[current_player;main;0.75,5.25;8,4;]
+ tableoptions[background=#00000000;highlight=#00000000;border=false] ]] ..
+ "tablecolumns[color;text;image," .. img .. "0;color;text]" ..
+ "table[6,0.75;3.3,4;givers;" .. giver .. "]" ..
+ "list[nodemeta:" .. spos .. ";mailbox;0,0.75;6,4;]" ..
+ "listring[nodemeta:" .. spos .. ";mailbox]" ..
+ xdecor.xbg .. default.get_hotbar_bg(0.75, 5.25)
+ end
+
+ return "size[8,5]" ..
+ "list[current_player;main;0,1.25;8,4;]" ..
+ "label[0,0;"..FS("Send your goods to\n@1",
+ (minetest.colorize and
+ minetest.colorize("#FFFF00", owner) or owner)) .. "]" ..
+ "list[nodemeta:" .. spos .. ";drop;3.5,0;1,1;]" ..
+ "listring[]" ..
+ xdecor.xbg .. default.get_hotbar_bg(0, 1.25)
+end
+
+function mailbox.dig(pos, player)
+ local meta = minetest.get_meta(pos)
+ local owner = meta:get_string("owner")
+ local player_name = player and player:get_player_name()
+ local inv = meta:get_inventory()
+
+ return inv:is_empty("mailbox") and player_name == owner
+end
+
+function mailbox.blast(pos)
+ return
+end
+
+function mailbox.after_place_node(pos, placer)
+ local meta = minetest.get_meta(pos)
+ local player_name = placer:get_player_name()
+
+ meta:set_string("owner", player_name)
+ meta:set_string("infotext", S("@1's Mailbox", player_name))
+
+ local inv = meta:get_inventory()
+ inv:set_size("mailbox", 6 * 4)
+ inv:set_size("drop", 1)
+end
+
+function mailbox.rightclick(pos, node, clicker, itemstack, pointed_thing)
+ local meta = minetest.get_meta(pos)
+ local player = clicker:get_player_name()
+ local owner = meta:get_string("owner")
+
+ minetest.show_formspec(player, "xdecor:mailbox",
+ mailbox:formspec(pos, owner, (player == owner)))
+
+ return itemstack
+end
+
+function mailbox.put(pos, listname, _, stack, player)
+ if listname == "drop" then
+ local inv = minetest.get_meta(pos):get_inventory()
+ if inv:room_for_item("mailbox", stack) then
+ return -1
+ else
+ minetest.chat_send_player(player:get_player_name(),
+ S("The mailbox is full."))
+ end
+ end
+
+ return 0
+end
+
+function mailbox.on_put(pos, listname, _, stack, player)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+
+ if listname == "drop" and inv:room_for_item("mailbox", stack) then
+ inv:set_list("drop", {})
+ inv:add_item("mailbox", stack)
+
+ for i = GIVER_LIST_LENGTH, 2, -1 do
+ meta:set_string("giver" .. i, meta:get_string("giver" .. (i - 1)))
+ meta:set_string("stack" .. i, meta:get_string("stack" .. (i - 1)))
+ end
+
+ meta:set_string("giver1", player:get_player_name())
+ meta:set_string("stack1", stack:to_string())
+ end
+end
+
+function mailbox.allow_take(pos, listname, index, stack, player)
+ if listname == "drop" then
+ return 0
+ end
+ local meta = minetest.get_meta(pos)
+
+ if player:get_player_name() ~= meta:get_string("owner") then
+ return 0
+ end
+
+ return stack:get_count()
+end
+
+function mailbox.allow_move(pos)
+ return 0
+end
+
+xdecor.register("mailbox", {
+ description = S("Mailbox"),
+ --~ Mailbox tooltip
+ _tt_help = S("Lets other players give you things"),
+ tiles = {"xdecor_mailbox_top.png", "xdecor_mailbox_bottom.png",
+ "xdecor_mailbox_side.png", "xdecor_mailbox_side.png",
+ "xdecor_mailbox.png", "xdecor_mailbox.png"},
+ groups = {cracky = 3, oddly_breakable_by_hand = 1},
+ is_ground_content = false,
+ sounds = default.node_sound_metal_defaults(),
+ on_rotate = screwdriver.rotate_simple,
+ can_dig = mailbox.dig,
+ on_blast = mailbox.blast,
+ on_rightclick = mailbox.rightclick,
+ allow_metadata_inventory_take = mailbox.allow_take,
+ allow_metadata_inventory_move = mailbox.allow_move,
+ on_metadata_inventory_put = mailbox.on_put,
+ allow_metadata_inventory_put = mailbox.put,
+ after_place_node = mailbox.after_place_node
+})
+
+-- Recipes
+
+minetest.register_craft({
+ output = "xdecor:mailbox",
+ recipe = {
+ {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
+ {"dye:red", "default:paper", "dye:red"},
+ {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}
+ }
+})
diff --git a/mods/xdecor/src/mechanisms.lua b/mods/xdecor/src/mechanisms.lua
new file mode 100644
index 00000000..0427675f
--- /dev/null
+++ b/mods/xdecor/src/mechanisms.lua
@@ -0,0 +1,325 @@
+-- Thanks to sofar for helping with that code.
+
+local plate = {}
+screwdriver = screwdriver or {}
+
+local S = minetest.get_translator("xdecor")
+local ALPHA_OPAQUE = minetest.features.use_texture_alpha_string_modes and "opaque" or false
+
+-- Number of seconds an actuator (pressure plate, lever) stays active.
+-- After this time, it will return to the disabled state again.
+local DISABLE_ACTUATOR_AFTER = 2.0
+
+-- Effect area of pressure plates and levers. Doors within this area
+-- can be affected.
+local PRESSURE_PLATE_AREA_MIN = {x = -2, y = 0, z = -2}
+local PRESSURE_PLATE_AREA_MAX = {x = 2, y = 0, z = 2}
+local LEVER_AREA_MIN = {x = -2, y = -1, z = -2}
+local LEVER_AREA_MAX = {x = 2, y = 1, z = 2}
+
+-- Pressure plates check for players within this radius
+local PRESSURE_PLATE_PLAYER_RADIUS = 0.8
+-- Interval in seconds that pressure plates check for players
+local PRESSURE_PLATE_CHECK_TIMER = 0.1
+
+local function door_open(pos_door, player)
+ local door = doors.get(pos_door)
+ if not door then
+ return
+ end
+ door:open(player)
+end
+
+local function door_close(pos_door, player)
+ local door = doors.get(pos_door)
+ if not door then
+ return
+ end
+ door:close(player)
+end
+
+-- Returns true if the door node at pos is currently next to any
+-- active actuator node (lever, pressure plate)
+local function door_is_actuatored(pos_door)
+ local minp = vector.add(LEVER_AREA_MIN, pos_door)
+ local maxp = vector.add(LEVER_AREA_MAX, pos_door)
+ local levers = minetest.find_nodes_in_area(minp, maxp, "group:lever")
+ for l=1, #levers do
+ local lnode = minetest.get_node(levers[l])
+ if minetest.get_item_group(lnode.name, "xdecor_actuator") == 2 then
+ return true
+ end
+ end
+ minp = vector.add(PRESSURE_PLATE_AREA_MIN, pos_door)
+ maxp = vector.add(PRESSURE_PLATE_AREA_MAX, pos_door)
+ local pressure_plates = minetest.find_nodes_in_area(minp, maxp, "group:pressure_plate")
+ for p=1, #pressure_plates do
+ local pnode = minetest.get_node(pressure_plates[p])
+ if minetest.get_item_group(pnode.name, "xdecor_actuator") == 2 then
+ return true
+ end
+ end
+ return false
+end
+
+local function actuator_timeout(pos_actuator, actuator_area_min, actuator_area_max)
+ local actuator = minetest.get_node(pos_actuator)
+
+ -- Get name of last player that triggered the actuator
+ local meta = minetest.get_meta(pos_actuator)
+ local last_triggerer_str = meta:get_string("last_triggerer")
+ local last_triggerer_obj = minetest.get_player_by_name(last_triggerer_str)
+
+ -- Turn off actuator
+ if minetest.get_item_group(actuator.name, "xdecor_actuator") == 2 then
+ local def = minetest.registered_nodes[actuator.name]
+ if def._xdecor_actuator_off then
+ minetest.set_node(pos_actuator, { name = def._xdecor_actuator_off, param2 = actuator.param2 })
+ end
+ end
+
+ -- Close neighboring doors that are no longer next to any active actuator
+ local minp = vector.add(actuator_area_min, pos_actuator)
+ local maxp = vector.add(actuator_area_max, pos_actuator)
+ local doors = minetest.find_nodes_in_area(minp, maxp, "group:door")
+ for d=1, #doors do
+ if not door_is_actuatored(doors[d]) then
+ local dnode = minetest.get_node(doors[d])
+ local ddef = minetest.registered_nodes[dnode.name]
+ if (ddef.protected and last_triggerer_obj) or (not ddef.protected) then
+ door_close(doors[d], last_triggerer_obj)
+ end
+ end
+ end
+end
+
+local function actuator_activate(pos_actuator, actuator_area_min, actuator_area_max, player)
+ local player_name = player:get_player_name()
+ local actuator = minetest.get_node(pos_actuator)
+ local ga = minetest.get_item_group(actuator.name, "xdecor_actuator")
+ if ga == 2 then
+ -- No-op if actuator is already active
+ return
+ elseif ga == 1 then
+ local def = minetest.registered_nodes[actuator.name]
+ -- Turn actuator on
+ if def._xdecor_actuator_on then
+ minetest.set_node(pos_actuator, { name = def._xdecor_actuator_on, param2 = actuator.param2 })
+
+ -- Store name of last player that triggered the actuator
+ local meta = minetest.get_meta(pos_actuator)
+ meta:set_string("last_triggerer", player_name)
+ end
+ end
+
+ -- Turn on neighboring doors
+ local minp = vector.add(actuator_area_min, pos_actuator)
+ local maxp = vector.add(actuator_area_max, pos_actuator)
+ local doors = minetest.find_nodes_in_area(minp, maxp, "group:door")
+ for i = 1, #doors do
+ door_open(doors[i], player)
+ end
+end
+
+function plate.construct(pos)
+ local timer = minetest.get_node_timer(pos)
+ timer:start(PRESSURE_PLATE_CHECK_TIMER)
+end
+
+function plate.has_player_standing_on(pos)
+ local objs = minetest.get_objects_inside_radius(pos, PRESSURE_PLATE_PLAYER_RADIUS)
+ for _, player in pairs(objs) do
+ if player:is_player() then
+ return true, player
+ end
+ end
+ return false
+end
+
+function plate.timer(pos)
+ if not doors.get then
+ return true
+ end
+ local ok, player = plate.has_player_standing_on(pos)
+ if ok then
+ actuator_activate(pos, PRESSURE_PLATE_AREA_MIN, PRESSURE_PLATE_AREA_MAX, player)
+ return false
+ end
+
+ return true
+end
+
+function plate.construct_on(pos)
+ local timer = minetest.get_node_timer(pos)
+ timer:start(DISABLE_ACTUATOR_AFTER)
+end
+
+function plate.timer_on(pos)
+ if plate.has_player_standing_on(pos) then
+ -- If player is still standing on active pressure plate, restart timer
+ local timer = minetest.get_node_timer(pos)
+ timer:start(DISABLE_ACTUATOR_AFTER)
+ return
+ end
+
+ actuator_timeout(pos, PRESSURE_PLATE_AREA_MIN, PRESSURE_PLATE_AREA_MAX)
+end
+
+function plate.register(material, desc, def)
+ local groups
+ if def.groups then
+ groups = table.copy(def.groups)
+ else
+ groups = {}
+ end
+ groups.pressure_plate = 1
+ groups.xdecor_actuator = 1
+ xdecor.register("pressure_" .. material .. "_off", {
+ description = def.description or (desc .. " Pressure Plate"),
+ --~ Pressure plate tooltip
+ _tt_help = S("Opens doors when stepped on"),
+ tiles = {"xdecor_pressure_" .. material .. ".png"},
+ use_texture_alpha = ALPHA_OPAQUE,
+ drawtype = "nodebox",
+ node_box = xdecor.pixelbox(16, {{1, 0, 1, 14, 1, 14}}),
+ groups = groups,
+ is_ground_content = false,
+ sounds = def.sounds,
+ sunlight_propagates = true,
+ on_rotate = screwdriver.rotate_simple,
+ on_construct = plate.construct,
+ on_timer = plate.timer,
+ _xdecor_actuator_off = "xdecor:pressure_"..material.."_off",
+ _xdecor_actuator_on = "xdecor:pressure_"..material.."_on",
+ })
+ local groups_on = table.copy(groups)
+ groups_on.xdecor_actuator = 2
+ groups_on.pressure_plate = 2
+ xdecor.register("pressure_" .. material .. "_on", {
+ tiles = {"xdecor_pressure_" .. material .. ".png"},
+ use_texture_alpha = ALPHA_OPAQUE,
+ drawtype = "nodebox",
+ node_box = xdecor.pixelbox(16, {{1, 0, 1, 14, 0.4, 14}}),
+ groups = groups_on,
+ is_ground_content = false,
+ sounds = def.sounds,
+ drop = "xdecor:pressure_" .. material .. "_off",
+ sunlight_propagates = true,
+ on_rotate = screwdriver.rotate_simple,
+ on_construct = plate.construct_on,
+ on_timer = plate.timer_on,
+ _xdecor_actuator_off = "xdecor:pressure_"..material.."_off",
+ _xdecor_actuator_on = "xdecor:pressure_"..material.."_on",
+ })
+end
+
+plate.register("wood", "Wooden", {
+ sounds = default.node_sound_wood_defaults(),
+ groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 2},
+ description = S("Wooden Pressure Plate"),
+})
+
+plate.register("stone", "Stone", {
+ sounds = default.node_sound_stone_defaults(),
+ groups = {cracky = 3, oddly_breakable_by_hand = 2},
+ description = S("Stone Pressure Plate"),
+})
+
+xdecor.register("lever_off", {
+ description = S("Lever"),
+ --~ Lever tooltip
+ _tt_help = S("Opens doors when pulled"),
+ tiles = {"xdecor_lever_off.png"},
+ use_texture_alpha = ALPHA_OPAQUE,
+ drawtype = "nodebox",
+ node_box = xdecor.pixelbox(16, {{2, 1, 15, 12, 14, 1}}),
+ groups = {cracky = 3, oddly_breakable_by_hand = 2, lever = 1, xdecor_actuator = 1},
+ is_ground_content = false,
+ sounds = default.node_sound_stone_defaults(),
+ sunlight_propagates = true,
+ on_rotate = screwdriver.rotate_simple,
+
+ on_rightclick = function(pos, node, clicker, itemstack)
+ if not doors.get then
+ return itemstack
+ end
+ actuator_activate(pos, LEVER_AREA_MIN, LEVER_AREA_MAX, clicker)
+ return itemstack
+ end,
+ _xdecor_itemframe_offset = -3.5,
+ _xdecor_actuator_off = "xdecor:lever_off",
+ _xdecor_actuator_on = "xdecor:lever_on",
+})
+
+xdecor.register("lever_on", {
+ tiles = {"xdecor_lever_on.png"},
+ use_texture_alpha = ALPHA_OPAQUE,
+ drawtype = "nodebox",
+ node_box = xdecor.pixelbox(16, {{2, 1, 15, 12, 14, 1}}),
+ groups = {cracky = 3, oddly_breakable_by_hand = 2, lever = 2, xdecor_actuator = 2, not_in_creative_inventory = 1},
+ is_ground_content = false,
+ sounds = default.node_sound_stone_defaults(),
+ sunlight_propagates = true,
+ on_rotate = screwdriver.rotate_simple,
+ on_rightclick = function(pos, node, clicker, itemstack)
+ -- Prevent placing nodes on activated lever with the place key
+ -- for consistent behavior with the lever in "off" state.
+ -- The player may still place nodes using [Sneak].
+ return itemstack
+ end,
+ on_construct = function(pos)
+ local timer = minetest.get_node_timer(pos)
+ timer:start(DISABLE_ACTUATOR_AFTER)
+ end,
+ on_timer = function(pos)
+ local node = minetest.get_node(pos)
+ actuator_timeout(pos, LEVER_AREA_MIN, LEVER_AREA_MAX)
+ end,
+ drop = "xdecor:lever_off",
+ _xdecor_itemframe_offset = -3.5,
+ _xdecor_actuator_off = "xdecor:lever_off",
+ _xdecor_actuator_on = "xdecor:lever_on",
+})
+
+-- Make sure the node timers of active actuators are still
+-- active when these nodes load again. If not, start them
+-- again to trigger their timer action, which is expected
+-- to turn off the actuator soon.
+minetest.register_lbm({
+ label = "Restart actuator timers (X-Decor-libre)",
+ name = "xdecor:restart_actuator_timers",
+ nodenames = { "group:xdecor_actuator" },
+ run_at_every_load = true,
+ action = function(pos, node)
+ local g = minetest.get_item_group(node.name, "xdecor_actuator")
+ if g ~= 2 then
+ return
+ end
+ local timer = minetest.get_node_timer(pos)
+ if not timer:is_started() then
+ timer:start(DISABLE_ACTUATOR_AFTER)
+ end
+ end,
+})
+
+-- Recipes
+
+minetest.register_craft({
+ output = "xdecor:pressure_stone_off",
+ type = "shapeless",
+ recipe = {"group:stone", "group:stone"}
+})
+
+minetest.register_craft({
+ output = "xdecor:pressure_wood_off",
+ type = "shapeless",
+ recipe = {"group:wood", "group:wood"}
+})
+
+minetest.register_craft({
+ output = "xdecor:lever_off",
+ recipe = {
+ {"group:stick"},
+ {"group:stone"}
+ }
+})
diff --git a/mods/xdecor/src/nodes.lua b/mods/xdecor/src/nodes.lua
new file mode 100644
index 00000000..157633a9
--- /dev/null
+++ b/mods/xdecor/src/nodes.lua
@@ -0,0 +1,962 @@
+screwdriver = screwdriver or {}
+local S = minetest.get_translator("xdecor")
+local ALPHA_CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or true
+local ALPHA_OPAQUE = minetest.features.use_texture_alpha_string_modes and "opaque" or false
+
+local function register_pane(name, desc, def)
+ xpanes.register_pane(name, {
+ description = desc,
+ tiles = {"xdecor_" .. name .. ".png"},
+ drawtype = "airlike",
+ paramtype = "light",
+ textures = def.textures or {"xdecor_" .. name .. ".png", "" ,"xdecor_" .. name .. ".png"},
+ inventory_image = "xdecor_" .. name .. ".png",
+ wield_image = "xdecor_" .. name .. ".png",
+ groups = def.groups,
+ sounds = def.sounds or default.node_sound_defaults(),
+ recipe = def.recipe
+ })
+end
+
+register_pane("bamboo_frame", S("Bamboo Frame"), {
+ groups = {choppy = 3, oddly_breakable_by_hand = 2, pane = 1, flammable = 2},
+ recipe = {
+ {"default:papyrus", "default:papyrus", "default:papyrus"},
+ {"default:papyrus", "farming:cotton", "default:papyrus"},
+ {"default:papyrus", "default:papyrus", "default:papyrus"}
+ },
+ sounds = default.node_sound_wood_defaults(),
+})
+
+register_pane("chainlink", S("Chainlink"), {
+ groups = {cracky = 3, oddly_breakable_by_hand = 2, pane = 1},
+ recipe = {
+ {"default:steel_ingot", "", "default:steel_ingot"},
+ {"", "default:steel_ingot", ""},
+ {"default:steel_ingot", "", "default:steel_ingot"}
+ }
+})
+
+register_pane("rusty_bar", S("Rusty Iron Bars"), {
+ sounds = default.node_sound_stone_defaults(),
+ textures = {"xdecor_rusty_bar.png", "", "xdecor_rusty_bar_top.png"},
+ groups = {cracky = 2, pane = 1},
+ recipe = {
+ {"", "default:dirt", ""},
+ {"default:iron_lump", "default:iron_lump", "default:iron_lump"},
+ {"default:iron_lump", "default:iron_lump", "default:iron_lump"},
+ },
+ sounds = default.node_sound_metal_defaults(),
+})
+
+register_pane("wood_frame", S("Wood Frame"), {
+ sounds = default.node_sound_wood_defaults(),
+ textures = {"xdecor_wood_frame.png", "", "xdecor_wood_frame_top.png"},
+ groups = {choppy = 2, pane = 1, flammable = 2},
+ recipe = {
+ {"group:wood", "group:stick", "group:wood"},
+ {"group:stick", "group:stick", "group:stick"},
+ {"group:wood", "group:stick", "group:wood"}
+ }
+})
+
+xdecor.register("baricade", {
+ description = S("Barricade"),
+ drawtype = "plantlike",
+ paramtype2 = "facedir",
+ inventory_image = "xdecor_baricade.png",
+ tiles = {"xdecor_baricade.png"},
+ groups = {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
+ is_ground_content = false,
+ damage_per_second = 4,
+ selection_box = xdecor.nodebox.slab_y(0.3),
+ collision_box = xdecor.pixelbox(2, {{0, 0, 1, 2, 2, 0}})
+})
+
+xdecor.register("barrel", {
+ description = S("Barrel"),
+ tiles = {"xdecor_barrel_top.png", "xdecor_barrel_top.png", "xdecor_barrel_sides.png"},
+ on_place = minetest.rotate_node,
+ groups = {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults()
+})
+
+local function blast_storage(pos)
+ local drops = xdecor.get_inventory_drops(pos, {"main"})
+ minetest.remove_node(pos)
+ return drops
+end
+
+local function register_storage(name, desc, def)
+ xdecor.register(name, {
+ description = desc,
+ _tt_help = def._tt_help,
+ inventory = {size = def.inv_size or 24},
+ infotext = desc,
+ tiles = def.tiles,
+ use_texture_alpha = ALPHA_OPAQUE,
+ node_box = def.node_box,
+ on_rotate = def.on_rotate,
+ on_place = def.on_place,
+ on_blast = blast_storage,
+ groups = def.groups or {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults()
+ })
+end
+
+register_storage("cabinet", S("Wooden Cabinet"), {
+ _tt_help = S("24 inventory slots"),
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {
+ "xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
+ "xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
+ "xdecor_cabinet_sides.png", "xdecor_cabinet_front.png"
+ }
+})
+
+register_storage("cabinet_half", S("Half Wooden Cabinet"), {
+ inv_size = 8,
+ _tt_help = S("8 inventory slots"),
+ node_box = xdecor.nodebox.slab_y(0.5, 0.5),
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {
+ "xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
+ "xdecor_half_cabinet_sides.png", "xdecor_half_cabinet_sides.png",
+ "xdecor_half_cabinet_sides.png", "xdecor_half_cabinet_front.png"
+ }
+})
+
+if minetest.get_modpath("moreblocks") then
+ minetest.register_alias("xdecor:empty_shelf", "moreblocks:empty_shelf")
+else
+ -- Node renamed from "Empty Shelf" because it was misleading.
+ -- (you can still put things in it, making it non-empty)
+ register_storage("empty_shelf", S("Plain Shelf"), {
+ _tt_help = S("24 inventory slots"),
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {
+ "default_wood.png", "default_wood.png", "default_wood.png",
+ "default_wood.png", "default_wood.png^xdecor_empty_shelf.png"
+ },
+ })
+
+ -- Update infotext of old empty_shelf nodes to "Plain Shelf"
+ minetest.register_lbm({
+ label = "Update plain shelf infotext",
+ name = "xdecor:empty_shelf_to_plain_shelf",
+ nodenames = {"xdecor:empty_shelf"},
+ run_at_every_load = false,
+ action = function(pos, node)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", S("Plain Shelf"))
+ end,
+ })
+end
+
+register_storage("multishelf", S("Multi Shelf"), {
+ _tt_help = S("24 inventory slots"),
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {
+ "default_wood.png", "default_wood.png", "default_wood.png",
+ "default_wood.png", "default_wood.png^xdecor_multishelf.png"
+ },
+})
+
+xdecor.register("candle", {
+ description = S("Candle"),
+ light_source = 12,
+ drawtype = "torchlike",
+ inventory_image = "xdecor_candle_inv.png",
+ wield_image = "xdecor_candle_wield.png",
+ paramtype2 = "wallmounted",
+ walkable = false,
+ groups = {dig_immediate = 3, attached_node = 1},
+ is_ground_content = false,
+ tiles = {
+ {
+ name = "xdecor_candle_floor.png",
+ animation = {type="vertical_frames", length = 1.5}
+ },
+ {
+ name = "xdecor_candle_hanging.png",
+ animation = {type="vertical_frames", length = 1.5}
+ },
+ {
+ name = "xdecor_candle_wall.png",
+ animation = {type="vertical_frames", length = 1.5}
+ }
+ },
+ selection_box = {
+ type = "wallmounted",
+ wall_top = {-0.25, -0.3, -0.25, 0.25, 0.5, 0.25},
+ wall_bottom = {-0.25, -0.5, -0.25, 0.25, 0.1, 0.25},
+ wall_side = {-0.5, -0.35, -0.15, -0.15, 0.4, 0.15}
+ }
+})
+
+xdecor.register("chair", {
+ description = S("Chair"),
+ tiles = {"xdecor_wood.png"},
+ sounds = default.node_sound_wood_defaults(),
+ groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 2, sittable = 1},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ node_box = xdecor.pixelbox(16, {
+ {3, 0, 11, 2, 16, 2},
+ {11, 0, 11, 2, 16, 2},
+ {5, 9, 11.5, 6, 6, 1},
+ {3, 0, 3, 2, 6, 2},
+ {11, 0, 3, 2, 6, 2},
+ {3, 6, 3, 10, 2, 8}
+ }),
+ can_dig = xdecor.sit_dig,
+ after_destruct = xdecor.sit_destruct,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ xdecor.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end,
+ _xdecor_itemframe_offset = -1.5,
+})
+
+xdecor.register("cobweb", {
+ description = S("Cobweb"),
+ drawtype = "plantlike",
+ tiles = {"xdecor_cobweb.png"},
+ inventory_image = "xdecor_cobweb.png",
+ move_resistance = 8,
+ walkable = false,
+ selection_box = {type = "regular"},
+ groups = {snappy = 3, flammable = 3},
+ is_ground_content = false,
+ sounds = default.node_sound_leaves_defaults()
+})
+
+local curtain_colors = {
+ red = { S("Red Curtain"), "wool_red.png", "wool:red" },
+}
+
+local CURTAIN_OFFSET = 1/16
+
+-- For preserve_metadata for curtains.
+-- Erases metadata from the drops
+-- because the item metadata should be empty
+-- to allow proper item stacking.
+local cleanup_curtain_meta = function(_,_,_,drops)
+ for d=1, #drops do
+ local meta = drops[d]:get_meta()
+ meta:set_string("palette_index", "")
+ end
+end
+
+for c, info in pairs(curtain_colors) do
+ local desc = info[1]
+ local base_texture = info[2]
+ local craft_item = info[3]
+ xdecor.register("curtain_" .. c, {
+ description = desc,
+ walkable = false,
+ tiles = {base_texture, "("..base_texture..")^[transformFY", base_texture},
+ use_texture_alpha = ALPHA_CLIP,
+ inventory_image = base_texture.."^xdecor_curtain_open_overlay.png^[makealpha:255,126,126",
+ wield_image = base_texture.."^xdecor_curtain_open_overlay.png^[makealpha:255,126,126",
+ drawtype = "nodebox",
+ paramtype2 = "wallmounted",
+ node_box = {
+ type = "wallmounted",
+ wall_side = { -0.5, -0.5, -0.5, -0.5+CURTAIN_OFFSET, 0.5, 0.5 },
+ wall_top = { -0.5, 0.5-CURTAIN_OFFSET, -0.5, 0.5, 0.5, 0.5 },
+ wall_bottom = { -0.5, -0.5, -0.5, 0.5, -0.5+CURTAIN_OFFSET, 0.5 },
+ },
+ groups = {dig_immediate = 3, flammable = 3},
+ is_ground_content = false,
+ on_rightclick = function(pos, node, _, itemstack)
+ minetest.set_node(pos, {name = "xdecor:curtain_open_" .. c, param2 = node.param2})
+ return itemstack
+ end,
+ preserve_metadata = cleanup_curtain_meta,
+ })
+
+ local open_tile = base_texture.."^xdecor_curtain_open_overlay.png^[makealpha:255,126,126"
+ xdecor.register("curtain_open_" .. c, {
+ tiles = {
+ open_tile,
+ "("..open_tile..")^[transformFY",
+ base_texture,
+ base_texture,
+ base_texture.."^xdecor_curtain_open_overlay_top.png^[makealpha:255,126,126",
+ base_texture.."^xdecor_curtain_open_overlay_bottom.png^[makealpha:255,126,126",
+ },
+ use_texture_alpha = ALPHA_CLIP,
+ drawtype = "nodebox",
+ paramtype2 = "wallmounted",
+ node_box = {
+ type = "wallmounted",
+ wall_side = { -0.5, -0.5, -0.5, -0.5+CURTAIN_OFFSET, 0.5, 0.5 },
+ wall_top = { -0.5, 0.5-CURTAIN_OFFSET, -0.5, 0.5, 0.5, 0.5 },
+ wall_bottom = { -0.5, -0.5, -0.5, 0.5, -0.5+CURTAIN_OFFSET, 0.5 },
+ },
+ walkable = false,
+ groups = {dig_immediate = 3, flammable = 3, not_in_creative_inventory = 1},
+ is_ground_content = false,
+ drop = "xdecor:curtain_" .. c,
+ on_rightclick = function(pos, node, _, itemstack)
+ minetest.set_node(pos, {name="xdecor:curtain_" .. c, param2 = node.param2})
+ return itemstack
+ end,
+ preserve_metadata = cleanup_curtain_meta,
+ })
+
+ minetest.register_craft({
+ output = "xdecor:curtain_" .. c .. " 4",
+ recipe = {
+ {"", craft_item, ""},
+ {"", craft_item, ""}
+ }
+ })
+end
+
+xdecor.register("cushion", {
+ description = S("Cushion"),
+ tiles = {"xdecor_cushion.png"},
+ groups = {snappy = 3, flammable = 3, fall_damage_add_percent = -50, sittable = 1},
+ is_ground_content = false,
+ on_place = minetest.rotate_node,
+ node_box = xdecor.nodebox.slab_y(0.5),
+ can_dig = xdecor.sit_dig,
+ after_destruct = xdecor.sit_destruct,
+ on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
+ xdecor.sit(pos, node, clicker, pointed_thing)
+ return itemstack
+ end
+})
+
+xdecor.register("cushion_block", {
+ description = S("Cushion Block"),
+ tiles = {"xdecor_cushion.png"},
+ groups = {snappy = 3, flammable = 3, fall_damage_add_percent = -75},
+ is_ground_content = false,
+})
+
+local function door_access(name)
+ return name:find("prison")
+end
+
+local xdecor_doors = {
+ japanese = {
+ recipe = {
+ {"group:wood", "default:paper"},
+ {"default:paper", "group:wood"},
+ {"group:wood", "default:paper"}
+ },
+ desc = S("Japanese Door"),
+ },
+ prison = {
+ recipe = {
+ {"xpanes:bar_flat", "xpanes:bar_flat",},
+ {"xpanes:bar_flat", "default:steel_ingot",},
+ {"xpanes:bar_flat", "xpanes:bar_flat"}
+ },
+ desc = S("Prison Door"),
+ sounds = default.node_sound_metal_defaults(),
+ sound_open = "xpanes_steel_bar_door_open",
+ sound_close = "xpanes_steel_bar_door_close",
+ gain_open = 0.18,
+ gain_close = 0.16,
+ },
+ rusty_prison = {
+ recipe = {
+ {"xpanes:rusty_bar_flat", "xpanes:rusty_bar_flat",},
+ {"xpanes:rusty_bar_flat", "xpanes:rusty_bar_flat",},
+ {"xpanes:rusty_bar_flat", "xpanes:rusty_bar_flat"}
+ },
+ desc = S("Rusty Prison Door"),
+ sounds = default.node_sound_metal_defaults(),
+ sound_open = "xpanes_steel_bar_door_open",
+ sound_close = "xpanes_steel_bar_door_close",
+ gain_open = 0.21,
+ gain_close = 0.19,
+ },
+ screen = {
+ recipe = {
+ {"group:wood", "group:wood"},
+ {"xpanes:chainlink_flat", "xpanes:chainlink_flat"},
+ {"group:wood", "group:wood"}
+ },
+ desc = S("Screen Door"),
+ },
+ slide = {
+ recipe = {
+ {"default:paper", "default:paper"},
+ {"default:paper", "default:paper"},
+ {"group:wood", "group:wood"}
+ },
+ desc = S("Paper Door"),
+ },
+ woodglass = {
+ recipe = {
+ {"default:glass", "default:glass"},
+ {"group:wood", "group:wood"},
+ {"group:wood", "group:wood"}
+ },
+ desc = S("Woodglass Door"),
+ },
+}
+
+local mesecons_register
+
+if minetest.global_exists("mesecon") then
+ mesecons_register = { effector = {
+ action_on = function(pos, node)
+ local door = doors.get(pos)
+ if door then
+ door:open()
+ end
+ end,
+ action_off = function(pos, node)
+ local door = doors.get(pos)
+ if door then
+ door:close()
+ end
+ end,
+ rules = mesecon.rules.pplate
+ }}
+end
+
+for name, def in pairs(xdecor_doors) do
+ if not doors.register then break end
+ doors.register(name .. "_door", {
+ tiles = {
+ {name = "xdecor_" .. name .. "_door.png", backface_culling = true}
+ },
+ description = def.desc,
+ inventory_image = "xdecor_" .. name .. "_door_inv.png",
+ sounds = def.sounds,
+ sound_open = def.sound_open,
+ sound_close = def.sound_close,
+ gain_open = def.gain_open,
+ gain_close = def.gain_close,
+ protected = door_access(name),
+ groups = {choppy = 2, cracky = 2, oddly_breakable_by_hand = 1, door = 1, node = 1},
+ recipe = def.recipe,
+ mesecons = mesecons_register,
+ })
+end
+
+xdecor.register("enderchest", {
+ description = S("Ender Chest"),
+ _tt_help = S("Interdimensional inventory"),
+ tiles = {
+ "xdecor_enderchest_top.png", "xdecor_enderchest_top.png",
+ "xdecor_enderchest_side.png", "xdecor_enderchest_side.png",
+ "xdecor_enderchest_side.png", "xdecor_enderchest_front.png"
+ },
+ groups = {cracky = 1, choppy = 1},
+ is_ground_content = false,
+ sounds = default.node_sound_stone_defaults(),
+ on_rotate = screwdriver.rotate_simple,
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", [[ size[8,9]
+ list[current_player;enderchest;0,0;8,4;]
+ list[current_player;main;0,5;8,4;]
+ listring[current_player;enderchest]
+ listring[current_player;main] ]]
+ .. xdecor.xbg .. default.get_hotbar_bg(0,5))
+
+ meta:set_string("infotext", S("Ender Chest"))
+ end
+})
+
+minetest.register_on_joinplayer(function(player)
+ local inv = player:get_inventory()
+ inv:set_size("enderchest", 8*4)
+end)
+
+xdecor.register("ivy", {
+ description = S("Ivy"),
+ drawtype = "signlike",
+ walkable = false,
+ climbable = true,
+ groups = {snappy = 3, attached_node = 1, plant = 1, flammable = 3},
+ paramtype2 = "wallmounted",
+ selection_box = {type="wallmounted"},
+ tiles = {"xdecor_ivy.png"},
+ inventory_image = "xdecor_ivy.png",
+ wield_image = "xdecor_ivy.png",
+ sounds = default.node_sound_leaves_defaults()
+})
+
+xdecor.register("rooster", {
+ description = S("Weathercock"),
+ drawtype = "torchlike",
+ inventory_image = "xdecor_rooster.png",
+ walkable = false,
+ groups = {snappy = 3, attached_node = 1},
+ is_ground_content = false,
+ tiles = {"xdecor_rooster.png"},
+ sounds = default.node_sound_metal_defaults(),
+})
+
+-- Lantern which attaches to the floor.
+-- Has a hanging variant
+xdecor.register("lantern", {
+ description = S("Lantern"),
+ light_source = 13,
+ drawtype = "plantlike",
+ inventory_image = "xdecor_lantern_inv.png",
+ wield_image = "xdecor_lantern_inv.png",
+ walkable = false,
+ groups = {snappy = 3, attached_node = 3},
+ is_ground_content = false,
+ tiles = {
+ {
+ name = "xdecor_lantern.png",
+ animation = {type="vertical_frames", length = 1.5}
+ }
+ },
+ selection_box = xdecor.pixelbox(16, {{4, 0, 4, 8, 16, 8}}),
+ sounds = default.node_sound_metal_defaults(),
+ on_place = function(itemstack, placer, pointed_thing)
+ if pointed_thing.type ~= "node" then
+ return itemstack
+ end
+
+ -- Use pointed node's on_rightclick function first, if present
+ if placer and not placer:get_player_control().sneak then
+ local node = minetest.get_node(pointed_thing.under)
+ if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
+ return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
+ end
+ end
+ -- Check protection
+ if minetest.is_protected(pointed_thing.above, placer:get_player_name()) and
+ not minetest.check_player_privs(placer, "protection_bypass") then
+ minetest.record_protection_violation(pointed_thing.above, placer:get_player_name())
+ return itemstack
+ end
+
+ -- Decide whether the lantern attaches the the floor
+ -- (default) or the ceiling.
+ local leftover, place_pos, nodename
+ local up = vector.new(pointed_thing.above.x, pointed_thing.above.y+1, pointed_thing.above.z)
+ local upnode = minetest.get_node(up)
+ local updef = minetest.registered_nodes[upnode.name]
+ local down = vector.new(pointed_thing.above.x, pointed_thing.above.y-1, pointed_thing.above.z)
+ local downnode = minetest.get_node(down)
+ local downdef = minetest.registered_nodes[downnode.name]
+ local sound_play = false
+ if pointed_thing.under.y > pointed_thing.above.y then
+ nodename = "xdecor:lantern_hanging"
+ if downdef and not downdef.walkable then
+ sound_play = true
+ end
+ elseif downdef and not downdef.walkable and updef and updef.walkable then
+ nodename = "xdecor:lantern_hanging"
+ sound_play = true
+ else
+ nodename = "xdecor:lantern"
+ end
+ leftover, place_pos = minetest.item_place_node(ItemStack(nodename), placer, pointed_thing)
+ if place_pos == nil then
+ return
+ end
+ if leftover:get_count() == 0 and
+ not minetest.is_creative_enabled(placer:get_player_name()) then
+ itemstack:take_item()
+ end
+
+ if sound_play then
+ minetest.sound_play(default.node_sound_metal_defaults().place, {pos=place_pos}, true)
+ end
+
+ return itemstack
+ end,
+})
+
+-- Same as lantern, but attaches to ceiling
+xdecor.register("lantern_hanging", {
+ description = S("Hanging Lantern"),
+ light_source = 13,
+ drawtype = "plantlike",
+ inventory_image = "xdecor_lantern_inv.png^xdecor_lantern_hanging_overlay_inv.png",
+ wield_image = "xdecor_lantern_inv.png",
+ walkable = false,
+ groups = {snappy = 3, attached_node = 4, not_in_creative_inventory = 1},
+ is_ground_content = false,
+ tiles = {
+ {
+ name = "xdecor_lantern.png",
+ animation = {type="vertical_frames", length = 1.5}
+ }
+ },
+ selection_box = xdecor.pixelbox(16, {{4, 0, 4, 8, 16, 8}}),
+ sounds = default.node_sound_metal_defaults(),
+ drop = "xdecor:lantern",
+})
+
+-- Update legacy lantern (back when they were wallmounted)
+-- that are hanging to the proper node.
+minetest.register_lbm({
+ label = "Update hanging lanterns",
+ name = "xdecor:update_hanging_lanterns",
+ nodenames = {"xdecor:lantern"},
+ run_at_every_load = false,
+ action = function(pos, node)
+ if node.param2 == 0 then -- wallmounted 0 value means attached to the ceiling
+ -- Only convert the node if it needs to hang
+ -- (walkable node above, non-walkable node below)
+ local up = vector.new(pos.x, pos.y+1, pos.z)
+ local upnode = minetest.get_node(up)
+ local updef = minetest.registered_nodes[upnode.name]
+ local down = vector.new(pos.x, pos.y-1, pos.z)
+ local downnode = minetest.get_node(down)
+ local downdef = minetest.registered_nodes[downnode.name]
+ if updef and updef.walkable and downdef and not downdef.walkable then
+ minetest.swap_node(pos, {name="xdecor:lantern_hanging"})
+ end
+ end
+ end,
+})
+
+local xdecor_lightbox = {
+ iron = S("Steel Lattice Light Box"),
+ wooden = S("Wooden Cross Light Box"),
+ wooden2 = S("Wooden Rhombus Light Box"),
+}
+
+for l, desc in pairs(xdecor_lightbox) do
+ xdecor.register(l .. "_lightbox", {
+ description = desc,
+ tiles = {"xdecor_" .. l .. "_lightbox.png"},
+ groups = {cracky = 3, choppy = 3, oddly_breakable_by_hand = 2},
+ is_ground_content = false,
+ light_source = 13,
+ sounds = default.node_sound_glass_defaults()
+ })
+end
+
+local xdecor_potted = {
+ dandelion_white = S("Potted White Dandelion"),
+ dandelion_yellow = S("Potted Yellow Dandelion"),
+ geranium = S("Potted Geranium"),
+ rose = S("Potted Rose"),
+ tulip = S("Potted Tulip"),
+ viola = S("Potted Viola"),
+}
+
+for f, desc in pairs(xdecor_potted) do
+ xdecor.register("potted_" .. f, {
+ description = desc,
+ walkable = false,
+ groups = {snappy = 3, flammable = 3, plant = 1, flower = 1},
+ is_ground_content = false,
+ tiles = {"xdecor_" .. f .. "_pot.png"},
+ inventory_image = "xdecor_" .. f .. "_pot.png",
+ drawtype = "plantlike",
+ sounds = default.node_sound_leaves_defaults({
+ place = default.node_sound_stone_defaults().place,
+ dug = default.node_sound_stone_defaults().dug,
+ }),
+ selection_box = xdecor.nodebox.slab_y(0.3)
+ })
+
+ minetest.register_craft({
+ output = "xdecor:potted_" .. f,
+ recipe = {
+ {"default:clay_brick", "flowers:" .. f, "default:clay_brick"},
+ {"", "default:clay_brick", ""}
+ }
+ })
+end
+
+local painting_box = {
+ type = "wallmounted",
+ wall_top = {-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125},
+ wall_bottom = {-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125},
+ wall_side = {-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375}
+}
+
+xdecor.register("painting_1", {
+ description = S("Painting"),
+ tiles = {"xdecor_painting_1.png","xdecor_painting_1.png^[transformR180","xdecor_painting_1.png"},
+ use_texture_alpha = ALPHA_OPAQUE,
+ inventory_image = "xdecor_painting_empty.png",
+ wield_image = "xdecor_painting_empty.png",
+ paramtype2 = "wallmounted",
+ wallmounted_rotate_vertical = true,
+ sunlight_propagates = true,
+ groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 2, attached_node = 1},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ node_box = painting_box,
+ node_placement_prediction = "",
+ on_place = function(itemstack, placer, pointed_thing)
+ if pointed_thing.type ~= "node" then
+ return itemstack
+ end
+
+ -- Use pointed node's on_rightclick function first, if present
+ if placer and not placer:get_player_control().sneak then
+ local node = minetest.get_node(pointed_thing.under)
+ if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
+ return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
+ end
+ end
+ -- Check protection
+ if minetest.is_protected(pointed_thing.above, placer:get_player_name()) and
+ not minetest.check_player_privs(placer, "protection_bypass") then
+ minetest.record_protection_violation(pointed_thing.above, placer:get_player_name())
+ return itemstack
+ end
+
+ local num = math.random(4)
+ local leftover, place_pos = minetest.item_place_node(
+ ItemStack("xdecor:painting_" .. num), placer, pointed_thing)
+
+ if not place_pos then
+ return itemstack
+ end
+
+ if leftover:get_count() == 0 and
+ not minetest.is_creative_enabled(placer:get_player_name()) then
+ itemstack:take_item()
+ end
+
+ -- Play 'place' sound manually
+ minetest.sound_play(default.node_sound_wood_defaults().place, {pos=place_pos}, true)
+
+ return itemstack
+ end,
+ sounds = default.node_sound_wood_defaults(),
+})
+
+for i = 2, 4 do
+ xdecor.register("painting_" .. i, {
+ tiles = {"xdecor_painting_"..i..".png","xdecor_painting_"..i..".png^[transformR180","xdecor_painting_"..i..".png"},
+ use_texture_alpha = ALPHA_OPAQUE,
+ paramtype2 = "wallmounted",
+ wallmounted_rotate_vertical = true,
+ drop = "xdecor:painting_1",
+ sunlight_propagates = true,
+ groups = {
+ choppy = 3,
+ oddly_breakable_by_hand = 2,
+ flammable = 2,
+ attached_node = 1,
+ not_in_creative_inventory = 1
+ },
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ node_box = painting_box
+ })
+end
+
+xdecor.register("stonepath", {
+ description = S("Garden Stone Path"),
+ tiles = {"default_stone.png"},
+ groups = {snappy = 3},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ sounds = default.node_sound_stone_defaults(),
+ sunlight_propagates = true,
+ node_box = xdecor.pixelbox(16, {
+ {8, 0, 8, 6, .5, 6}, {1, 0, 1, 6, .5, 6},
+ {1, 0, 10, 5, .5, 5}, {10, 0, 2, 4, .5, 4}
+ }),
+ selection_box = xdecor.nodebox.slab_y(0.05)
+})
+
+local function register_hard_node(name, desc, def)
+ def = def or {}
+ xdecor.register(name, {
+ description = desc,
+ tiles = def.tiles or {"xdecor_" .. name .. ".png"},
+ groups = def.groups or {cracky = 1},
+ is_ground_content = false,
+ sounds = def.sounds or default.node_sound_stone_defaults()
+ })
+end
+
+register_hard_node("cactusbrick", S("Cactus Brick"))
+register_hard_node("coalstone_tile", S("Coal Stone Tile"))
+register_hard_node("desertstone_tile", S("Polished Desert Stone Block"))
+register_hard_node("hard_clay", S("Hardened Clay"))
+register_hard_node("moonbrick", S("Moon Brick"))
+register_hard_node("stone_rune", S("Runestone"))
+
+-- renamed from stone_tile to fix naming collision with moreblocks
+-- mod for the registrations under the 'stairs:' namespace
+register_hard_node("stone_tile_x", S("Polished Stone Block"), {
+ tiles = {"xdecor_stone_tile.png"},
+})
+xdecor.register_legacy_aliases("stone_tile", "stone_tile_x")
+
+register_hard_node("packed_ice", S("Packed Ice"), {
+ groups = {cracky = 1, cools_lava = 1, slippery = 3},
+ sounds = default.node_sound_glass_defaults()
+})
+
+-- renamed from wood_tile to fix naming collision with moreblocks
+-- mod for the registrations under the 'stairs:' namespace
+register_hard_node("wood_tile_x", S("Wooden Tile"), {
+ groups = {choppy = 1, wood = 1, flammable = 2},
+ sounds = default.node_sound_wood_defaults(),
+ tiles = {"xdecor_wood_tile.png"},
+})
+xdecor.register_legacy_aliases("wood_tile", "wood_tile_x")
+
+xdecor.register("table", {
+ description = S("Table"),
+ tiles = {"xdecor_wood.png"},
+ groups = {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ node_box = xdecor.pixelbox(16, {
+ {0, 14, 0, 16, 2, 16}, {5.5, 0, 5.5, 5, 14, 6}
+ })
+})
+
+xdecor.register("tatami", {
+ description = S("Tatami"),
+ tiles = {"xdecor_tatami.png"},
+ wield_image = "xdecor_tatami.png",
+ groups = {snappy = 3, flammable = 3},
+ is_ground_content = false,
+ sunlight_propagates = true,
+ node_box = xdecor.nodebox.slab_y(0.0625)
+})
+
+xdecor.register("trampoline", {
+ description = S("Trampoline"),
+ tiles = {"xdecor_trampoline.png", "xdecor_trampoline_bottom.png", "xdecor_trampoline_sides.png"},
+ use_texture_alpha = ALPHA_CLIP,
+ groups = {cracky = 3, oddly_breakable_by_hand = 1, fall_damage_add_percent = -80, bouncy = 90},
+ is_ground_content = false,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ { -0.5, -1/16, -0.5, 0.5, 0, 0.5 }, -- bouncy top
+ { -0.5, -0.5, -0.5, -3/16, 0, -3/16 }, -- leg 1
+ { 3/16, -0.5, -0.5, 0.5, 0, -3/16 }, -- leg 2
+ { -0.5, -0.5, 3/16, -3/16, 0, 0.5 }, -- leg 3
+ { 3/16, -0.5, 3/16, 0.5, 0, 0.5 }, -- leg 4
+ { -3/16, -5/16, -0.5, 3/16, -1/16, -7/16 }, -- connector 1
+ { -0.5, -5/16, -3/16, -7/16, -1/16, 3/16 }, -- connector 2
+ { -3/16, -5/16, 7/16, 3/16, -1/16, 0.5 }, -- connector 3
+ { 7/16, -5/16, -3/16, 0.5, -1/16, 3/16 }, -- connector 4
+ },
+ },
+ selection_box = xdecor.nodebox.slab_y(0.5),
+ collision_box = xdecor.nodebox.slab_y(0.5),
+ sounds = default.node_sound_defaults({
+ footstep = {
+ name = "xdecor_bouncy",
+ gain = 0.8
+ },
+ dig = default.node_sound_wood_defaults().dig,
+ }),
+})
+
+xdecor.register("tv", {
+ description = S("Television"),
+ light_source = 11,
+ groups = {cracky = 3, oddly_breakable_by_hand = 2},
+ is_ground_content = false,
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {
+ "xdecor_television_left.png^[transformR270",
+ "xdecor_television_left.png^[transformR90",
+ "xdecor_television_left.png^[transformFX",
+ "xdecor_television_left.png", "xdecor_television_back.png",
+ {
+ name = "xdecor_television_front_animated.png",
+ animation = {type = "vertical_frames", length = 80.0}
+ }
+ },
+ sounds = default.node_sound_metal_defaults(),
+})
+
+xdecor.register("woodframed_glass", {
+ description = S("Wood Framed Glass"),
+ drawtype = "glasslike_framed",
+ sunlight_propagates = true,
+ tiles = {"xdecor_woodframed_glass.png", "xdecor_woodframed_glass_detail.png"},
+ use_texture_alpha = ALPHA_CLIP,
+ groups = {cracky = 2, oddly_breakable_by_hand = 1},
+ is_ground_content = false,
+ sounds = default.node_sound_glass_defaults(),
+ _xdecor_custom_noncube_tiles = {
+ stair = {
+ "xdecor_woodframed_glass_split.png",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass_stairside_flip.png",
+ "xdecor_woodframed_glass_stairside.png",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass_split.png",
+ },
+ stair_inner = {
+ "xdecor_woodframed_glass_stairside.png^[transformR270",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass_stairside_flip.png",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass_stairside.png",
+ },
+ stair_outer = {
+ "xdecor_woodframed_glass_stairside.png^[transformR90",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass_outer_stairside.png",
+ "xdecor_woodframed_glass_stairside_flip.png",
+ "xdecor_woodframed_glass_stairside.png^[transformR90",
+ "xdecor_woodframed_glass_outer_stairside.png",
+ },
+ halfstair = {
+ "xdecor_woodframed_glass_cube.png",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass_stairside_flip.png",
+ "xdecor_woodframed_glass_stairside.png",
+ "xdecor_woodframed_glass_split.png^[transformR90",
+ "xdecor_woodframed_glass_cube.png",
+ },
+ slab = {
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass.png",
+ "xdecor_woodframed_glass_split.png",
+ },
+ cube = { "xdecor_woodframed_glass_cube.png" },
+ thinstair = { "xdecor_woodframed_glass_split.png" },
+ micropanel = { "xdecor_woodframed_glass_split.png" },
+ panel = {
+ "xdecor_woodframed_glass_split.png",
+ "xdecor_woodframed_glass_split.png",
+ "xdecor_woodframed_glass_cube.png",
+ "xdecor_woodframed_glass_cube.png",
+ "xdecor_woodframed_glass_split.png",
+ },
+ },
+})
+
+local devices = {
+ { "radio", S("Radio"), default.node_sound_metal_defaults() },
+ --~ as in "loudspeaker"
+ { "speaker", S("Speaker"), default.node_sound_metal_defaults() },
+}
+for _, v in pairs(devices) do
+ xdecor.register(v[1], {
+ description = v[2],
+ on_rotate = screwdriver.rotate_simple,
+ tiles = {
+ "xdecor_" .. v[1] .. "_top.png",
+ "xdecor_" .. v[1] .. "_side.png",
+ "xdecor_" .. v[1] .. "_side.png",
+ "xdecor_" .. v[1] .. "_side.png",
+ "xdecor_" .. v[1] .. "_back.png",
+ "xdecor_" .. v[1] .. "_front.png",
+ },
+ groups = {cracky = 2, not_cuttable = 1},
+ is_ground_content = false,
+ sounds = v[3],
+ })
+end
diff --git a/mods/xdecor/src/recipes.lua b/mods/xdecor/src/recipes.lua
new file mode 100644
index 00000000..e2089b7f
--- /dev/null
+++ b/mods/xdecor/src/recipes.lua
@@ -0,0 +1,405 @@
+minetest.register_craft({
+ output = "xdecor:baricade",
+ recipe = {
+ {"group:stick", "", "group:stick"},
+ {"", "default:steel_ingot", ""},
+ {"group:stick", "", "group:stick"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:barrel",
+ recipe = {
+ {"group:wood", "group:wood", "group:wood"},
+ {"default:iron_lump", "", "default:iron_lump"},
+ {"group:wood", "group:wood", "group:wood"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:candle",
+ recipe = {
+ {"default:torch"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:cabinet",
+ recipe = {
+ {"group:wood", "group:wood", "group:wood"},
+ {"doors:trapdoor", "", "doors:trapdoor"},
+ {"group:wood", "group:wood", "group:wood"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:cabinet_half 2",
+ recipe = {
+ {"xdecor:cabinet"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:cactusbrick",
+ recipe = {
+ {"default:brick", "default:cactus"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:chair",
+ recipe = {
+ {"group:stick", "", ""},
+ {"group:stick", "group:stick", "group:stick"},
+ {"group:stick", "", "group:stick"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:coalstone_tile 4",
+ recipe = {
+ {"default:coalblock", "default:stone"},
+ {"default:stone", "default:coalblock"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:cobweb",
+ recipe = {
+ {"farming:string", "", "farming:string"},
+ {"", "farming:string", ""},
+ {"farming:string", "", "farming:string"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:cushion 3",
+ recipe = {
+ {"wool:red", "wool:red", "wool:red"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:cushion_block",
+ recipe = {
+ {"xdecor:cushion"},
+ {"xdecor:cushion"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:desertstone_tile 4",
+ recipe = {
+ {"default:desert_stone_block", "default:desert_stone_block"},
+ {"default:desert_stone_block", "default:desert_stone_block"},
+ }
+})
+
+if not minetest.get_modpath("moreblocks") then
+ minetest.register_craft({
+ output = "xdecor:empty_shelf",
+ recipe = {
+ {"group:wood", "group:wood", "group:wood"},
+ {"", "", ""},
+ {"group:wood", "group:wood", "group:wood"}
+ }
+ })
+end
+
+minetest.register_craft({
+ output = "xdecor:enderchest",
+ recipe = {
+ {"", "default:obsidian", ""},
+ {"default:obsidian", "default:chest", "default:obsidian"},
+ {"", "default:obsidian", ""}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:hard_clay",
+ recipe = {
+ {"default:clay", "default:clay"},
+ {"default:clay", "default:clay"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:iron_lightbox",
+ recipe = {
+ {"xpanes:bar_flat", "default:torch", "xpanes:bar_flat"},
+ {"xpanes:bar_flat", "default:glass", "xpanes:bar_flat"},
+ {"xpanes:bar_flat", "default:torch", "xpanes:bar_flat"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:ivy 4",
+ recipe = {
+ {"group:leaves"},
+ {"group:leaves"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:lantern",
+ recipe = {
+ {"default:iron_lump"},
+ {"default:torch"},
+ {"default:iron_lump"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:moonbrick",
+ recipe = {
+ {"default:brick", "default:stone"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:multishelf",
+ recipe = {
+ {"group:wood", "group:wood", "group:wood"},
+ {"group:vessel", "group:book", "group:vessel"},
+ {"group:wood", "group:wood", "group:wood"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:packed_ice",
+ recipe = {
+ {"", "default:ice", ""},
+ {"default:ice", "", "default:ice"},
+ {"", "default:ice", ""},
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:painting_1",
+ recipe = {
+ {"default:sign_wall_wood", "group:dye"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:radio",
+ type = "shapeless",
+ recipe = {"xdecor:speaker", "xdecor:speaker"}
+})
+
+minetest.register_craft({
+ output = "xdecor:rooster",
+ recipe = {
+ {"default:gold_ingot", "", "default:gold_ingot"},
+ {"", "default:gold_ingot", ""},
+ {"default:gold_ingot", "", "default:gold_ingot"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:speaker",
+ recipe = {
+ {"default:gold_ingot", "default:copper_ingot", "default:gold_ingot"},
+ {"default:copper_ingot", "", "default:copper_ingot"},
+ {"default:gold_ingot", "default:copper_ingot", "default:gold_ingot"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:stone_tile_x 4",
+ recipe = {
+ {"default:stone_block", "default:stone_block"},
+ {"default:stone_block", "default:stone_block"},
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:stone_rune 4",
+ recipe = {
+ {"default:stone_block", "default:stone_block", "default:stone_block"},
+ {"default:stone_block", "", "default:stone_block"},
+ {"default:stone_block", "default:stone_block", "default:stone_block"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:stonepath 16",
+ recipe = {
+ {"stairs:slab_cobble", "", "stairs:slab_cobble"},
+ {"", "stairs:slab_cobble", ""},
+ {"stairs:slab_cobble", "", "stairs:slab_cobble"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:table",
+ recipe = {
+ {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
+ {"", "group:stick", ""},
+ {"", "group:stick", ""}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:tatami",
+ recipe = {
+ {"farming:wheat", "farming:wheat", "farming:wheat"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:trampoline",
+ recipe = {
+ {"farming:string", "farming:string", "farming:string"},
+ {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
+ {"default:steel_ingot", "", "default:steel_ingot"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:tv",
+ recipe = {
+ {"default:steel_ingot", "default:copper_ingot", "default:steel_ingot"},
+ {"default:steel_ingot", "default:glass", "default:steel_ingot"},
+ {"default:steel_ingot", "default:copper_ingot", "default:steel_ingot"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:woodframed_glass",
+ recipe = {
+ {"group:stick", "group:stick", "group:stick"},
+ {"group:stick", "default:glass", "group:stick"},
+ {"group:stick", "group:stick", "group:stick"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:wood_tile_x 2",
+ recipe = {
+ {"", "group:wood", ""},
+ {"group:wood", "", "group:wood"},
+ {"", "group:wood", ""}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:wooden_lightbox",
+ recipe = {
+ {"group:stick", "default:torch", "group:stick"},
+ {"group:stick", "default:glass", "group:stick"},
+ {"group:stick", "default:torch", "group:stick"}
+ }
+})
+
+minetest.register_craft({
+ output = "xdecor:wooden2_lightbox",
+ recipe = {
+ {"group:stick", "group:stick", "group:stick"},
+ {"default:torch", "default:glass", "default:torch"},
+ {"group:stick", "group:stick", "group:stick"}
+ },
+})
+
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:empty_shelf",
+ burntime = 30,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:multishelf",
+ burntime = 30,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:cabinet",
+ burntime = 30,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:barrel",
+ burntime = 30,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:cabinet_half",
+ burntime = 15,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:workbench",
+ burntime = 15,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:table",
+ burntime = 12,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "doors:woodglass_door",
+ burntime = 13,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "doors:screen_door",
+ burntime = 10,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "doors:slide_door",
+ burntime = 8,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xpanes:wood_frame_flat",
+ burntime = 5,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xpanes:bamboo_frame_flat",
+ burntime = 3,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "doors:japanese_door",
+ burntime = 8,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:chair",
+ burntime = 6,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:baricade",
+ burntime = 6,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:wood_tile_x",
+ burntime = 10,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "realchess:chessboard",
+ burntime = 4,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:painting_1",
+ burntime = 3,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:tatami",
+ burntime = 1,
+})
+minetest.register_craft({
+ type = "fuel",
+ recipe = "xdecor:ivy",
+ burntime = 1,
+})
+
diff --git a/mods/xdecor/src/rope.lua b/mods/xdecor/src/rope.lua
new file mode 100644
index 00000000..fb4acd93
--- /dev/null
+++ b/mods/xdecor/src/rope.lua
@@ -0,0 +1,132 @@
+local rope = {}
+local S = minetest.get_translator("xdecor")
+
+-- Maximum length a rope can extend to
+local MAX_ROPES = 30
+
+local ropesounds = default.node_sound_leaves_defaults()
+
+-- Code by Mirko K. (modified by Temperest, Wulfsdad, kilbith and Wuzzy) (License: GPL).
+function rope.place(itemstack, placer, pointed_thing)
+ local creative = minetest.is_creative_enabled(placer:get_player_name())
+ local protection_bypass = minetest.check_player_privs(placer, "protection_bypass")
+ local pname = placer:get_player_name()
+ if pointed_thing.type == "node" then
+ -- Use pointed node's on_rightclick function first, if present
+ if placer and not placer:get_player_control().sneak then
+ local node = minetest.get_node(pointed_thing.under)
+ if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
+ return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
+ end
+ end
+ local pos = pointed_thing.above
+ -- Check protection
+ if minetest.is_protected(pos, pname) and not protection_bypass then
+ minetest.record_protection_violation(pos, pname)
+ return itemstack
+ end
+
+ local oldnode = minetest.get_node(pos)
+ local stackname = itemstack:get_name()
+ -- Limit rope length to max. stack size or MAX_ROPES (whatever is smaller).
+ -- Prevents the rope to extend infinitely in Creative Mode.
+ local max_ropes = math.min(itemstack:get_stack_max(), MAX_ROPES)
+
+ -- Start placing ropes and extend it downwards until we hit an obstacle,
+ -- run out of ropes or hit the maximum rope length.
+ local start_pos = table.copy(pos)
+ local ropes_to_place = 0
+ local new_rope_nodes = {}
+ while oldnode.name == "air" and (creative or (ropes_to_place < itemstack:get_count())) and ropes_to_place < max_ropes do
+ -- Stop extending rope into protected area
+ if minetest.is_protected(pos, pname) and not protection_bypass then
+ break
+ end
+
+ table.insert(new_rope_nodes, table.copy(pos))
+ pos.y = pos.y - 1
+ oldnode = minetest.get_node(pos)
+ ropes_to_place = ropes_to_place + 1
+ end
+ local newnode = {name = stackname}
+ if ropes_to_place == 1 then
+ minetest.set_node(new_rope_nodes[1], newnode)
+ else
+ minetest.bulk_set_node(new_rope_nodes, newnode)
+ end
+ if not creative then
+ itemstack:take_item(ropes_to_place)
+ end
+
+ -- Play placement sound manually
+ if ropes_to_place > 0 then
+ minetest.sound_play(ropesounds.place, {pos=start_pos}, true)
+ end
+ end
+
+ return itemstack
+end
+
+function rope.remove(pos, oldnode, digger, rope_name)
+ local num = 0
+ local below = {x = pos.x, y = pos.y, z = pos.z}
+ local digger_inv = digger:get_inventory()
+
+ while minetest.get_node(below).name == rope_name do
+ minetest.remove_node(below)
+ below.y = below.y - 1
+ num = num + 1
+ end
+
+ if num == 0 then return end
+
+ -- Play dig sound manually
+ minetest.sound_play(ropesounds.dug, {pos=pos}, true)
+
+ -- Give/drop rope items
+ local creative = minetest.is_creative_enabled(digger:get_player_name())
+ if not creative or not digger_inv:contains_item("main", rope_name) then
+ if creative then
+ num = 1
+ end
+ local item = rope_name.." "..num
+ local leftover = digger_inv:add_item("main", rope_name.." "..num)
+ if not leftover:is_empty() then
+ minetest.add_item(pos, leftover)
+ end
+ end
+
+ return true
+end
+
+xdecor.register("rope", {
+ description = S("Rope"),
+ drawtype = "plantlike",
+ walkable = false,
+ climbable = true,
+ groups = {dig_immediate = 3, flammable = 3},
+ is_ground_content = false,
+ tiles = {"xdecor_rope.png"},
+ inventory_image = "xdecor_rope_inv.png",
+ wield_image = "xdecor_rope_inv.png",
+ selection_box = xdecor.pixelbox(8, {{3, 0, 3, 2, 8, 2}}),
+ node_placement_prediction = "",
+ on_place = rope.place,
+
+ after_dig_node = function(pos, oldnode, oldmetadata, digger)
+ pos = vector.new(pos.x, pos.y-1, pos.z)
+ rope.remove(pos, oldnode, digger, "xdecor:rope")
+ end,
+ sounds = ropesounds,
+})
+
+-- Recipes
+
+minetest.register_craft({
+ output = "xdecor:rope",
+ recipe = {
+ {"farming:string"},
+ {"farming:string"},
+ {"farming:string"}
+ }
+})
diff --git a/mods/xdecor/src/workbench.lua b/mods/xdecor/src/workbench.lua
new file mode 100644
index 00000000..cb28492b
--- /dev/null
+++ b/mods/xdecor/src/workbench.lua
@@ -0,0 +1,602 @@
+local workbench = {}
+local registered_cuttable_nodes = {}
+local special_cuts = {}
+
+screwdriver = screwdriver or {}
+local min, ceil = math.min, math.ceil
+local S = minetest.get_translator("xdecor")
+local FS = function(...) return minetest.formspec_escape(S(...)) end
+
+local DEFAULT_HAMMER_REPAIR = 500
+local DEFAULT_HAMMER_REPAIR_COST = 700
+
+
+-- Nodeboxes definitions
+workbench.defs = {
+ -- Name Yield Nodeboxes (X Y Z W H L) Description
+ {"nanoslab", 16, {{ 0, 0, 0, 8, 1, 8 }}, S("Nanoslab")},
+ {"micropanel", 16, {{ 0, 0, 0, 16, 1, 8 }}, S("Micropanel")},
+ {"microslab", 8, {{ 0, 0, 0, 16, 1, 16 }}, S("Microslab")},
+ {"thinstair", 8, {{ 0, 7, 0, 16, 1, 8 },
+ { 0, 15, 8, 16, 1, 8 }}, S("Thin Stair")},
+ {"cube", 4, {{ 0, 0, 0, 8, 8, 8 }}, S("Cube")},
+ {"panel", 4, {{ 0, 0, 0, 16, 8, 8 }}, S("Panel")},
+ {"slab", 2, nil, S("Slab") },
+ {"doublepanel", 2, {{ 0, 0, 0, 16, 8, 8 },
+ { 0, 8, 8, 16, 8, 8 }}, S("Double Panel")},
+ {"halfstair", 2, {{ 0, 0, 0, 8, 8, 16 },
+ { 0, 8, 8, 8, 8, 8 }}, S("Half-Stair")},
+ {"stair_outer", 1, nil, nil},
+ {"stair", 1, nil, S("Stair")},
+ {"stair_inner", 1, nil, nil},
+}
+
+local custom_repairable = {}
+function xdecor:register_repairable(item)
+ custom_repairable[item] = true
+end
+
+-- Tools allowed to be repaired
+function workbench:repairable(stack)
+ -- Explicitly registered as repairable: Overrides everything else
+ if custom_repairable[stack] then
+ return true
+ end
+ -- no repair if non-tool
+ if not minetest.registered_tools[stack] then
+ return false
+ end
+ -- no repair if disable_repair group
+ if minetest.get_item_group(stack, "disable_repair") == 1 then
+ return false
+ end
+ return true
+end
+
+-- Returns true if item can be cut into basic stairs and slabs
+function workbench:cuttable(itemname)
+ local split = string.split(itemname, ":")
+ if split and split[1] and split[2] then
+ if minetest.registered_nodes["stairs:stair_"..split[2]] ~= nil or
+ minetest.registered_nodes["stairs:slab_"..split[2]] ~= nil then
+ return true
+ end
+ end
+ if registered_cuttable_nodes[itemname] == true then
+ return true
+ end
+ return false
+end
+
+-- Returns true if item can be cut into xdecor extended shapes (thinslab, panel, cube, etc.)
+function workbench:cuttable_extended(itemname)
+ return registered_cuttable_nodes[itemname] == true
+end
+
+-- method to allow other mods to check if an item is repairable
+function xdecor:is_repairable(stack)
+ return workbench:repairable(stack)
+end
+
+function workbench:get_output(inv, input, name)
+ local output = {}
+ local extended = workbench:cuttable_extended(input:get_name())
+ for i = 1, #self.defs do
+ local nbox = self.defs[i]
+ local cuttype = nbox[1]
+ local count = nbox[2] * input:get_count()
+ local max_count = input:get_stack_max()
+ if count > max_count then
+ -- Limit count to maximum multiple to avoid waste
+ count = nbox[2] * math.floor(max_count / nbox[2])
+ end
+ local was_cut = false
+ if extended or nbox[3] == nil then
+ local item = name .. "_" .. cuttype
+
+ item = nbox[3] and item or "stairs:" .. cuttype .. "_" .. name:match(":(.*)")
+ if minetest.registered_items[item] then
+ output[i] = item .. " " .. count
+ was_cut = true
+ end
+ end
+ if not was_cut and special_cuts[input:get_name()] ~= nil then
+ local cut = special_cuts[input:get_name()][cuttype]
+ if cut then
+ output[i] = cut .. " " .. count
+ was_cut = true
+ end
+ end
+ end
+
+ inv:set_list("forms", output)
+end
+
+local main_fs = ""..
+ --~ Verb shown in workbench form where you can cut a node
+ "label[0.9,1.23;"..FS("Cut").."]"
+ --~ Verb shown in workbench form where you can repair an item
+ .."label[0.9,2.23;"..FS("Repair").."]"
+ ..[[ box[-0.05,1;2.05,0.9;#555555]
+ box[-0.05,2;2.05,0.9;#555555] ]]
+ --~ Button in workbench form
+ .."button[0,0;2,1;craft;"..FS("Crafting").."]"
+ --~ Button in workbench form
+ .."button[2,0;2,1;storage;"..FS("Storage").."]"
+ ..[[ image[3,1;1,1;gui_arrow.png]
+ image[0,1;1,1;worktable_saw.png]
+ image[0,2;1,1;worktable_anvil.png]
+ image[3,2;1,1;hammer_layout.png]
+ list[context;input;2,1;1,1;]
+ list[context;tool;2,2;1,1;]
+ list[context;hammer;3,2;1,1;]
+ list[context;forms;4,0;4,3;]
+ listring[current_player;main]
+ listring[context;tool]
+ listring[current_player;main]
+ listring[context;hammer]
+ listring[current_player;main]
+ listring[context;forms]
+ listring[current_player;main]
+ listring[context;input]
+]]
+
+local crafting_fs = "image[5,1;1,1;gui_furnace_arrow_bg.png^[transformR270]"
+ .."button[0,0;1.5,1;back;< "..FS("Back").."]"
+ ..[[ list[current_player;craft;2,0;3,3;]
+ list[current_player;craftpreview;6,1;1,1;]
+ listring[current_player;main]
+ listring[current_player;craft]
+]]
+
+local storage_fs = "list[context;storage;0,1;8,2;]"
+ .."button[0,0;1.5,1;back;< "..FS("Back").."]"
+ ..[[listring[context;storage]
+ listring[current_player;main]
+]]
+
+local formspecs = {
+ -- Main formspec
+ main_fs,
+
+ -- Crafting formspec
+ crafting_fs,
+
+ -- Storage formspec
+ storage_fs,
+}
+
+function workbench:set_formspec(meta, id)
+ meta:set_string("formspec",
+ "size[8,7;]list[current_player;main;0,3.25;8,4;]" ..
+ formspecs[id] .. xdecor.xbg .. default.get_hotbar_bg(0,3.25))
+end
+
+function workbench.construct(pos)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+
+ inv:set_size("tool", 1)
+ inv:set_size("input", 1)
+ inv:set_size("hammer", 1)
+ inv:set_size("forms", 4*3)
+ inv:set_size("storage", 8*2)
+
+ meta:set_string("infotext", S("Work Bench"))
+ workbench:set_formspec(meta, 1)
+end
+
+function workbench.fields(pos, _, fields)
+ if fields.quit then return end
+
+ local meta = minetest.get_meta(pos)
+ local id = fields.back and 1 or fields.craft and 2 or fields.storage and 3
+ if not id then return end
+
+ workbench:set_formspec(meta, id)
+end
+
+function workbench.dig(pos)
+ local inv = minetest.get_meta(pos):get_inventory()
+ return inv:is_empty("input") and inv:is_empty("hammer") and
+ inv:is_empty("tool") and inv:is_empty("storage")
+end
+
+function workbench.blast(pos)
+ local drops = xdecor.get_inventory_drops(pos, {"input", "hammer", "tool", "storage"})
+ minetest.remove_node(pos)
+ return drops
+end
+
+function workbench.timer(pos)
+ local timer = minetest.get_node_timer(pos)
+ local inv = minetest.get_meta(pos):get_inventory()
+ local tool = inv:get_stack("tool", 1)
+ local hammer = inv:get_stack("hammer", 1)
+
+ if tool:is_empty() or hammer:is_empty() or tool:get_wear() == 0 then
+ timer:stop()
+ return
+ end
+
+ local hammerdef = hammer:get_definition()
+
+ -- Tool's wearing range: 0-65535; 0 = new condition
+ tool:add_wear(-hammerdef._xdecor_hammer_repair or DEFAULT_HAMMER_REPAIR)
+ hammer:add_wear(hammerdef._xdecor_hammer_repair_cost or DEFAULT_HAMMER_REPAIR_COST)
+
+ inv:set_stack("tool", 1, tool)
+ inv:set_stack("hammer", 1, hammer)
+
+ return true
+end
+
+function workbench.allow_put(pos, listname, index, stack, player)
+ local stackname = stack:get_name()
+ if (listname == "tool" and workbench:repairable(stackname)) or
+ (listname == "input" and workbench:cuttable(stackname)) or
+ (listname == "hammer" and minetest.get_item_group(stackname, "repair_hammer") == 1) or
+ listname == "storage" then
+ return stack:get_count()
+ end
+
+ return 0
+end
+
+function workbench.on_put(pos, listname, index, stack, player)
+ local inv = minetest.get_meta(pos):get_inventory()
+ if listname == "input" then
+ local input = inv:get_stack("input", 1)
+ workbench:get_output(inv, input, stack:get_name())
+ elseif listname == "tool" or listname == "hammer" then
+ local timer = minetest.get_node_timer(pos)
+ timer:start(3.0)
+ end
+end
+
+function workbench.allow_move(pos, from_list, from_index, to_list, to_index, count, player)
+ if (to_list == "storage" and from_list ~= "forms") then
+ return count
+ elseif (to_list == "hammer" and from_list == "tool") or (to_list == "tool" and from_list == "hammer") then
+ local inv = minetest.get_inventory({type="node", pos=pos})
+ local stack = inv:get_stack(from_list, from_index)
+ if minetest.get_item_group(stack:get_name(), "repair_hammer") == 1 then
+ return count
+ end
+ end
+ return 0
+end
+
+function workbench.on_move(pos, from_list, from_index, to_list, to_index, count, player)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ local from_stack = inv:get_stack(from_list, from_index)
+ local to_stack = inv:get_stack(to_list, to_index)
+
+ workbench.on_take(pos, from_list, from_index, from_stack, player)
+ workbench.on_put(pos, to_list, to_index, to_stack, player)
+end
+
+function workbench.allow_take(pos, listname, index, stack, player)
+ return stack:get_count()
+end
+
+function workbench.on_take(pos, listname, index, stack, player)
+ local inv = minetest.get_meta(pos):get_inventory()
+ local input = inv:get_stack("input", 1)
+ local inputname = input:get_name()
+ local stackname = stack:get_name()
+
+ if listname == "input" then
+ if stackname == inputname and workbench:cuttable(inputname) then
+ workbench:get_output(inv, input, stackname)
+ else
+ inv:set_list("forms", {})
+ end
+ elseif listname == "forms" then
+ local fromstack = inv:get_stack(listname, index)
+ if not fromstack:is_empty() and fromstack:get_name() ~= stackname then
+ local player_inv = player:get_inventory()
+ if player_inv:room_for_item("main", fromstack) then
+ player_inv:add_item("main", fromstack)
+ end
+ end
+
+ input:take_item(ceil(stack:get_count() / workbench.defs[index][2]))
+ inv:set_stack("input", 1, input)
+ workbench:get_output(inv, input, inputname)
+ end
+end
+
+xdecor.register("workbench", {
+ description = S("Work Bench"),
+ _tt_help = S("For cutting blocks, repairing tools with a hammer, crafting and storing items"),
+ groups = {cracky = 2, choppy = 2, oddly_breakable_by_hand = 1},
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ tiles = {
+ "xdecor_workbench_top.png","xdecor_workbench_bottom.png",
+ "xdecor_workbench_sides.png", "xdecor_workbench_sides.png",
+ "xdecor_workbench_front.png", "xdecor_workbench_front.png"
+ },
+ on_rotate = screwdriver.rotate_simple,
+ can_dig = workbench.dig,
+ on_blast = workbench.blast,
+ on_timer = workbench.timer,
+ on_construct = workbench.construct,
+ on_receive_fields = workbench.fields,
+ on_metadata_inventory_put = workbench.on_put,
+ on_metadata_inventory_take = workbench.on_take,
+ on_metadata_inventory_move = workbench.on_move,
+ allow_metadata_inventory_put = workbench.allow_put,
+ allow_metadata_inventory_take = workbench.allow_take,
+ allow_metadata_inventory_move = workbench.allow_move
+})
+
+local function register_cut_raw(node, workbench_def)
+ local mod_name, item_name = node:match("^(.-):(.*)")
+ local def = minetest.registered_nodes[node]
+
+ if item_name and workbench_def[3] then
+ local groups = {}
+ local tiles
+ groups.not_in_creative_inventory = 1
+
+ for k, v in pairs(def.groups) do
+ if k ~= "wood" and k ~= "stone" and k ~= "level" then
+ groups[k] = v
+ end
+ end
+
+ if def.tiles then
+ if #def.tiles > 1 and (def.drawtype:sub(1,5) ~= "glass") then
+ tiles = def.tiles
+ else
+ tiles = {def.tiles[1]}
+ end
+ else
+ tiles = {def.tile_images[1]}
+ end
+
+ -- Erase `tileable_vertical=false` from tiles because it
+ -- lead to buggy textures (e.g. with default:permafrost_with_moss)
+ for t=1, #tiles do
+ if type(tiles[t]) == "table" and tiles[t].tileable_vertical == false then
+ tiles[t].tileable_vertical = nil
+ end
+ end
+
+ local custom_tiles = xdecor.glasscuts[node]
+ if custom_tiles then
+ if not custom_tiles.nanoslab then
+ custom_tiles.nanoslab = custom_tiles.cube
+ end
+ if not custom_tiles.micropanel then
+ custom_tiles.micropanel = custom_tiles.micropanel
+ end
+ if not custom_tiles.doublepanel then
+ custom_tiles.doublepanel = custom_tiles.panel
+ end
+ end
+
+ if not minetest.registered_nodes["stairs:slab_" .. item_name] then
+ if custom_tiles and (custom_tiles.slab or custom_tiles.stair) then
+ if custom_tiles.stair then
+ stairs.register_stair(item_name, node,
+ groups, custom_tiles.stair, S("@1 Stair", def.description),
+ def.sounds)
+ stairs.register_stair_inner(item_name, node,
+ groups, custom_tiles.stair_inner, "", def.sounds, nil, S("Inner @1 Stair", def.description))
+ stairs.register_stair_outer(item_name, node,
+ groups, custom_tiles.stair_outer, "", def.sounds, nil, S("Outer @1 Stair", def.description))
+ end
+ if custom_tiles.slab then
+ stairs.register_slab(item_name, node,
+ groups, custom_tiles.slab, S("@1 Slab", def.description),
+ def.sounds)
+ end
+ else
+ stairs.register_stair_and_slab(item_name, node,
+ groups, tiles,
+ S("@1 Stair", def.description),
+ S("@1 Slab", def.description),
+ def.sounds, nil,
+ S("Inner @1 Stair", def.description),
+ S("Outer @1 Stair", def.description))
+ end
+ end
+
+ local cutname = workbench_def[1]
+ local tiles_special_cut
+ if custom_tiles and custom_tiles[cutname] then
+ tiles_special_cut = custom_tiles[cutname]
+ else
+ tiles_special_cut = tiles
+ end
+
+ local cutnodename = node .. "_" .. cutname
+ if minetest.registered_nodes[cutnodename] then
+ minetest.log("error", "[xdecor] register_cut_raw: Refusing to register node "..cutnodename.." becaut it was already registered!")
+ return false
+ end
+ minetest.register_node(":" .. cutnodename, {
+ --~ Format of the description of a cut node. @1: Base node description (e.g. "Stone"); @2: modifier (e.g. "Nanoslab")
+ description = S("@1 @2", def.description, workbench_def[4]),
+ paramtype = "light",
+ paramtype2 = "facedir",
+ drawtype = "nodebox",
+ sounds = def.sounds,
+ tiles = tiles_special_cut,
+ use_texture_alpha = def.use_texture_alpha,
+ groups = groups,
+ is_ground_content = def.is_ground_content,
+ node_box = xdecor.pixelbox(16, workbench_def[3]),
+ sunlight_propagates = true,
+ on_place = minetest.rotate_node
+ })
+
+ elseif item_name and mod_name then
+ minetest.register_alias_force(
+ ("%s:%s_innerstair"):format(mod_name, item_name),
+ ("stairs:stair_inner_%s"):format(item_name)
+ )
+ minetest.register_alias_force(
+ ("%s:%s_outerstair"):format(mod_name, item_name),
+ ("stairs:stair_outer_%s"):format(item_name)
+ )
+ end
+ return true
+end
+
+function workbench:register_cut(nodename, cutlist)
+ if registered_cuttable_nodes[nodename] then
+ minetest.log("error", "[xdecor] Workbench: Tried to register cut for node "..nodename..", but it was already registered!")
+ return false
+ end
+ local ok = true
+ for _, d in ipairs(workbench.defs) do
+ local ok = register_cut_raw(nodename, d)
+ if not ok then
+ ok = false
+ end
+ end
+ registered_cuttable_nodes[nodename] = true
+ return ok
+end
+
+function workbench:register_special_cut(nodename, cutlist)
+ if registered_cuttable_nodes[nodename] or special_cuts[nodename] then
+ minetest.log("error", "[xdecor] Workbench: Tried to register special cut for node "..nodename..", but it was already registered!")
+ return false
+ end
+ registered_cuttable_nodes[nodename] = true
+ special_cuts[nodename] = cutlist
+end
+
+-- Workbench craft
+minetest.register_craft({
+ output = "xdecor:workbench",
+ recipe = {
+ {"group:wood", "group:wood"},
+ {"group:wood", "group:wood"}
+ }
+})
+
+-- Register default cuttable blocks
+do
+ local cuttable_nodes = {}
+
+ -- Nodes allowed to be cut:
+ -- Only the regular, solid blocks without metas or explosivity
+ -- from the xdecor or default mods.
+ for nodename, def in pairs(minetest.registered_nodes) do
+ local nodenamesplit = string.split(nodename, ":")
+ local modname = nodenamesplit[1]
+ if (modname == "xdecor" or modname == "default") and xdecor.stairs_valid_def(def) then
+ cuttable_nodes[#cuttable_nodes + 1] = nodename
+ end
+ end
+
+ for i = 1, #cuttable_nodes do
+ local node = cuttable_nodes[i]
+ workbench:register_cut(node)
+ end
+end
+
+-- Special cuts for cushion block and cabinet
+workbench:register_special_cut("xdecor:cushion_block", { slab = "xdecor:cushion" })
+workbench:register_special_cut("xdecor:cabinet", { slab = "xdecor:cabinet_half" })
+
+--[[ API FUNCTIONS ]]
+
+--[[ Register a custom hammer (for repairing).
+A hammer repair items at the work bench. The workbench repeatedly
+checks if a hammer and a repairable tool are in the slots. The hammer
+will repair the tool in regular intervals. This is called a "step".
+In each step, the hammer reduces the wear of the repairable
+tool but increases its own wear, each by a fixed amount.
+
+This function allows you to register a custom hammer with custom
+name, item image and wear stats.
+
+Arguments:
+* name: Internal itemname
+* def: Definition table:
+ * description: Item `description`
+ * image: Inventory image and wield image
+ * groups: Item groups (MUST contain at least `repair_hammer = 1`)
+ * repair: How much item wear the hammer repairs per step
+ * repair_cost: How much item wear the hammer takes itself per step
+
+Note: Mind the implication of repair_cost! If repair_cost is lower than
+repair, this means practically infinite durability if you have two
+hammers that repair each other. If repair_cost is higher than repair,
+then hammers will break eventually.
+]]
+function xdecor.register_hammer(name, def)
+ minetest.register_tool(name, {
+ description = def.description,
+ _tt_help = S("Repairs tools at the work bench"),
+ inventory_image = def.image,
+ wield_image = def.image,
+ on_use = function() do
+ return end
+ end,
+ groups = def.groups,
+ _xdecor_hammer_repair = def.repair or DEFAULT_HAMMER_REPAIR,
+ _xdecor_hammer_repair_cost = def.repair_cost or DEFAULT_HAMMER_REPAIR_COST,
+ })
+end
+
+--[[ EXPERIMENTAL FUNCTION:
+Registers various 'cut' node variants for the node with the given nodename,
+which will be available in the workbench.
+This must only be called once per node. Calling it again is an error.
+
+The following nodes will be registered:
+
+* _nanoslab
+* _micropanel
+* _microslab
+* _thinstair
+* _cube
+* _panel
+* _doublepanel
+* _halfstair
+
+You MUST make sure these names are not already taken before
+calling this function. Failing to do so is an error.
+
+Additionally, a slab, stair, inner stair and outer stair
+will be registered by using the `stairs` mod if the slab
+node does not exist yet. Refer to the `stairs` mod documentation
+for details.
+
+Returns true if all nodes were registered successfully,
+returns false (and writes to error log) if any error occurred.
+]]
+xdecor.register_cut = function(nodename)
+ return workbench:register_cut(nodename)
+end
+
+
+--[[ END OF API FUNCTIONS ]]
+
+
+-- Register xdecor's built-in hammer
+xdecor.register_hammer("xdecor:hammer", {
+ description = S("Hammer"),
+ image = "xdecor_hammer.png",
+ groups = { repair_hammer = 1 },
+ repair = DEFAULT_HAMMER_REPAIR,
+ repair_cost = DEFAULT_HAMMER_REPAIR_COST,
+})
+
+-- Hammer recipes
+minetest.register_craft({
+ output = "xdecor:hammer",
+ recipe = {
+ {"default:steel_ingot", "group:stick", "default:steel_ingot"},
+ {"", "group:stick", ""}
+ }
+})
diff --git a/mods/xdecor/textures/bg_btn.png b/mods/xdecor/textures/bg_btn.png
new file mode 100644
index 00000000..58155720
Binary files /dev/null and b/mods/xdecor/textures/bg_btn.png differ
diff --git a/mods/xdecor/textures/bishop_black.png b/mods/xdecor/textures/bishop_black.png
new file mode 100644
index 00000000..388a622e
Binary files /dev/null and b/mods/xdecor/textures/bishop_black.png differ
diff --git a/mods/xdecor/textures/bishop_white.png b/mods/xdecor/textures/bishop_white.png
new file mode 100644
index 00000000..2819a586
Binary files /dev/null and b/mods/xdecor/textures/bishop_white.png differ
diff --git a/mods/xdecor/textures/chess_bg.png b/mods/xdecor/textures/chess_bg.png
new file mode 100644
index 00000000..b84ab6ac
Binary files /dev/null and b/mods/xdecor/textures/chess_bg.png differ
diff --git a/mods/xdecor/textures/chess_draw_50move.png b/mods/xdecor/textures/chess_draw_50move.png
new file mode 100644
index 00000000..5ae1c411
Binary files /dev/null and b/mods/xdecor/textures/chess_draw_50move.png differ
diff --git a/mods/xdecor/textures/chess_draw_50move_next.png b/mods/xdecor/textures/chess_draw_50move_next.png
new file mode 100644
index 00000000..b44e836c
Binary files /dev/null and b/mods/xdecor/textures/chess_draw_50move_next.png differ
diff --git a/mods/xdecor/textures/chess_draw_repeat3.png b/mods/xdecor/textures/chess_draw_repeat3.png
new file mode 100644
index 00000000..1cbcb758
Binary files /dev/null and b/mods/xdecor/textures/chess_draw_repeat3.png differ
diff --git a/mods/xdecor/textures/chess_draw_repeat3_next.png b/mods/xdecor/textures/chess_draw_repeat3_next.png
new file mode 100644
index 00000000..2d0697de
Binary files /dev/null and b/mods/xdecor/textures/chess_draw_repeat3_next.png differ
diff --git a/mods/xdecor/textures/chess_figurine_bishop_black.png b/mods/xdecor/textures/chess_figurine_bishop_black.png
new file mode 100644
index 00000000..0e4895fe
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_bishop_black.png differ
diff --git a/mods/xdecor/textures/chess_figurine_bishop_white.png b/mods/xdecor/textures/chess_figurine_bishop_white.png
new file mode 100644
index 00000000..7d753d92
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_bishop_white.png differ
diff --git a/mods/xdecor/textures/chess_figurine_king_black.png b/mods/xdecor/textures/chess_figurine_king_black.png
new file mode 100644
index 00000000..974aeb01
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_king_black.png differ
diff --git a/mods/xdecor/textures/chess_figurine_king_white.png b/mods/xdecor/textures/chess_figurine_king_white.png
new file mode 100644
index 00000000..889932fa
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_king_white.png differ
diff --git a/mods/xdecor/textures/chess_figurine_knight_black.png b/mods/xdecor/textures/chess_figurine_knight_black.png
new file mode 100644
index 00000000..2ebb32a0
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_knight_black.png differ
diff --git a/mods/xdecor/textures/chess_figurine_knight_white.png b/mods/xdecor/textures/chess_figurine_knight_white.png
new file mode 100644
index 00000000..933bf348
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_knight_white.png differ
diff --git a/mods/xdecor/textures/chess_figurine_pawn_black.png b/mods/xdecor/textures/chess_figurine_pawn_black.png
new file mode 100644
index 00000000..ed4d1854
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_pawn_black.png differ
diff --git a/mods/xdecor/textures/chess_figurine_pawn_white.png b/mods/xdecor/textures/chess_figurine_pawn_white.png
new file mode 100644
index 00000000..aeb49293
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_pawn_white.png differ
diff --git a/mods/xdecor/textures/chess_figurine_queen_black.png b/mods/xdecor/textures/chess_figurine_queen_black.png
new file mode 100644
index 00000000..452fde2b
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_queen_black.png differ
diff --git a/mods/xdecor/textures/chess_figurine_queen_white.png b/mods/xdecor/textures/chess_figurine_queen_white.png
new file mode 100644
index 00000000..e888f009
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_queen_white.png differ
diff --git a/mods/xdecor/textures/chess_figurine_rook_black.png b/mods/xdecor/textures/chess_figurine_rook_black.png
new file mode 100644
index 00000000..5d2dba63
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_rook_black.png differ
diff --git a/mods/xdecor/textures/chess_figurine_rook_white.png b/mods/xdecor/textures/chess_figurine_rook_white.png
new file mode 100644
index 00000000..4d473694
Binary files /dev/null and b/mods/xdecor/textures/chess_figurine_rook_white.png differ
diff --git a/mods/xdecor/textures/chess_resign.png b/mods/xdecor/textures/chess_resign.png
new file mode 100644
index 00000000..6426fdca
Binary files /dev/null and b/mods/xdecor/textures/chess_resign.png differ
diff --git a/mods/xdecor/textures/chess_turn_black.png b/mods/xdecor/textures/chess_turn_black.png
new file mode 100644
index 00000000..00f7c746
Binary files /dev/null and b/mods/xdecor/textures/chess_turn_black.png differ
diff --git a/mods/xdecor/textures/chess_turn_white.png b/mods/xdecor/textures/chess_turn_white.png
new file mode 100644
index 00000000..6ede5a93
Binary files /dev/null and b/mods/xdecor/textures/chess_turn_white.png differ
diff --git a/mods/xdecor/textures/chessboard_sides.png b/mods/xdecor/textures/chessboard_sides.png
new file mode 100644
index 00000000..1498b1db
Binary files /dev/null and b/mods/xdecor/textures/chessboard_sides.png differ
diff --git a/mods/xdecor/textures/chessboard_top.png b/mods/xdecor/textures/chessboard_top.png
new file mode 100644
index 00000000..d6db0513
Binary files /dev/null and b/mods/xdecor/textures/chessboard_top.png differ
diff --git a/mods/xdecor/textures/ench_ui.png b/mods/xdecor/textures/ench_ui.png
new file mode 100644
index 00000000..8a2f4a00
Binary files /dev/null and b/mods/xdecor/textures/ench_ui.png differ
diff --git a/mods/xdecor/textures/gui_arrow.png b/mods/xdecor/textures/gui_arrow.png
new file mode 100644
index 00000000..df1bbdb4
Binary files /dev/null and b/mods/xdecor/textures/gui_arrow.png differ
diff --git a/mods/xdecor/textures/hammer_layout.png b/mods/xdecor/textures/hammer_layout.png
new file mode 100644
index 00000000..355ceabf
Binary files /dev/null and b/mods/xdecor/textures/hammer_layout.png differ
diff --git a/mods/xdecor/textures/hive_bee.png b/mods/xdecor/textures/hive_bee.png
new file mode 100644
index 00000000..e221f1f2
Binary files /dev/null and b/mods/xdecor/textures/hive_bee.png differ
diff --git a/mods/xdecor/textures/hive_layout.png b/mods/xdecor/textures/hive_layout.png
new file mode 100644
index 00000000..27794f42
Binary files /dev/null and b/mods/xdecor/textures/hive_layout.png differ
diff --git a/mods/xdecor/textures/king_black.png b/mods/xdecor/textures/king_black.png
new file mode 100644
index 00000000..06cfc30c
Binary files /dev/null and b/mods/xdecor/textures/king_black.png differ
diff --git a/mods/xdecor/textures/king_white.png b/mods/xdecor/textures/king_white.png
new file mode 100644
index 00000000..60621c41
Binary files /dev/null and b/mods/xdecor/textures/king_white.png differ
diff --git a/mods/xdecor/textures/knight_black.png b/mods/xdecor/textures/knight_black.png
new file mode 100644
index 00000000..44de5f1c
Binary files /dev/null and b/mods/xdecor/textures/knight_black.png differ
diff --git a/mods/xdecor/textures/knight_white.png b/mods/xdecor/textures/knight_white.png
new file mode 100644
index 00000000..1de460f3
Binary files /dev/null and b/mods/xdecor/textures/knight_white.png differ
diff --git a/mods/xdecor/textures/mailbox_blank16.png b/mods/xdecor/textures/mailbox_blank16.png
new file mode 100644
index 00000000..017d4f93
Binary files /dev/null and b/mods/xdecor/textures/mailbox_blank16.png differ
diff --git a/mods/xdecor/textures/mese_layout.png b/mods/xdecor/textures/mese_layout.png
new file mode 100644
index 00000000..154a6bc3
Binary files /dev/null and b/mods/xdecor/textures/mese_layout.png differ
diff --git a/mods/xdecor/textures/pawn_black.png b/mods/xdecor/textures/pawn_black.png
new file mode 100644
index 00000000..7cb5fc85
Binary files /dev/null and b/mods/xdecor/textures/pawn_black.png differ
diff --git a/mods/xdecor/textures/pawn_black_promo_anim.png b/mods/xdecor/textures/pawn_black_promo_anim.png
new file mode 100644
index 00000000..7edaf271
Binary files /dev/null and b/mods/xdecor/textures/pawn_black_promo_anim.png differ
diff --git a/mods/xdecor/textures/pawn_white.png b/mods/xdecor/textures/pawn_white.png
new file mode 100644
index 00000000..f7a8dec0
Binary files /dev/null and b/mods/xdecor/textures/pawn_white.png differ
diff --git a/mods/xdecor/textures/pawn_white_promo_anim.png b/mods/xdecor/textures/pawn_white_promo_anim.png
new file mode 100644
index 00000000..3e9295a5
Binary files /dev/null and b/mods/xdecor/textures/pawn_white_promo_anim.png differ
diff --git a/mods/xdecor/textures/queen_black.png b/mods/xdecor/textures/queen_black.png
new file mode 100644
index 00000000..d59e312e
Binary files /dev/null and b/mods/xdecor/textures/queen_black.png differ
diff --git a/mods/xdecor/textures/queen_white.png b/mods/xdecor/textures/queen_white.png
new file mode 100644
index 00000000..d923f058
Binary files /dev/null and b/mods/xdecor/textures/queen_white.png differ
diff --git a/mods/xdecor/textures/rook_black.png b/mods/xdecor/textures/rook_black.png
new file mode 100644
index 00000000..fa87141c
Binary files /dev/null and b/mods/xdecor/textures/rook_black.png differ
diff --git a/mods/xdecor/textures/rook_white.png b/mods/xdecor/textures/rook_white.png
new file mode 100644
index 00000000..67034526
Binary files /dev/null and b/mods/xdecor/textures/rook_white.png differ
diff --git a/mods/xdecor/textures/stairs_glass_cube.png b/mods/xdecor/textures/stairs_glass_cube.png
new file mode 100644
index 00000000..f8182ebb
Binary files /dev/null and b/mods/xdecor/textures/stairs_glass_cube.png differ
diff --git a/mods/xdecor/textures/stairs_obsidian_glass_cube.png b/mods/xdecor/textures/stairs_obsidian_glass_cube.png
new file mode 100644
index 00000000..68bd9f8c
Binary files /dev/null and b/mods/xdecor/textures/stairs_obsidian_glass_cube.png differ
diff --git a/mods/xdecor/textures/worktable_anvil.png b/mods/xdecor/textures/worktable_anvil.png
new file mode 100644
index 00000000..afca15ec
Binary files /dev/null and b/mods/xdecor/textures/worktable_anvil.png differ
diff --git a/mods/xdecor/textures/worktable_saw.png b/mods/xdecor/textures/worktable_saw.png
new file mode 100644
index 00000000..73246ca8
Binary files /dev/null and b/mods/xdecor/textures/worktable_saw.png differ
diff --git a/mods/xdecor/textures/xdecor_bamboo_frame.png b/mods/xdecor/textures/xdecor_bamboo_frame.png
new file mode 100644
index 00000000..6a0d863e
Binary files /dev/null and b/mods/xdecor/textures/xdecor_bamboo_frame.png differ
diff --git a/mods/xdecor/textures/xdecor_baricade.png b/mods/xdecor/textures/xdecor_baricade.png
new file mode 100644
index 00000000..87109bb5
Binary files /dev/null and b/mods/xdecor/textures/xdecor_baricade.png differ
diff --git a/mods/xdecor/textures/xdecor_barrel_sides.png b/mods/xdecor/textures/xdecor_barrel_sides.png
new file mode 100644
index 00000000..4172b81f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_barrel_sides.png differ
diff --git a/mods/xdecor/textures/xdecor_barrel_top.png b/mods/xdecor/textures/xdecor_barrel_top.png
new file mode 100644
index 00000000..014b00ac
Binary files /dev/null and b/mods/xdecor/textures/xdecor_barrel_top.png differ
diff --git a/mods/xdecor/textures/xdecor_book_open.png b/mods/xdecor/textures/xdecor_book_open.png
new file mode 100644
index 00000000..206b11cb
Binary files /dev/null and b/mods/xdecor/textures/xdecor_book_open.png differ
diff --git a/mods/xdecor/textures/xdecor_bowl.png b/mods/xdecor/textures/xdecor_bowl.png
new file mode 100644
index 00000000..9197da97
Binary files /dev/null and b/mods/xdecor/textures/xdecor_bowl.png differ
diff --git a/mods/xdecor/textures/xdecor_bowl_soup.png b/mods/xdecor/textures/xdecor_bowl_soup.png
new file mode 100644
index 00000000..54d87ae0
Binary files /dev/null and b/mods/xdecor/textures/xdecor_bowl_soup.png differ
diff --git a/mods/xdecor/textures/xdecor_cabinet_front.png b/mods/xdecor/textures/xdecor_cabinet_front.png
new file mode 100644
index 00000000..63809680
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cabinet_front.png differ
diff --git a/mods/xdecor/textures/xdecor_cabinet_sides.png b/mods/xdecor/textures/xdecor_cabinet_sides.png
new file mode 100644
index 00000000..32ab2579
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cabinet_sides.png differ
diff --git a/mods/xdecor/textures/xdecor_cactusbrick.png b/mods/xdecor/textures/xdecor_cactusbrick.png
new file mode 100644
index 00000000..4856f09a
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cactusbrick.png differ
diff --git a/mods/xdecor/textures/xdecor_candle_floor.png b/mods/xdecor/textures/xdecor_candle_floor.png
new file mode 100644
index 00000000..c1bc4e3f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_candle_floor.png differ
diff --git a/mods/xdecor/textures/xdecor_candle_hanging.png b/mods/xdecor/textures/xdecor_candle_hanging.png
new file mode 100644
index 00000000..04869c71
Binary files /dev/null and b/mods/xdecor/textures/xdecor_candle_hanging.png differ
diff --git a/mods/xdecor/textures/xdecor_candle_inv.png b/mods/xdecor/textures/xdecor_candle_inv.png
new file mode 100644
index 00000000..90a479e1
Binary files /dev/null and b/mods/xdecor/textures/xdecor_candle_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_candle_wall.png b/mods/xdecor/textures/xdecor_candle_wall.png
new file mode 100644
index 00000000..b8a4b502
Binary files /dev/null and b/mods/xdecor/textures/xdecor_candle_wall.png differ
diff --git a/mods/xdecor/textures/xdecor_candle_wield.png b/mods/xdecor/textures/xdecor_candle_wield.png
new file mode 100644
index 00000000..60bdeded
Binary files /dev/null and b/mods/xdecor/textures/xdecor_candle_wield.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_bottom.png b/mods/xdecor/textures/xdecor_cauldron_bottom.png
new file mode 100644
index 00000000..b89acbca
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_bottom.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_sides.png b/mods/xdecor/textures/xdecor_cauldron_sides.png
new file mode 100644
index 00000000..6644edb3
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_sides.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_top_anim_boiling_river_water.png b/mods/xdecor/textures/xdecor_cauldron_top_anim_boiling_river_water.png
new file mode 100644
index 00000000..083f8103
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_top_anim_boiling_river_water.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_top_anim_boiling_water.png b/mods/xdecor/textures/xdecor_cauldron_top_anim_boiling_water.png
new file mode 100644
index 00000000..48584d5f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_top_anim_boiling_water.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_top_anim_soup.png b/mods/xdecor/textures/xdecor_cauldron_top_anim_soup.png
new file mode 100644
index 00000000..f6d5258c
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_top_anim_soup.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_top_empty.png b/mods/xdecor/textures/xdecor_cauldron_top_empty.png
new file mode 100644
index 00000000..5cdf5fdd
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_top_empty.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_top_idle.png b/mods/xdecor/textures/xdecor_cauldron_top_idle.png
new file mode 100644
index 00000000..69ec2ab0
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_top_idle.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_top_idle_river_water.png b/mods/xdecor/textures/xdecor_cauldron_top_idle_river_water.png
new file mode 100644
index 00000000..a7456ec4
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_top_idle_river_water.png differ
diff --git a/mods/xdecor/textures/xdecor_cauldron_top_idle_soup.png b/mods/xdecor/textures/xdecor_cauldron_top_idle_soup.png
new file mode 100644
index 00000000..30804a3f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cauldron_top_idle_soup.png differ
diff --git a/mods/xdecor/textures/xdecor_chainlink.png b/mods/xdecor/textures/xdecor_chainlink.png
new file mode 100644
index 00000000..1aba3c09
Binary files /dev/null and b/mods/xdecor/textures/xdecor_chainlink.png differ
diff --git a/mods/xdecor/textures/xdecor_coalstone_tile.png b/mods/xdecor/textures/xdecor_coalstone_tile.png
new file mode 100644
index 00000000..3122db0d
Binary files /dev/null and b/mods/xdecor/textures/xdecor_coalstone_tile.png differ
diff --git a/mods/xdecor/textures/xdecor_cobweb.png b/mods/xdecor/textures/xdecor_cobweb.png
new file mode 100644
index 00000000..bb1eceaf
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cobweb.png differ
diff --git a/mods/xdecor/textures/xdecor_curtain_open_overlay.png b/mods/xdecor/textures/xdecor_curtain_open_overlay.png
new file mode 100644
index 00000000..e6eea416
Binary files /dev/null and b/mods/xdecor/textures/xdecor_curtain_open_overlay.png differ
diff --git a/mods/xdecor/textures/xdecor_curtain_open_overlay_bottom.png b/mods/xdecor/textures/xdecor_curtain_open_overlay_bottom.png
new file mode 100644
index 00000000..ed733afa
Binary files /dev/null and b/mods/xdecor/textures/xdecor_curtain_open_overlay_bottom.png differ
diff --git a/mods/xdecor/textures/xdecor_curtain_open_overlay_side.png b/mods/xdecor/textures/xdecor_curtain_open_overlay_side.png
new file mode 100644
index 00000000..98dda28c
Binary files /dev/null and b/mods/xdecor/textures/xdecor_curtain_open_overlay_side.png differ
diff --git a/mods/xdecor/textures/xdecor_curtain_open_overlay_top.png b/mods/xdecor/textures/xdecor_curtain_open_overlay_top.png
new file mode 100644
index 00000000..8c319ef9
Binary files /dev/null and b/mods/xdecor/textures/xdecor_curtain_open_overlay_top.png differ
diff --git a/mods/xdecor/textures/xdecor_cushion.png b/mods/xdecor/textures/xdecor_cushion.png
new file mode 100644
index 00000000..ad4674c4
Binary files /dev/null and b/mods/xdecor/textures/xdecor_cushion.png differ
diff --git a/mods/xdecor/textures/xdecor_dandelion_white_pot.png b/mods/xdecor/textures/xdecor_dandelion_white_pot.png
new file mode 100644
index 00000000..ccb91ee5
Binary files /dev/null and b/mods/xdecor/textures/xdecor_dandelion_white_pot.png differ
diff --git a/mods/xdecor/textures/xdecor_dandelion_yellow_pot.png b/mods/xdecor/textures/xdecor_dandelion_yellow_pot.png
new file mode 100644
index 00000000..f3034750
Binary files /dev/null and b/mods/xdecor/textures/xdecor_dandelion_yellow_pot.png differ
diff --git a/mods/xdecor/textures/xdecor_desertstone_tile.png b/mods/xdecor/textures/xdecor_desertstone_tile.png
new file mode 100644
index 00000000..094437fc
Binary files /dev/null and b/mods/xdecor/textures/xdecor_desertstone_tile.png differ
diff --git a/mods/xdecor/textures/xdecor_empty_shelf.png b/mods/xdecor/textures/xdecor_empty_shelf.png
new file mode 100644
index 00000000..a8363c0f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_empty_shelf.png differ
diff --git a/mods/xdecor/textures/xdecor_enchantment_bottom.png b/mods/xdecor/textures/xdecor_enchantment_bottom.png
new file mode 100644
index 00000000..38cdaf15
Binary files /dev/null and b/mods/xdecor/textures/xdecor_enchantment_bottom.png differ
diff --git a/mods/xdecor/textures/xdecor_enchantment_side.png b/mods/xdecor/textures/xdecor_enchantment_side.png
new file mode 100644
index 00000000..9bbd5a25
Binary files /dev/null and b/mods/xdecor/textures/xdecor_enchantment_side.png differ
diff --git a/mods/xdecor/textures/xdecor_enchantment_top.png b/mods/xdecor/textures/xdecor_enchantment_top.png
new file mode 100644
index 00000000..cc79b353
Binary files /dev/null and b/mods/xdecor/textures/xdecor_enchantment_top.png differ
diff --git a/mods/xdecor/textures/xdecor_enderchest_front.png b/mods/xdecor/textures/xdecor_enderchest_front.png
new file mode 100644
index 00000000..e7286ce6
Binary files /dev/null and b/mods/xdecor/textures/xdecor_enderchest_front.png differ
diff --git a/mods/xdecor/textures/xdecor_enderchest_side.png b/mods/xdecor/textures/xdecor_enderchest_side.png
new file mode 100644
index 00000000..46912476
Binary files /dev/null and b/mods/xdecor/textures/xdecor_enderchest_side.png differ
diff --git a/mods/xdecor/textures/xdecor_enderchest_top.png b/mods/xdecor/textures/xdecor_enderchest_top.png
new file mode 100644
index 00000000..1f6676ce
Binary files /dev/null and b/mods/xdecor/textures/xdecor_enderchest_top.png differ
diff --git a/mods/xdecor/textures/xdecor_geranium_pot.png b/mods/xdecor/textures/xdecor_geranium_pot.png
new file mode 100644
index 00000000..0a68204c
Binary files /dev/null and b/mods/xdecor/textures/xdecor_geranium_pot.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph1.png b/mods/xdecor/textures/xdecor_glyph1.png
new file mode 100644
index 00000000..645abacd
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph1.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph10.png b/mods/xdecor/textures/xdecor_glyph10.png
new file mode 100644
index 00000000..06bd0743
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph10.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph11.png b/mods/xdecor/textures/xdecor_glyph11.png
new file mode 100644
index 00000000..532d25c4
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph11.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph12.png b/mods/xdecor/textures/xdecor_glyph12.png
new file mode 100644
index 00000000..1f6be218
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph12.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph13.png b/mods/xdecor/textures/xdecor_glyph13.png
new file mode 100644
index 00000000..4678f7f1
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph13.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph14.png b/mods/xdecor/textures/xdecor_glyph14.png
new file mode 100644
index 00000000..bf095bbf
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph14.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph15.png b/mods/xdecor/textures/xdecor_glyph15.png
new file mode 100644
index 00000000..27199bb2
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph15.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph16.png b/mods/xdecor/textures/xdecor_glyph16.png
new file mode 100644
index 00000000..27e18072
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph16.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph17.png b/mods/xdecor/textures/xdecor_glyph17.png
new file mode 100644
index 00000000..7ed2bd43
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph17.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph18.png b/mods/xdecor/textures/xdecor_glyph18.png
new file mode 100644
index 00000000..f03b73a6
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph18.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph2.png b/mods/xdecor/textures/xdecor_glyph2.png
new file mode 100644
index 00000000..81b20619
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph2.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph3.png b/mods/xdecor/textures/xdecor_glyph3.png
new file mode 100644
index 00000000..24b8cf40
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph3.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph4.png b/mods/xdecor/textures/xdecor_glyph4.png
new file mode 100644
index 00000000..dc1909b6
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph4.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph5.png b/mods/xdecor/textures/xdecor_glyph5.png
new file mode 100644
index 00000000..1473ea41
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph5.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph6.png b/mods/xdecor/textures/xdecor_glyph6.png
new file mode 100644
index 00000000..45c0c9a6
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph6.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph7.png b/mods/xdecor/textures/xdecor_glyph7.png
new file mode 100644
index 00000000..57086232
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph7.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph8.png b/mods/xdecor/textures/xdecor_glyph8.png
new file mode 100644
index 00000000..c5ae1c3b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph8.png differ
diff --git a/mods/xdecor/textures/xdecor_glyph9.png b/mods/xdecor/textures/xdecor_glyph9.png
new file mode 100644
index 00000000..148b03d0
Binary files /dev/null and b/mods/xdecor/textures/xdecor_glyph9.png differ
diff --git a/mods/xdecor/textures/xdecor_half_cabinet_front.png b/mods/xdecor/textures/xdecor_half_cabinet_front.png
new file mode 100644
index 00000000..ea9c3892
Binary files /dev/null and b/mods/xdecor/textures/xdecor_half_cabinet_front.png differ
diff --git a/mods/xdecor/textures/xdecor_half_cabinet_sides.png b/mods/xdecor/textures/xdecor_half_cabinet_sides.png
new file mode 100644
index 00000000..6f274b8b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_half_cabinet_sides.png differ
diff --git a/mods/xdecor/textures/xdecor_hammer.png b/mods/xdecor/textures/xdecor_hammer.png
new file mode 100644
index 00000000..17b98561
Binary files /dev/null and b/mods/xdecor/textures/xdecor_hammer.png differ
diff --git a/mods/xdecor/textures/xdecor_hard_clay.png b/mods/xdecor/textures/xdecor_hard_clay.png
new file mode 100644
index 00000000..8abe6666
Binary files /dev/null and b/mods/xdecor/textures/xdecor_hard_clay.png differ
diff --git a/mods/xdecor/textures/xdecor_hive_front.png b/mods/xdecor/textures/xdecor_hive_front.png
new file mode 100644
index 00000000..45155515
Binary files /dev/null and b/mods/xdecor/textures/xdecor_hive_front.png differ
diff --git a/mods/xdecor/textures/xdecor_hive_side.png b/mods/xdecor/textures/xdecor_hive_side.png
new file mode 100644
index 00000000..7fd26ad7
Binary files /dev/null and b/mods/xdecor/textures/xdecor_hive_side.png differ
diff --git a/mods/xdecor/textures/xdecor_hive_top.png b/mods/xdecor/textures/xdecor_hive_top.png
new file mode 100644
index 00000000..43203e14
Binary files /dev/null and b/mods/xdecor/textures/xdecor_hive_top.png differ
diff --git a/mods/xdecor/textures/xdecor_honey.png b/mods/xdecor/textures/xdecor_honey.png
new file mode 100644
index 00000000..04c404cb
Binary files /dev/null and b/mods/xdecor/textures/xdecor_honey.png differ
diff --git a/mods/xdecor/textures/xdecor_iron_lightbox.png b/mods/xdecor/textures/xdecor_iron_lightbox.png
new file mode 100644
index 00000000..45146f93
Binary files /dev/null and b/mods/xdecor/textures/xdecor_iron_lightbox.png differ
diff --git a/mods/xdecor/textures/xdecor_itemframe.png b/mods/xdecor/textures/xdecor_itemframe.png
new file mode 100644
index 00000000..088ba152
Binary files /dev/null and b/mods/xdecor/textures/xdecor_itemframe.png differ
diff --git a/mods/xdecor/textures/xdecor_ivy.png b/mods/xdecor/textures/xdecor_ivy.png
new file mode 100644
index 00000000..b85e112f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_ivy.png differ
diff --git a/mods/xdecor/textures/xdecor_japanese_door.png b/mods/xdecor/textures/xdecor_japanese_door.png
new file mode 100644
index 00000000..35f77ec7
Binary files /dev/null and b/mods/xdecor/textures/xdecor_japanese_door.png differ
diff --git a/mods/xdecor/textures/xdecor_japanese_door_inv.png b/mods/xdecor/textures/xdecor_japanese_door_inv.png
new file mode 100644
index 00000000..4d3ec649
Binary files /dev/null and b/mods/xdecor/textures/xdecor_japanese_door_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_lantern.png b/mods/xdecor/textures/xdecor_lantern.png
new file mode 100644
index 00000000..18c9ce2f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_lantern.png differ
diff --git a/mods/xdecor/textures/xdecor_lantern_hanging_overlay_inv.png b/mods/xdecor/textures/xdecor_lantern_hanging_overlay_inv.png
new file mode 100644
index 00000000..74efa7a4
Binary files /dev/null and b/mods/xdecor/textures/xdecor_lantern_hanging_overlay_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_lantern_inv.png b/mods/xdecor/textures/xdecor_lantern_inv.png
new file mode 100644
index 00000000..71cef66d
Binary files /dev/null and b/mods/xdecor/textures/xdecor_lantern_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_lever_off.png b/mods/xdecor/textures/xdecor_lever_off.png
new file mode 100644
index 00000000..6d8bb493
Binary files /dev/null and b/mods/xdecor/textures/xdecor_lever_off.png differ
diff --git a/mods/xdecor/textures/xdecor_lever_on.png b/mods/xdecor/textures/xdecor_lever_on.png
new file mode 100644
index 00000000..c64d27f5
Binary files /dev/null and b/mods/xdecor/textures/xdecor_lever_on.png differ
diff --git a/mods/xdecor/textures/xdecor_mailbox.png b/mods/xdecor/textures/xdecor_mailbox.png
new file mode 100644
index 00000000..82898840
Binary files /dev/null and b/mods/xdecor/textures/xdecor_mailbox.png differ
diff --git a/mods/xdecor/textures/xdecor_mailbox_bottom.png b/mods/xdecor/textures/xdecor_mailbox_bottom.png
new file mode 100644
index 00000000..b613863a
Binary files /dev/null and b/mods/xdecor/textures/xdecor_mailbox_bottom.png differ
diff --git a/mods/xdecor/textures/xdecor_mailbox_side.png b/mods/xdecor/textures/xdecor_mailbox_side.png
new file mode 100644
index 00000000..44b2a2f6
Binary files /dev/null and b/mods/xdecor/textures/xdecor_mailbox_side.png differ
diff --git a/mods/xdecor/textures/xdecor_mailbox_top.png b/mods/xdecor/textures/xdecor_mailbox_top.png
new file mode 100644
index 00000000..3584c53b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_mailbox_top.png differ
diff --git a/mods/xdecor/textures/xdecor_moonbrick.png b/mods/xdecor/textures/xdecor_moonbrick.png
new file mode 100644
index 00000000..efdac14c
Binary files /dev/null and b/mods/xdecor/textures/xdecor_moonbrick.png differ
diff --git a/mods/xdecor/textures/xdecor_multishelf.png b/mods/xdecor/textures/xdecor_multishelf.png
new file mode 100644
index 00000000..0a5e10e4
Binary files /dev/null and b/mods/xdecor/textures/xdecor_multishelf.png differ
diff --git a/mods/xdecor/textures/xdecor_packed_ice.png b/mods/xdecor/textures/xdecor_packed_ice.png
new file mode 100644
index 00000000..8c44eafd
Binary files /dev/null and b/mods/xdecor/textures/xdecor_packed_ice.png differ
diff --git a/mods/xdecor/textures/xdecor_painting_1.png b/mods/xdecor/textures/xdecor_painting_1.png
new file mode 100644
index 00000000..7609c2b5
Binary files /dev/null and b/mods/xdecor/textures/xdecor_painting_1.png differ
diff --git a/mods/xdecor/textures/xdecor_painting_2.png b/mods/xdecor/textures/xdecor_painting_2.png
new file mode 100644
index 00000000..8a9cf4a5
Binary files /dev/null and b/mods/xdecor/textures/xdecor_painting_2.png differ
diff --git a/mods/xdecor/textures/xdecor_painting_3.png b/mods/xdecor/textures/xdecor_painting_3.png
new file mode 100644
index 00000000..d094d3d0
Binary files /dev/null and b/mods/xdecor/textures/xdecor_painting_3.png differ
diff --git a/mods/xdecor/textures/xdecor_painting_4.png b/mods/xdecor/textures/xdecor_painting_4.png
new file mode 100644
index 00000000..5ccb1419
Binary files /dev/null and b/mods/xdecor/textures/xdecor_painting_4.png differ
diff --git a/mods/xdecor/textures/xdecor_painting_empty.png b/mods/xdecor/textures/xdecor_painting_empty.png
new file mode 100644
index 00000000..216429a4
Binary files /dev/null and b/mods/xdecor/textures/xdecor_painting_empty.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_moss_cube.png b/mods/xdecor/textures/xdecor_permafrost_moss_cube.png
new file mode 100644
index 00000000..df7b0fd7
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_moss_cube.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_moss_outer_stairside.png b/mods/xdecor/textures/xdecor_permafrost_moss_outer_stairside.png
new file mode 100644
index 00000000..bf4dd6a8
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_moss_outer_stairside.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_moss_split.png b/mods/xdecor/textures/xdecor_permafrost_moss_split.png
new file mode 100644
index 00000000..29162605
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_moss_split.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_moss_stairside.png b/mods/xdecor/textures/xdecor_permafrost_moss_stairside.png
new file mode 100644
index 00000000..3da13482
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_moss_stairside.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_stones_cube.png b/mods/xdecor/textures/xdecor_permafrost_stones_cube.png
new file mode 100644
index 00000000..0002ab4f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_stones_cube.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_stones_outer_stairside.png b/mods/xdecor/textures/xdecor_permafrost_stones_outer_stairside.png
new file mode 100644
index 00000000..0002ab4f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_stones_outer_stairside.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_stones_split.png b/mods/xdecor/textures/xdecor_permafrost_stones_split.png
new file mode 100644
index 00000000..0002ab4f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_stones_split.png differ
diff --git a/mods/xdecor/textures/xdecor_permafrost_stones_stairside.png b/mods/xdecor/textures/xdecor_permafrost_stones_stairside.png
new file mode 100644
index 00000000..e570270b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_permafrost_stones_stairside.png differ
diff --git a/mods/xdecor/textures/xdecor_pressure_stone.png b/mods/xdecor/textures/xdecor_pressure_stone.png
new file mode 100644
index 00000000..faf3ae0e
Binary files /dev/null and b/mods/xdecor/textures/xdecor_pressure_stone.png differ
diff --git a/mods/xdecor/textures/xdecor_pressure_wood.png b/mods/xdecor/textures/xdecor_pressure_wood.png
new file mode 100644
index 00000000..504113a9
Binary files /dev/null and b/mods/xdecor/textures/xdecor_pressure_wood.png differ
diff --git a/mods/xdecor/textures/xdecor_prison_door.png b/mods/xdecor/textures/xdecor_prison_door.png
new file mode 100644
index 00000000..2f7e66f1
Binary files /dev/null and b/mods/xdecor/textures/xdecor_prison_door.png differ
diff --git a/mods/xdecor/textures/xdecor_prison_door_inv.png b/mods/xdecor/textures/xdecor_prison_door_inv.png
new file mode 100644
index 00000000..3285408e
Binary files /dev/null and b/mods/xdecor/textures/xdecor_prison_door_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_radio_back.png b/mods/xdecor/textures/xdecor_radio_back.png
new file mode 100644
index 00000000..78b28c44
Binary files /dev/null and b/mods/xdecor/textures/xdecor_radio_back.png differ
diff --git a/mods/xdecor/textures/xdecor_radio_front.png b/mods/xdecor/textures/xdecor_radio_front.png
new file mode 100644
index 00000000..0202114e
Binary files /dev/null and b/mods/xdecor/textures/xdecor_radio_front.png differ
diff --git a/mods/xdecor/textures/xdecor_radio_side.png b/mods/xdecor/textures/xdecor_radio_side.png
new file mode 100644
index 00000000..441ae303
Binary files /dev/null and b/mods/xdecor/textures/xdecor_radio_side.png differ
diff --git a/mods/xdecor/textures/xdecor_radio_top.png b/mods/xdecor/textures/xdecor_radio_top.png
new file mode 100644
index 00000000..b8c12806
Binary files /dev/null and b/mods/xdecor/textures/xdecor_radio_top.png differ
diff --git a/mods/xdecor/textures/xdecor_rooster.png b/mods/xdecor/textures/xdecor_rooster.png
new file mode 100644
index 00000000..41ab6991
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rooster.png differ
diff --git a/mods/xdecor/textures/xdecor_rope.png b/mods/xdecor/textures/xdecor_rope.png
new file mode 100644
index 00000000..bcfaba26
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rope.png differ
diff --git a/mods/xdecor/textures/xdecor_rope_inv.png b/mods/xdecor/textures/xdecor_rope_inv.png
new file mode 100644
index 00000000..51a24a89
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rope_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_rope_wield.png b/mods/xdecor/textures/xdecor_rope_wield.png
new file mode 100644
index 00000000..3ca8fef3
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rope_wield.png differ
diff --git a/mods/xdecor/textures/xdecor_rose_pot.png b/mods/xdecor/textures/xdecor_rose_pot.png
new file mode 100644
index 00000000..12923acf
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rose_pot.png differ
diff --git a/mods/xdecor/textures/xdecor_rusty_bar.png b/mods/xdecor/textures/xdecor_rusty_bar.png
new file mode 100644
index 00000000..382d7ca3
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rusty_bar.png differ
diff --git a/mods/xdecor/textures/xdecor_rusty_bar_top.png b/mods/xdecor/textures/xdecor_rusty_bar_top.png
new file mode 100644
index 00000000..a408e1be
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rusty_bar_top.png differ
diff --git a/mods/xdecor/textures/xdecor_rusty_prison_door.png b/mods/xdecor/textures/xdecor_rusty_prison_door.png
new file mode 100644
index 00000000..3b1dd1a5
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rusty_prison_door.png differ
diff --git a/mods/xdecor/textures/xdecor_rusty_prison_door_inv.png b/mods/xdecor/textures/xdecor_rusty_prison_door_inv.png
new file mode 100644
index 00000000..65cf8725
Binary files /dev/null and b/mods/xdecor/textures/xdecor_rusty_prison_door_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_screen_door.png b/mods/xdecor/textures/xdecor_screen_door.png
new file mode 100644
index 00000000..a00ebb3b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_screen_door.png differ
diff --git a/mods/xdecor/textures/xdecor_screen_door_inv.png b/mods/xdecor/textures/xdecor_screen_door_inv.png
new file mode 100644
index 00000000..a842732b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_screen_door_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_slide_door.png b/mods/xdecor/textures/xdecor_slide_door.png
new file mode 100644
index 00000000..1810ccdd
Binary files /dev/null and b/mods/xdecor/textures/xdecor_slide_door.png differ
diff --git a/mods/xdecor/textures/xdecor_slide_door_inv.png b/mods/xdecor/textures/xdecor_slide_door_inv.png
new file mode 100644
index 00000000..e80e75ac
Binary files /dev/null and b/mods/xdecor/textures/xdecor_slide_door_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_speaker_back.png b/mods/xdecor/textures/xdecor_speaker_back.png
new file mode 100644
index 00000000..0696d9fb
Binary files /dev/null and b/mods/xdecor/textures/xdecor_speaker_back.png differ
diff --git a/mods/xdecor/textures/xdecor_speaker_front.png b/mods/xdecor/textures/xdecor_speaker_front.png
new file mode 100644
index 00000000..7d3b0859
Binary files /dev/null and b/mods/xdecor/textures/xdecor_speaker_front.png differ
diff --git a/mods/xdecor/textures/xdecor_speaker_side.png b/mods/xdecor/textures/xdecor_speaker_side.png
new file mode 100644
index 00000000..319dbf62
Binary files /dev/null and b/mods/xdecor/textures/xdecor_speaker_side.png differ
diff --git a/mods/xdecor/textures/xdecor_speaker_top.png b/mods/xdecor/textures/xdecor_speaker_top.png
new file mode 100644
index 00000000..b510a2e9
Binary files /dev/null and b/mods/xdecor/textures/xdecor_speaker_top.png differ
diff --git a/mods/xdecor/textures/xdecor_stone_rune.png b/mods/xdecor/textures/xdecor_stone_rune.png
new file mode 100644
index 00000000..319a2df6
Binary files /dev/null and b/mods/xdecor/textures/xdecor_stone_rune.png differ
diff --git a/mods/xdecor/textures/xdecor_stone_tile.png b/mods/xdecor/textures/xdecor_stone_tile.png
new file mode 100644
index 00000000..43467837
Binary files /dev/null and b/mods/xdecor/textures/xdecor_stone_tile.png differ
diff --git a/mods/xdecor/textures/xdecor_tatami.png b/mods/xdecor/textures/xdecor_tatami.png
new file mode 100644
index 00000000..e1998f7a
Binary files /dev/null and b/mods/xdecor/textures/xdecor_tatami.png differ
diff --git a/mods/xdecor/textures/xdecor_television_back.png b/mods/xdecor/textures/xdecor_television_back.png
new file mode 100644
index 00000000..761b1c72
Binary files /dev/null and b/mods/xdecor/textures/xdecor_television_back.png differ
diff --git a/mods/xdecor/textures/xdecor_television_front_animated.png b/mods/xdecor/textures/xdecor_television_front_animated.png
new file mode 100644
index 00000000..ae5df4ff
Binary files /dev/null and b/mods/xdecor/textures/xdecor_television_front_animated.png differ
diff --git a/mods/xdecor/textures/xdecor_television_left.png b/mods/xdecor/textures/xdecor_television_left.png
new file mode 100644
index 00000000..65444fb8
Binary files /dev/null and b/mods/xdecor/textures/xdecor_television_left.png differ
diff --git a/mods/xdecor/textures/xdecor_trampoline.png b/mods/xdecor/textures/xdecor_trampoline.png
new file mode 100644
index 00000000..3b679208
Binary files /dev/null and b/mods/xdecor/textures/xdecor_trampoline.png differ
diff --git a/mods/xdecor/textures/xdecor_trampoline_bottom.png b/mods/xdecor/textures/xdecor_trampoline_bottom.png
new file mode 100644
index 00000000..bd1e8b0f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_trampoline_bottom.png differ
diff --git a/mods/xdecor/textures/xdecor_trampoline_sides.png b/mods/xdecor/textures/xdecor_trampoline_sides.png
new file mode 100644
index 00000000..165be13b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_trampoline_sides.png differ
diff --git a/mods/xdecor/textures/xdecor_tulip_pot.png b/mods/xdecor/textures/xdecor_tulip_pot.png
new file mode 100644
index 00000000..d339ef6d
Binary files /dev/null and b/mods/xdecor/textures/xdecor_tulip_pot.png differ
diff --git a/mods/xdecor/textures/xdecor_viola_pot.png b/mods/xdecor/textures/xdecor_viola_pot.png
new file mode 100644
index 00000000..7549c4ce
Binary files /dev/null and b/mods/xdecor/textures/xdecor_viola_pot.png differ
diff --git a/mods/xdecor/textures/xdecor_wood.png b/mods/xdecor/textures/xdecor_wood.png
new file mode 100644
index 00000000..426e5bf7
Binary files /dev/null and b/mods/xdecor/textures/xdecor_wood.png differ
diff --git a/mods/xdecor/textures/xdecor_wood_frame.png b/mods/xdecor/textures/xdecor_wood_frame.png
new file mode 100644
index 00000000..f528f5ad
Binary files /dev/null and b/mods/xdecor/textures/xdecor_wood_frame.png differ
diff --git a/mods/xdecor/textures/xdecor_wood_frame_top.png b/mods/xdecor/textures/xdecor_wood_frame_top.png
new file mode 100644
index 00000000..752e1489
Binary files /dev/null and b/mods/xdecor/textures/xdecor_wood_frame_top.png differ
diff --git a/mods/xdecor/textures/xdecor_wood_tile.png b/mods/xdecor/textures/xdecor_wood_tile.png
new file mode 100644
index 00000000..272df2c4
Binary files /dev/null and b/mods/xdecor/textures/xdecor_wood_tile.png differ
diff --git a/mods/xdecor/textures/xdecor_wooden2_lightbox.png b/mods/xdecor/textures/xdecor_wooden2_lightbox.png
new file mode 100644
index 00000000..a53ad96a
Binary files /dev/null and b/mods/xdecor/textures/xdecor_wooden2_lightbox.png differ
diff --git a/mods/xdecor/textures/xdecor_wooden_lightbox.png b/mods/xdecor/textures/xdecor_wooden_lightbox.png
new file mode 100644
index 00000000..c5a6b526
Binary files /dev/null and b/mods/xdecor/textures/xdecor_wooden_lightbox.png differ
diff --git a/mods/xdecor/textures/xdecor_woodframed_glass.png b/mods/xdecor/textures/xdecor_woodframed_glass.png
new file mode 100644
index 00000000..3605c38f
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodframed_glass.png differ
diff --git a/mods/xdecor/textures/xdecor_woodframed_glass_cube.png b/mods/xdecor/textures/xdecor_woodframed_glass_cube.png
new file mode 100644
index 00000000..45cc3557
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodframed_glass_cube.png differ
diff --git a/mods/xdecor/textures/xdecor_woodframed_glass_detail.png b/mods/xdecor/textures/xdecor_woodframed_glass_detail.png
new file mode 100644
index 00000000..eff4b0a3
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodframed_glass_detail.png differ
diff --git a/mods/xdecor/textures/xdecor_woodframed_glass_outer_stairside.png b/mods/xdecor/textures/xdecor_woodframed_glass_outer_stairside.png
new file mode 100644
index 00000000..14a9572c
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodframed_glass_outer_stairside.png differ
diff --git a/mods/xdecor/textures/xdecor_woodframed_glass_split.png b/mods/xdecor/textures/xdecor_woodframed_glass_split.png
new file mode 100644
index 00000000..568d4258
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodframed_glass_split.png differ
diff --git a/mods/xdecor/textures/xdecor_woodframed_glass_stairside.png b/mods/xdecor/textures/xdecor_woodframed_glass_stairside.png
new file mode 100644
index 00000000..a59f75a9
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodframed_glass_stairside.png differ
diff --git a/mods/xdecor/textures/xdecor_woodglass_door.png b/mods/xdecor/textures/xdecor_woodglass_door.png
new file mode 100644
index 00000000..51209e2e
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodglass_door.png differ
diff --git a/mods/xdecor/textures/xdecor_woodglass_door_inv.png b/mods/xdecor/textures/xdecor_woodglass_door_inv.png
new file mode 100644
index 00000000..663eab4b
Binary files /dev/null and b/mods/xdecor/textures/xdecor_woodglass_door_inv.png differ
diff --git a/mods/xdecor/textures/xdecor_workbench_bottom.png b/mods/xdecor/textures/xdecor_workbench_bottom.png
new file mode 100644
index 00000000..be5f8909
Binary files /dev/null and b/mods/xdecor/textures/xdecor_workbench_bottom.png differ
diff --git a/mods/xdecor/textures/xdecor_workbench_front.png b/mods/xdecor/textures/xdecor_workbench_front.png
new file mode 100644
index 00000000..3ef72368
Binary files /dev/null and b/mods/xdecor/textures/xdecor_workbench_front.png differ
diff --git a/mods/xdecor/textures/xdecor_workbench_sides.png b/mods/xdecor/textures/xdecor_workbench_sides.png
new file mode 100644
index 00000000..24521adb
Binary files /dev/null and b/mods/xdecor/textures/xdecor_workbench_sides.png differ
diff --git a/mods/xdecor/textures/xdecor_workbench_top.png b/mods/xdecor/textures/xdecor_workbench_top.png
new file mode 100644
index 00000000..852a83da
Binary files /dev/null and b/mods/xdecor/textures/xdecor_workbench_top.png differ