--[[ X Farming. Extends Luanti farming mod with new plants, crops and ice fishing. Copyright (C) 2025 SaKeL 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 juraj.vajda@gmail.com --]] local S = core.get_translator(core.get_current_modname()) x_farming.register_plant('x_farming:rice', { description = S('Rice Seed') .. '\n' .. S('Compost chance') .. ': 30%', short_description = S('Rice Seed'), tiles = { 'x_farming_silt_loam_soil.png' }, inventory_image = 'x_farming_rice_seed_inv.png', steps = 8, minlight = 13, maxlight = 14, fertility = { 'grassland' }, drawtype = 'plantlike_rooted', paramtype2 = 'none', visual_scale = 3.0, special_tiles = 'x_farming_rice_seed.png', node_dig_prediction = 'x_farming:silt_loam_soil', groups = { -- MTG attached_node = 0, -- VL plant = 1, dig_by_water = 0, destroy_by_lava_flow = 1, dig_by_piston = 1, compostability = 30, -- X Farming compost = 30, }, node_placement_prediction = '', buildable_to = false, floodable = false, selection_box = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 1.5, 4 / 16 }, }, }, selection_box_1 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 1.5, 4 / 16 }, }, }, selection_box_2 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 1.5, 4 / 16 }, }, }, selection_box_3 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 2, 4 / 16 }, }, }, selection_box_4 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 2.5, 4 / 16 }, }, }, selection_box_5 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 2.5, 4 / 16 }, }, }, selection_box_6 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 3, 4 / 16 }, }, }, selection_box_7 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 3, 4 / 16 }, }, }, selection_box_8 = { type = 'fixed', fixed = { { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }, { -4 / 16, 0.5, -4 / 16, 4 / 16, 3, 4 / 16 }, }, }, on_place = function(itemstack, placer, pointed_thing) if pointed_thing.type ~= 'node' or not placer then return itemstack end local player_name = placer:get_player_name() local pos_under = pointed_thing.under local pos_above = pointed_thing.above local node_under = core.get_node(pos_under) local def_under = core.registered_nodes[node_under.name] if def_under and def_under.on_rightclick and not placer:get_player_control().sneak then return def_under.on_rightclick(pos_under, node_under, placer, itemstack, pointed_thing) or itemstack end local water_above = core.find_nodes_in_area( vector.new(pos_above), vector.new(pos_above.x, pos_above.y + 2, pos_above.z), { 'group:water' } ) if node_under.name ~= 'x_farming:silt_loam_soil' or core.get_item_group(core.get_node(pos_above).name, 'water') == 0 or #water_above ~= 1 then return itemstack end if core.is_protected(pos_under, player_name) or core.is_protected(pos_above, player_name) then core.record_protection_violation(pos_under, player_name) return itemstack end node_under.name = itemstack:get_name() core.set_node(pos_under, node_under) x_farming.tick(pos_under) if not core.is_creative_enabled(player_name) then itemstack:take_item() end return itemstack end, after_destruct = function(pos, oldnode) core.set_node(pos, { name = 'x_farming:silt_loam_soil' }) end, on_timer = function(pos, elapsed) local node = core.get_node(pos) local name = node.name local def = core.registered_nodes[name] if not def.next_plant then -- disable timer for fully grown plant return end -- grow seed if core.get_item_group(node.name, 'seed') and def.fertility then local water_above = core.find_nodes_in_area( vector.new(pos), vector.new(pos.x, pos.y + 2, pos.z), { 'default:water_source' } ) if #water_above ~= 1 then x_farming.tick_again(pos) return end -- omitted is a check for light, we assume seeds can germinate in the dark. local placenode = { name = def.next_plant } if def.place_param2 then placenode.param2 = def.place_param2 end core.swap_node(pos, placenode) if core.registered_nodes[def.next_plant].next_plant then x_farming.tick(pos) return end return end -- check if emerged in water local water_above = core.find_nodes_in_area( vector.new(pos), vector.new(pos.x, pos.y + 2, pos.z), { 'default:water_source' } ) local air_above = core.find_nodes_in_area( vector.new(pos), vector.new(pos.x, pos.y + 3, pos.z), { 'air' } ) if #water_above ~= 1 or #air_above ~= 2 then x_farming.tick_again(pos) return end -- check light local light = core.get_node_light(pos) if not light or light < def.minlight or light > def.maxlight then x_farming.tick_again(pos) return end -- grow local placenode = { name = def.next_plant } if def.place_param2 then placenode.param2 = def.place_param2 end core.swap_node(pos, placenode) -- new timer needed? if core.registered_nodes[def.next_plant].next_plant then x_farming.tick(pos) end end }) -- Registered before the stairs so the stairs get fuel recipes. core.register_craft({ type = 'fuel', recipe = 'x_farming:rice_stack', burntime = 3, }) if core.get_modpath('stairs') then do local recipe = 'x_farming:rice' local groups = { snappy = 3, flammable = 4 } local images = { 'x_farming_rice_stack_side.png' } local sounds = x_farming.node_sound_leaves_defaults() stairs.register_stair('rice_stack', recipe, groups, images, S('Rice Stack Stair'), sounds, true) stairs.register_stair_inner('rice_stack', recipe, groups, images, '', sounds, true, S('Inner Rice Stack Stair')) stairs.register_stair_outer('rice_stack', recipe, groups, images, '', sounds, true, S('Outer Rice Stack Stair')) stairs.register_slab('rice_stack', recipe, groups, images, S('Rice Stack Slab'), sounds, true) end end -- crate x_farming.register_crate('crate_rice_3', { description = S('Rice Crate'), short_description = S('Rice Crate'), tiles = { 'x_farming_crate_rice_3.png' }, _custom = { crate_item = 'x_farming:rice' } }) -- bag x_farming.register_bag('bag_rice_grains', { description = S('Rice Grain') .. ' Bag', short_description = S('Rice Grain') .. ' Bag', tiles = { 'x_farming_bag_rice_grains.png' }, _custom = { bag_item = 'x_farming:rice_grains' } }) core.register_on_mods_loaded(function() local deco_place_on = {} local deco_biomes = {} -- MTG if core.get_modpath('default') then table.insert(deco_place_on, 'default:dirt') table.insert(deco_place_on, 'default:dry_dirt') table.insert(deco_biomes, 'rainforest_swamp') table.insert(deco_biomes, 'savanna_shore') end -- MCL if core.get_modpath('mcl_core') then table.insert(deco_place_on, 'mcl_core:sand') table.insert(deco_biomes, 'Savanna_beach') end if next(deco_place_on) and next(deco_biomes) then core.register_decoration({ name = 'x_farming:rice', deco_type = 'simple', place_on = deco_place_on, sidelen = 16, noise_params = { offset = -0.3, scale = 0.7, spread = { x = 200, y = 200, z = 200 }, seed = 354, octaves = 3, persist = 0.7 }, biomes = deco_biomes, y_max = -1, y_min = -1, decoration = { 'x_farming:rice_5', 'x_farming:rice_6', 'x_farming:rice_7', 'x_farming:rice_8' }, flags = 'force_placement' }) end end)