diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..d2d8860 --- /dev/null +++ b/config.lua @@ -0,0 +1,83 @@ +-- Enables the spray of particles out the back of a digger head and puffs of smoke from the controller +local particle_effects = minetest.settings:get_bool("enable_particles") + +-- this causes digtrons to operate without consuming fuel or building materials. +local digtron_uses_resources = minetest.settings:get_bool("digtron_uses_resources") +if digtron_uses_resources == nil then digtron_uses_resources = true end + +-- when true, lava counts as protected nodes. +local lava_impassible = minetest.settings:get_bool("digtron_lava_impassible") + +-- when true, diggers deal damage to creatures when they trigger. +local damage_creatures = minetest.settings:get_bool("digtron_damage_creatures") + +digtron.creative_mode = not digtron_uses_resources -- default false +digtron.particle_effects = particle_effects or particle_effects == nil -- default true +digtron.lava_impassible = lava_impassible or lava_impassible == nil -- default true +digtron.diggers_damage_creatures = damage_creatures or damage_creatures == nil -- default true + +-- maximum distance a builder head can extrude blocks +local maximum_extrusion = tonumber(minetest.settings:get("digtron_maximum_extrusion")) +if maximum_extrusion == nil or maximum_extrusion < 1 or maximum_extrusion > 100 then + digtron.maximum_extrusion = 25 +else + digtron.maximum_extrusion = maximum_extrusion +end + +-- How many seconds a digtron waits between cycles. Auto-controllers can make this wait longer, but cannot make it shorter. +local digtron_cycle_time = tonumber(minetest.settings:get("digtron_cycle_time")) +if digtron_cycle_time == nil or digtron_cycle_time < 0 then + digtron.cycle_time = 1.0 +else + digtron.cycle_time = digtron_cycle_time +end + +-- How many digtron nodes can be moved for each adjacent solid node that the digtron has traction against +local digtron_traction_factor = tonumber(minetest.settings:get("digtron_traction_factor")) +if digtron_traction_factor == nil or digtron_traction_factor < 0 then + digtron.traction_factor = 3.0 +else + digtron.traction_factor = digtron_traction_factor +end + +-- fuel costs. For comparison, in the default game: +-- one default tree block is 30 units +-- one coal lump is 40 units +-- one coal block is 370 units (apparently it's slightly more productive making your coal lumps into blocks before burning) +-- one book is 3 units + +-- how much fuel is required to dig a node if not in one of the following groups. +local digtron_dig_cost_default = tonumber(minetest.settings:get("digtron_dig_cost_default")) +if digtron_dig_cost_default == nil or digtron_dig_cost_default < 0 then + digtron.dig_cost_default = 0.5 +else + digtron.dig_cost_default = digtron_dig_cost_default +end +-- eg, stone +local digtron_dig_cost_cracky = tonumber(minetest.settings:get("digtron_dig_cost_cracky")) +if digtron_dig_cost_cracky == nil or digtron_dig_cost_cracky < 0 then + digtron.dig_cost_cracky = 1.0 +else + digtron.dig_cost_cracky = digtron_dig_cost_cracky +end +-- eg, dirt, sand +local digtron_dig_cost_crumbly = tonumber(minetest.settings:get("digtron_dig_cost_crumbly")) +if digtron_dig_cost_crumbly == nil or digtron_dig_cost_crumbly < 0 then + digtron.dig_cost_crumbly = 0.5 +else + digtron.dig_cost_crumbly = digtron_dig_cost_crumbly +end +-- eg, wood +local digtron_dig_cost_choppy = tonumber(minetest.settings:get("digtron_dig_cost_choppy")) +if digtron_dig_cost_choppy == nil or digtron_dig_cost_choppy < 0 then + digtron.dig_cost_choppy = 0.75 +else + digtron.dig_cost_choppy = digtron_dig_cost_choppy +end +-- how much fuel is required to build a node +local digtron_build_cost = tonumber(minetest.settings:get("digtron_build_cost")) +if digtron_build_cost == nil or digtron_build_cost < 0 then + digtron.build_cost = 1.0 +else + digtron.build_cost = digtron_build_cost +end \ No newline at end of file diff --git a/doc.lua b/doc.lua index 42fd5d2..275f5ac 100644 --- a/doc.lua +++ b/doc.lua @@ -17,7 +17,11 @@ digtron.doc.core_usagehelp = S("Place the Digtron Core in the center of the craf -------------------------------------------------------------------- digtron.doc.builder_longdesc = S("A 'builder' module for a Digtron. By itself it does nothing, but as part of a Digtron it is used to construct user-defined blocks.") -digtron.doc.builder_usagehelp = S("A builder head is the most complex component of this system. It has period and offset properties, and also an inventory slot where you \"program\" it by placing an example of the block type that you want it to build.".."\n\n".."When the \"Save & Show\" button is clicked the properties for period and offset will be saved, and markers will briefly be shown to indicate where the nearest spots corresponding to those values are. The builder will build its output at those locations provided it is moving along the matching axis." +digtron.doc.builder_usagehelp = S("A builder head is the most complex component of this system. It has period and offset properties, and also an inventory slot where you \"program\" it by placing an example of the block type that you want it to build." +.."\n\n".. +"When the \"Save & Show\" button is clicked the properties for period and offset will be saved, and markers will briefly be shown to indicate where the nearest spots corresponding to those values are. The builder will build its output at those locations provided it is moving along the matching axis." +.."\n\n".. +"There is also an \"Extrusion\" setting. This allows your builder to extrude a line of identical blocks from the builder output, in the direction the output side is facing, until it reaches an obstruction or until it reaches the extrusion limit. This can be useful for placing columns below a bridge, or for filling a large volume with a uniform block type without requiring a large number of builder heads." .."\n\n".. "The \"output\" side of a builder is the side with a black crosshair on it." .."\n\n".. diff --git a/init.lua b/init.lua index 324cd48..dbccbdd 100644 --- a/init.lua +++ b/init.lua @@ -4,6 +4,7 @@ digtron.auto_controller_colorize = "#88000030" digtron.pusher_controller_colorize = "#00880030" digtron.soft_digger_colorize = "#88880030" +dofile( minetest.get_modpath( "digtron" ) .. "/config.lua" ) dofile( minetest.get_modpath( "digtron" ) .. "/util.lua" ) dofile( minetest.get_modpath( "digtron" ) .. "/doc.lua" ) dofile( minetest.get_modpath( "digtron" ) .. "/awards.lua" ) @@ -19,82 +20,6 @@ dofile( minetest.get_modpath( "digtron" ) .. "/node_axle.lua" ) -- Rotation cont dofile( minetest.get_modpath( "digtron" ) .. "/node_crate.lua" ) -- Digtron portability support dofile( minetest.get_modpath( "digtron" ) .. "/recipes.lua" ) --- Enables the spray of particles out the back of a digger head and puffs of smoke from the controller -local particle_effects = minetest.settings:get_bool("enable_particles") - --- this causes digtrons to operate without consuming fuel or building materials. -local digtron_uses_resources = minetest.settings:get_bool("digtron_uses_resources") -if digtron_uses_resources == nil then digtron_uses_resources = true end - --- when true, lava counts as protected nodes. -local lava_impassible = minetest.settings:get_bool("digtron_lava_impassible") - --- when true, diggers deal damage to creatures when they trigger. -local damage_creatures = minetest.settings:get_bool("digtron_damage_creatures") - -digtron.creative_mode = not digtron_uses_resources -- default false -digtron.particle_effects = particle_effects or particle_effects == nil -- default true -digtron.lava_impassible = lava_impassible or lava_impassible == nil -- default true -digtron.diggers_damage_creatures = damage_creatures or damage_creatures == nil -- default true - --- How many seconds a digtron waits between cycles. Auto-controllers can make this wait longer, but cannot make it shorter. -local digtron_cycle_time = tonumber(minetest.settings:get("digtron_cycle_time")) -if digtron_cycle_time == nil or digtron_cycle_time < 0 then - digtron.cycle_time = 1.0 -else - digtron.cycle_time = digtron_cycle_time -end - --- How many digtron nodes can be moved for each adjacent solid node that the digtron has traction against -local digtron_traction_factor = tonumber(minetest.settings:get("digtron_traction_factor")) -if digtron_traction_factor == nil or digtron_traction_factor < 0 then - digtron.traction_factor = 3.0 -else - digtron.traction_factor = digtron_traction_factor -end - --- fuel costs. For comparison, in the default game: --- one default tree block is 30 units --- one coal lump is 40 units --- one coal block is 370 units (apparently it's slightly more productive making your coal lumps into blocks before burning) --- one book is 3 units - --- how much fuel is required to dig a node if not in one of the following groups. -local digtron_dig_cost_default = tonumber(minetest.settings:get("digtron_dig_cost_default")) -if digtron_dig_cost_default == nil or digtron_dig_cost_default < 0 then - digtron.dig_cost_default = 0.5 -else - digtron.dig_cost_default = digtron_dig_cost_default -end --- eg, stone -local digtron_dig_cost_cracky = tonumber(minetest.settings:get("digtron_dig_cost_cracky")) -if digtron_dig_cost_cracky == nil or digtron_dig_cost_cracky < 0 then - digtron.dig_cost_cracky = 1.0 -else - digtron.dig_cost_cracky = digtron_dig_cost_cracky -end --- eg, dirt, sand -local digtron_dig_cost_crumbly = tonumber(minetest.settings:get("digtron_dig_cost_crumbly")) -if digtron_dig_cost_crumbly == nil or digtron_dig_cost_crumbly < 0 then - digtron.dig_cost_crumbly = 0.5 -else - digtron.dig_cost_crumbly = digtron_dig_cost_crumbly -end --- eg, wood -local digtron_dig_cost_choppy = tonumber(minetest.settings:get("digtron_dig_cost_choppy")) -if digtron_dig_cost_choppy == nil or digtron_dig_cost_choppy < 0 then - digtron.dig_cost_choppy = 0.75 -else - digtron.dig_cost_choppy = digtron_dig_cost_choppy -end --- how much fuel is required to build a node -local digtron_build_cost = tonumber(minetest.settings:get("digtron_build_cost")) -if digtron_build_cost == nil or digtron_build_cost < 0 then - digtron.build_cost = 1.0 -else - digtron.build_cost = digtron_build_cost -end - -- digtron group numbers: -- 1 - generic digtron node, nothing special is done with these. They're just dragged along. -- 2 - inventory-holding digtron, has a "main" inventory that the digtron can add to and take from. diff --git a/node_builders.lua b/node_builders.lua index d7910c0..08a1727 100644 --- a/node_builders.lua +++ b/node_builders.lua @@ -9,8 +9,10 @@ local builder_formspec = default.gui_bg .. default.gui_bg_img .. default.gui_slots .. - "list[current_name;main;0.5,0;1,1;]" .. - "label[0.5,0.8;" .. S("Block to build") .. "]" .. + "list[current_name;main;0,0;1,1;]" .. + "label[0,0.8;" .. S("Block to build") .. "]" .. + "field[1.3,0.8;1,0.1;extrusion;" .. S("Extrusion") .. ";${extrusion}]" .. + "tooltip[extrusion;" .. S("Builder will extrude this many blocks in the direction it is facing.\nCan be set from 1 to @1.\nNote that Digtron won't build into unloaded map regions.", digtron.maximum_extrusion) .. "]" .. "field[2.3,0.8;1,0.1;period;" .. S("Periodicity") .. ";${period}]" .. "tooltip[period;" .. S("Builder will build once every n steps.\nThese steps are globally aligned, so all builders with the\nsame period and offset will build on the same location.") .. "]" .. "field[3.3,0.8;1,0.1;offset;" .. S("Offset") .. ";${offset}]" .. @@ -80,6 +82,7 @@ minetest.register_node("digtron:builder", { meta:set_int("period", 1) meta:set_int("offset", 0) meta:set_int("build_facing", 0) + meta:set_int("extrusion", 1) local inv = meta:get_inventory() inv:set_size("main", 1) @@ -90,7 +93,9 @@ minetest.register_node("digtron:builder", { local period = tonumber(fields.period) local offset = tonumber(fields.offset) local build_facing = tonumber(fields.build_facing) - if period and period > 0 then + local extrusion = tonumber(fields.extrusion) + + if period and period > 0 then meta:set_int("period", math.floor(tonumber(fields.period))) else period = meta:get_int("period") @@ -105,6 +110,11 @@ minetest.register_node("digtron:builder", { -- Should prevent that somehow. But not tonight. meta:set_int("build_facing", math.floor(build_facing)) end + if extrusion and extrusion > 0 and extrusion <= digtron.maximum_extrusion then + meta:set_int("extrusion", math.floor(tonumber(fields.extrusion))) + else + extrusion = meta:get_int("extrusion") + end if fields.set then local buildpos = digtron.find_new_pos(pos, minetest.get_node(pos).param2) @@ -178,10 +188,10 @@ minetest.register_node("digtron:builder", { -- If you're not supposed to build at all, or the location is obstructed, return 0 to let us know you're okay and we shouldn't abort." --return code and accompanying value: - -- 0, nil -- not supposed to build, no error - -- 1, {itemstack, source inventory pos} -- can build, took an item from inventory - -- 2, itemstack -- was supposed to build, but couldn't get the item from inventory - -- 3, nil -- builder configuration error + -- 0, {} -- not supposed to build, no error + -- 1, {{itemstack, source inventory pos}, ...} -- can build, took items from inventory + -- 2, {{itemstack, source inventory pos}, ...}, itemstack -- was supposed to build, but couldn't get the item from inventory + -- 3, {} -- builder configuration error test_build = function(pos, test_pos, inventory_positions, protected_nodes, nodes_dug, controlling_coordinate, controller_pos) local meta = minetest.get_meta(pos) local facing = minetest.get_node(pos).param2 @@ -189,80 +199,113 @@ minetest.register_node("digtron:builder", { if (buildpos[controlling_coordinate] + meta:get_int("offset")) % meta:get_int("period") ~= 0 then --It's not the builder's turn to build right now. - return 0, nil + return 0, {} end - if not digtron.can_move_to(buildpos, protected_nodes, nodes_dug) then - --using "can_move_to" instead of "can_build_to" test case in case the builder is pointed "backward", and will thus - --be building into the space that it's currently in and will be vacating after moving, or in case the builder is aimed - --sideways and a fellow digtron node was ahead of it (will also be moving out of the way). - - --If the player has built his digtron stupid (eg has another digtron node in the place the builder wants to build) this - --assumption is wrong, but I can't hold the player's hand through *every* possible bad design decision. Worst case, - --the digtron will think its inventory can't handle the next build step and abort the build when it actually could have - --managed one more cycle. That's not a bad outcome for a digtron array that was built stupidly to begin with. - --The player should be thanking me for all the error-checking I *do* do, really. - --Ungrateful wretch. - return 0, nil + local extrusion_count = 0 + local extrusion_target = meta:get_int("extrusion") + if extrusion_target == nil or extrusion_target > 100 then + extrusion_target = 100 -- failsafe end + local return_items = {} + local inv = minetest.get_inventory({type="node", pos=pos}) local item_stack = inv:get_stack("main", 1) - if not item_stack:is_empty() then + + if item_stack:is_empty() then + return 3, {} -- error code for "this builder's item slot is unset" + end + + while extrusion_count < extrusion_target do + if not digtron.can_move_to(buildpos, protected_nodes, nodes_dug) then + --using "can_move_to" instead of "can_build_to" test case in case the builder is pointed "backward", and will thus + --be building into the space that it's currently in and will be vacating after moving, or in case the builder is aimed + --sideways and a fellow digtron node was ahead of it (will also be moving out of the way). + + --If the player has built his digtron stupid (eg has another digtron node in the place the builder wants to build) this + --assumption is wrong, but I can't hold the player's hand through *every* possible bad design decision. Worst case, + --the digtron will think its inventory can't handle the next build step and abort the build when it actually could have + --managed one more cycle. That's not a bad outcome for a digtron array that was built stupidly to begin with. + return 1, return_items + end + local source_location = digtron.take_from_inventory(item_stack:get_name(), inventory_positions) if source_location ~= nil then - return 1, {item=item_stack, location=source_location} + table.insert(return_items, {item=item_stack, location=source_location}) + else + return 2, return_items, item_stack -- error code for "needed an item but couldn't get it from inventory" end - return 2, item_stack -- error code for "needed an item but couldn't get it from inventory" - else - return 3, nil -- error code for "this builder's item slot is unset" + extrusion_count = extrusion_count + 1 + buildpos = digtron.find_new_pos(buildpos, facing) end + + return 1, return_items end, execute_build = function(pos, player, inventory_positions, protected_nodes, nodes_dug, controlling_coordinate, controller_pos) local meta = minetest.get_meta(pos) - local build_facing = meta:get_int("build_facing") + local build_facing = tonumber(meta:get_int("build_facing")) local facing = minetest.get_node(pos).param2 local buildpos = digtron.find_new_pos(pos, facing) - local oldnode = minetest.get_node(buildpos) if (buildpos[controlling_coordinate] + meta:get_int("offset")) % meta:get_int("period") ~= 0 then - return nil + return 0 end - if digtron.can_build_to(buildpos, protected_nodes, nodes_dug) then - local inv = minetest.get_inventory({type="node", pos=pos}) - local item_stack = inv:get_stack("main", 1) - if not item_stack:is_empty() then - - if digtron.creative_mode then - local returned_stack, success = digtron.item_place_node(item_stack, player, buildpos, tonumber(build_facing)) - if success == true then - minetest.log("action", string.format(S("%s uses Digtron to build %s at (%d, %d, %d), displacing %s"), player:get_player_name(), item_stack:get_name(), buildpos.x, buildpos.y, buildpos.z, oldnode.name)) - nodes_dug:set(buildpos.x, buildpos.y, buildpos.z, false) - return true - end - return nil - end - - local sourcepos = digtron.take_from_inventory(item_stack:get_name(), inventory_positions) - if sourcepos == nil then - -- item not in inventory! Need to sound the angry buzzer to let the player know, so return false. - return false - end - local returned_stack, success = digtron.item_place_node(item_stack, player, buildpos, tonumber(build_facing)) + local extrusion_count = 0 + local extrusion_target = meta:get_int("extrusion") + if extrusion_target == nil or extrusion_target > 100 then + extrusion_target = 100 -- failsafe + end + local built_count = 0 + + local inv = minetest.get_inventory({type="node", pos=pos}) + local item_stack = inv:get_stack("main", 1) + if item_stack:is_empty() then + return built_count + end + + while extrusion_count < extrusion_target do + if not digtron.can_build_to(buildpos, protected_nodes, nodes_dug) then + return built_count + end + + local oldnode = minetest.get_node(buildpos) + + if digtron.creative_mode then + local returned_stack, success = digtron.item_place_node(item_stack, player, buildpos, build_facing) if success == true then minetest.log("action", string.format(S("%s uses Digtron to build %s at (%d, %d, %d), displacing %s"), player:get_player_name(), item_stack:get_name(), buildpos.x, buildpos.y, buildpos.z, oldnode.name)) - --flag this node as *not* to be dug. nodes_dug:set(buildpos.x, buildpos.y, buildpos.z, false) - digtron.award_item_built(item_stack:get_name(), player:get_player_name()) - return true + built_count = built_count + 1 else - --failed to build, target node probably obstructed. Put the item back in inventory. - digtron.place_in_specific_inventory(item_stack, sourcepos, inventory_positions, controller_pos) - return nil + return built_count end end + + local sourcepos = digtron.take_from_inventory(item_stack:get_name(), inventory_positions) + if sourcepos == nil then + -- item not in inventory! Need to sound the angry buzzer to let the player know, so return a negative number. + return (built_count + 1) * -1 + end + local returned_stack, success = digtron.item_place_node(item_stack, player, buildpos, build_facing) + if success == true then + minetest.log("action", string.format(S("%s uses Digtron to build %s at (%d, %d, %d), displacing %s"), player:get_player_name(), item_stack:get_name(), buildpos.x, buildpos.y, buildpos.z, oldnode.name)) + --flag this node as *not* to be dug. + nodes_dug:set(buildpos.x, buildpos.y, buildpos.z, false) + digtron.award_item_built(item_stack:get_name(), player:get_player_name()) + built_count = built_count + 1 + else + --failed to build, target node probably obstructed. Put the item back in inventory. + --Should probably never reach this since we're guarding against can_build_to, above, but this makes things safe if we somehow do. + digtron.place_in_specific_inventory(item_stack, sourcepos, inventory_positions, controller_pos) + return built_count + end + + extrusion_count = extrusion_count + 1 + buildpos = digtron.find_new_pos(buildpos, facing) end + return built_count end, }) \ No newline at end of file diff --git a/settingtypes.txt b/settingtypes.txt index bf1ce10..228c605 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -2,17 +2,24 @@ #though they still check whether they have enough in inventory. #It's a separate setting from regular creative mode. digtron_uses_resources (Digtron uses resources) bool true + #When true, lava counts as protected blocks. digtron_lava_impassible (Lava is impassible to Digtrons) bool true + #When true, diggers deal damage to creatures when they trigger. digtron_damage_creatures (Digtrons cause damage) bool true + #How many seconds a digtron waits between cycles. #Auto-controllers can make this wait longer, but cannot make it shorter. digtron_cycle_time (Minimum Digtron cycle time in seconds) float 1.0 0.0 60.0 + #How many Digtron blocks can be moved for each adjacent #solid block that the Digtron has traction against digtron_traction_factor (Digtron traction factor) float 3.0 0.0 1000.0 +#The maximum extrusion setting permitted for a Digtron builder module. +digtron_maximum_extrusion (Digtron maximum extrusion) int 25 1 100 + [Fuel costs] #eg, stone. diff --git a/util_execute_cycle.lua b/util_execute_cycle.lua index 1dc47fa..0e0e092 100644 --- a/util_execute_cycle.lua +++ b/util_execute_cycle.lua @@ -180,7 +180,8 @@ digtron.execute_dig_cycle = function(pos, clicker) -- Note that this test may overestimate the amount of work that will actually need to be done so don't treat its fuel cost as authoritative. local can_build = true local test_build_return_code = nil - local test_build_return_item = nil + local test_build_return_items = nil + local failed_to_find = nil local test_items = {} local test_fuel_items = {} local test_build_fuel_cost = 0 @@ -189,15 +190,15 @@ digtron.execute_dig_cycle = function(pos, clicker) local targetdef = minetest.registered_nodes[target.name] local test_location = vector.add(location.pos, dir) if targetdef.test_build ~= nil then - test_build_return_code, test_build_return_item = targetdef.test_build(location.pos, test_location, layout.inventories, layout.protected, layout.nodes_dug, controlling_coordinate, layout.controller) + test_build_return_code, test_build_return_items, failed_to_find = targetdef.test_build(location.pos, test_location, layout.inventories, layout.protected, layout.nodes_dug, controlling_coordinate, layout.controller) + for k, return_item in pairs(test_build_return_items) do + table.insert(test_items, return_item) + test_build_fuel_cost = test_build_fuel_cost + digtron.build_cost + end if test_build_return_code > 1 then can_build = false break end - if test_build_return_code == 1 then - table.insert(test_items, test_build_return_item) - test_build_fuel_cost = test_build_fuel_cost + digtron.build_cost - end else minetest.log(string.format("%s has builder group but is missing test_build method! This is an error in mod programming, file a bug.", targetdef.name)) end @@ -231,7 +232,7 @@ digtron.execute_dig_cycle = function(pos, clicker) elseif test_build_return_code == 2 then minetest.sound_play("dingding", {gain=1.0, pos=pos}) -- Insufficient inventory return_string = string.format(S("Digtron has insufficient building materials. Needed: %s") .. "\n", - test_build_return_item:get_name()) + failed_to_find:get_name()) return_code = 7 end return pos, return_string .. status_text, return_code --Abort, don't dig and don't build. @@ -281,13 +282,14 @@ digtron.execute_dig_cycle = function(pos, clicker) if targetdef.execute_build ~= nil then --using the old location of the controller as fallback so that any leftovers land with the rest of the digger output. Not that there should be any. local build_return = targetdef.execute_build(location.pos, clicker, layout.inventories, layout.protected, layout.nodes_dug, controlling_coordinate, oldpos) - if build_return == false then + if build_return < 0 then -- This happens if there's insufficient inventory, but we should have confirmed there was sufficient inventory during test phase. -- So this should never happen. However, "should never happens" happen sometimes. So -- don't interrupt the build cycle as a whole, we've already moved so might as well try to complete as much as possible. strange_failure = true - elseif build_return == true and not digtron.creative_mode == true then - building_fuel_cost = building_fuel_cost + digtron.build_cost + build_return = (build_return * -1) - 1 + elseif not digtron.creative_mode == true then + building_fuel_cost = building_fuel_cost + (digtron.build_cost * build_return) end else minetest.log(string.format("%s has builder group but is missing execute_build method! This is an error in mod programming, file a bug.", targetdef.name))