diff --git a/README.md b/README.md index c620d50..9dccab1 100644 --- a/README.md +++ b/README.md @@ -27,41 +27,44 @@ throwing.allow_arrow_placing = false There are two available functions in the mod API: ```lua -function throwing.register_bow(name, itemcraft, description, texture[, groups]) +function throwing.register_bow(name, definition) --[[ Name: Bow name (in second part of the itemstring). -Itemcraft: item used to craft the bow (nil if uncraftable). -Description: Description of the bow. -Texture: Texture of the bow, shown in inventory. -Groups: optional groups. +Definition: definition table, containing: + * itemcraft (optional, you may want to register your own craft or to make the bow uncraftable): item used to craft the bow. + * description (highly recommended): description of the bow. + * texture (essential): texture of the bow, shown in inventory. + * groups (optional): groups of the item. ]] -- Example: throwing.register_bow("bow_stone", "default:cobble", "Stone Bow", "throwing_bow_stone.png") - -function throwing.register_arrow(name, itemcraft, craft_quantity, description, tiles, on_hit_sound, on_hit[, on_throw[, groups]]) +itemcraft, craft_quantity, description, tiles, on_hit_sound, on_hit[, on_throw[, groups]] +function throwing.register_arrow(name, definition table) --[[ Name: Arrow name (in second part of the itemstring). -Itemcraft: item used to craft the arrow (nil if uncraftable). -Craft_quantity: quantity of arrows in the craft output. -Tiles: tiles of the arrow. -On_hit_sound: sound played when the arrow hits a node or an object (nil if no sound). -On_hit: callback function: on_hit(pos, last_pos, node, object, hitter, self) where: - * Pos: the position of the hitted node or object - * Last_pos: the last air node where the arrow was (used by the build_arrow, for example) - * Node and object: hit node or object. Either node or object is nil, depending - whether the arrow hit a node or an object (you should always check for that). - An object can be a player or a luaentity. - * Hitter: the ObjectRef of the player who threw the arrow. - * Self: the arrow entity table (it allows you to hack a lot!) - * When it fails, it should return: - false[, reason] -On_throw: option callback function: on_throw(pos, thrower, self) where: - * Pos: the position from where the arrow is throw (which a bit higher than the hitter position) - * Thrower: the ObjectRef of the thrower - * Self: the arrow entity table - * Should return false if the arrow should not be throw +Definition: definition table, containing: + * itemcraft (optional, you may want to register your own craft or to make the arrow uncraftable): item used to craft the arrow. + * craft_quantity (optional, defaulting to 1 if itemcraft is non-nil, pointless otherwise): quantity of arrows in the craft output. + * tiles (essential): tiles of the arrow. + * target (optional, defaulting to throwing.target_both): what the arrow is able to hit (throwing.target_node, throwing.target_object, throwing.target_both). + * on_hit_sound (optional): sound played when the arrow hits a node or an object. + * on_hit (must exist, will crash if nil): callback function: on_hit(pos, last_pos, node, object, hitter, self): + - pos: the position of the hit node or object. + - last_pos: the last air node where the arrow was + - node and object: hit node or object. Either node or object is nil, depending + whether the arrow hit a node or an object. + - hitter: an ObjectRef to the thrower player. + - self: the arrow entity table (it allows you to hack a lot!) + - If it fails, it should return: + false[, reason] + * on_throw (optional): callback function: on_throw(pos, thrower, next_itemstack, self): + - pos: the position from where the arrow is throw (which a bit higher than the hitter position) + - thrower: an ObjectRef to the thrower player + - next_itemstack: the ItemStack next to the arrow in the "main" inventory + - If the arrow shouldn't be throw, it should return false. + * on_throw_sound (optional, there is a default sound, specify "" for no sound): sound to be played when the arrow is throw ]] -- Examples: diff --git a/init.lua b/init.lua index 43404a0..23371f7 100644 --- a/init.lua +++ b/init.lua @@ -2,6 +2,10 @@ throwing = {} throwing.arrows = {} +throwing.target_object = 1 +throwing.target_node = 2 +throwing.target_both = 3 + throwing.modname = minetest.get_current_modname() --------- Arrows functions --------- @@ -29,7 +33,10 @@ local function shoot_arrow(itemstack, player) obj:setvelocity({x=dir.x*velocity_factor, y=dir.y*velocity_factor, z=dir.z*velocity_factor}) obj:setacceleration({x=dir.x*horizontal_acceleration_factor, y=vertical_acceleration, z=dir.z*horizontal_acceleration_factor}) obj:setyaw(player:get_look_horizontal()-math.pi/2) - minetest.sound_play("throwing_sound", {pos=playerpos, gain = 0.5}) + + if luaentity.on_throw_sound ~= "" then + minetest.sound_play(luaentity.on_throw_sound or "throwing_sound", {pos=playerpos, gain = 0.5}) + end if not minetest.setting_getbool("creative_mode") then player:get_inventory():remove_item("main", arrow) @@ -54,13 +61,11 @@ local function arrow_step(self, dtime) if obj then if obj:is_player() then if obj:get_player_name() == self.player then -- Avoid hitting the hitter - return + return false end end end - self.object:remove() - local player = minetest.get_player_by_name(self.player) if not player then -- Possible if the player disconnected return @@ -118,7 +123,13 @@ local function arrow_step(self, dtime) logging("reached ignore. Removing.") return elseif node.name ~= "air" then - hit(pos, node, nil) + if self.target ~= throwing.target_object then -- throwing.target_both, nil, throwing.target_node, or any invalid value + if hit(pos, node, nil) ~= false then + self.object:remove() + end + else + self.object:remove() + end return end @@ -127,10 +138,22 @@ local function arrow_step(self, dtime) for k, obj in pairs(objs) do if obj:get_luaentity() then if obj:get_luaentity().name ~= self.name and obj:get_luaentity().name ~= "__builtin:item" then - hit(pos, nil, obj) + if self.target ~= throwing.target_node then -- throwing.target_both, nil, throwing.target_object, or any invalid value + if hit(pos, nil, obj) ~= false then + self.object:remove() + end + else + self.object:remove() + end end else - hit(pos, nil, obj) + if self.target ~= throwing.target_node then -- throwing.target_both, nil, throwing.target_object, or any invalid value + if hit(pos, nil, obj) ~= false then + self.object:remove() + end + else + self.object:remove() + end end end @@ -147,13 +170,13 @@ Should return false or false, reason on failure. on_throw(pos, hitter) Unlike on_hit, it is optional. ]] -function throwing.register_arrow(name, itemcraft, craft_quantity, description, tiles, on_hit_sound, on_hit, on_throw, groups) +function throwing.register_arrow(name, def) table.insert(throwing.arrows, throwing.modname..":"..name) - local _groups = {dig_immediate = 3} - if groups then - for k, v in pairs(groups) do - _groups[k] = v + local groups = {dig_immediate = 3} + if def.groups then + for k, v in pairs(def.groups) do + groups[k] = v end end minetest.register_node(throwing.modname..":"..name, { @@ -179,10 +202,10 @@ function throwing.register_arrow(name, itemcraft, craft_quantity, description, t {7.5/17, -2.5/17, -2.5/17, 8.5/17, -3.5/17, -3.5/17}, } }, - tiles = tiles, - inventory_image = tiles[1], - description = description, - groups = _groups, + tiles = def.tiles, + inventory_image = def.tiles[1], + description = def.description, + groups = groups, on_place = function(itemstack, placer, pointed_thing) if minetest.setting_getbool("throwing.allow_arrow_placing") and pointed_thing.above then local playername = placer:get_player_name() @@ -209,25 +232,27 @@ function throwing.register_arrow(name, itemcraft, craft_quantity, description, t visual_size = {x = 0.125, y = 0.125}, textures = {throwing.modname..":"..name}, collisionbox = {0, 0, 0, 0, 0, 0}, - on_hit = on_hit, - on_hit_sound = on_hit_sound, - on_throw = on_throw, + on_hit = def.on_hit, + on_hit_sound = def.on_hit_sound, + on_throw_sound = def.on_throw_sound, + on_throw = def.on_throw, + target = def.target, node = throwing.modname..":"..name, player = "", on_step = arrow_step }) - if itemcraft then + if def.itemcraft then minetest.register_craft({ - output = throwing.modname..":"..name.." "..craft_quantity, + output = throwing.modname..":"..name.." "..tostring(def.craft_quantity or 1), recipe = { - {itemcraft, "default:stick", "default:stick"} + {def.itemcraft, "default:stick", "default:stick"} } }) minetest.register_craft({ - output = throwing.modname..":"..name.." "..craft_quantity, + output = throwing.modname..":"..name.." "..tostring(def.craft_quantity or 1), recipe = { - { "default:stick", "default:stick", itemcraft} + { "default:stick", "default:stick", def.itemcraft} } }) end @@ -235,10 +260,10 @@ end ---------- Bows ----------- -function throwing.register_bow(name, itemcraft, description, texture, groups) +function throwing.register_bow(name, def) minetest.register_tool(throwing.modname..":"..name, { - description = description, - inventory_image = texture, + description = def.description, + inventory_image = def.texture, on_use = function(itemstack, user, pointed_thing) if shoot_arrow(itemstack, user, pointed_thing) then if not minetest.setting_getbool("creative_mode") then @@ -247,16 +272,16 @@ function throwing.register_bow(name, itemcraft, description, texture, groups) end return itemstack end, - groups = groups + groups = def.groups }) - if itemcraft then + if def.itemcraft then minetest.register_craft({ output = throwing.modname..":"..name, recipe = { - {"farming:cotton", itemcraft, ""}, - {"farming:cotton", "", itemcraft}, - {"farming:cotton", itemcraft, ""}, + {"farming:cotton", def.itemcraft, ""}, + {"farming:cotton", "", def.itemcraft}, + {"farming:cotton", def.itemcraft, ""}, } }) end diff --git a/registration.lua b/registration.lua index b809819..346a9f7 100644 --- a/registration.lua +++ b/registration.lua @@ -1,9 +1,33 @@ -throwing.register_bow("bow_wood", "default:wood", "Wooden Bow", "throwing_bow_wood.png") -throwing.register_bow("bow_stone", "default:cobble", "Stone Bow", "throwing_bow_stone.png") -throwing.register_bow("bow_steel", "default:steel_ingot", "Steel Bow", "throwing_bow_steel.png") -throwing.register_bow("bow_bronze", "default:bronze_ingot", "Bronze Bow", "throwing_bow_bronze.png") -throwing.register_bow("bow_mese", "default:mese_crystal", "Mese Bow", "throwing_bow_mese.png") -throwing.register_bow("bow_diamond", "default:diamond", "Diamond Bow", "throwing_bow_diamond.png") +throwing.register_bow("bow_wood", { + itemcraft = "default:wood", + description = "Wooden Bow", + texture = "throwing_bow_wood.png" +}) +throwing.register_bow("bow_stone", { + itemcraft = "default:cobble", + description = "Stone Bow", + texture = "throwing_bow_stone.png" +}) +throwing.register_bow("bow_steel", { + itemcraft = "default:steel_ingot", + description = "Steel Bow", + texture = "throwing_bow_steel.png" +}) +throwing.register_bow("bow_bronze", { + itemcraft = "default:bronze_ingot", + description = "Bronze Bow", + texture = "throwing_bow_bronze.png" +}) +throwing.register_bow("bow_mese", { + itemcraft = "default:mese_crystal", + description = "Mese Bow", + texture = "throwing_bow_mese.png" +}) +throwing.register_bow("bow_diamond", { + itemcraft = "default:diamond", + description = "Diamond Bow", + texture = "throwing_bow_diamond.png" +}) local function get_setting(name) local value = minetest.setting_getbool("throwing.enable_"..name) @@ -15,108 +39,120 @@ local function get_setting(name) end if get_setting("arrow") then - throwing.register_arrow("arrow", "default:steel_ingot", 16, "Arrow", - {"throwing_arrow.png", "throwing_arrow.png", "throwing_arrow_back.png", "throwing_arrow_front.png", "throwing_arrow_2.png", "throwing_arrow.png"}, "throwing_arrow", - function(pos, _, _, object, hitter) - if not object then - return + throwing.register_arrow("arrow", { + itemcraft = "default:steel_ingot", + craft_quantity = 16, + description = "Arrow", + tiles = {"throwing_arrow.png", "throwing_arrow.png", "throwing_arrow_back.png", "throwing_arrow_front.png", "throwing_arrow_2.png", "throwing_arrow.png"}, + target = throwing.target_object, + on_hit_sound = "throwing_arrow", + on_hit = function(pos, _, _, object, hitter) + object:punch(hitter, 1, { + full_punch_interval = 1, + damage_groups = {fleshy = 3} + }) end - object:punch(hitter, 1, { - full_punch_interval = 1, - damage_groups = {fleshy = 3} - }) - end) + }) end if get_setting("golden_arrow") then - throwing.register_arrow("arrow_gold", "default:gold_ingot", 16, "Golden Arrow", - {"throwing_arrow_gold.png", "throwing_arrow_gold.png", "throwing_arrow_gold_back.png", "throwing_arrow_gold_front.png", "throwing_arrow_gold_2.png", "throwing_arrow_gold.png"}, "throwing_arrow", - function(pos, _, _, object, hitter) - if not object then - return + throwing.register_arrow("arrow_gold", { + itemcraft = "default:gold_ingot", + craft_quantity = 16, + description = "Golden Arrow", + tiles = {"throwing_arrow_gold.png", "throwing_arrow_gold.png", "throwing_arrow_gold_back.png", "throwing_arrow_gold_front.png", "throwing_arrow_gold_2.png", "throwing_arrow_gold.png"}, + target = throwing.target_object, + on_hit_sound = "throwing_arrow", + on_hit = function(pos, _, _, object, hitter) + object:punch(hitter, 1, { + full_punch_interval = 1, + damage_groups = {fleshy = 5} + }) end - object:punch(hitter, 1, { - full_punch_interval = 1, - damage_groups = {fleshy = 5} - }) - end) + }) end if get_setting("dig_arrow") then - throwing.register_arrow("arrow_dig", "default:pick_wood", 1, "Dig Arrow", - {"throwing_arrow_dig.png", "throwing_arrow_dig.png", "throwing_arrow_dig_back.png", "throwing_arrow_dig_front.png", "throwing_arrow_dig_2.png", "throwing_arrow_dig.png"}, "throwing_dig_arrow", - function(pos, _, node, _, hitter) - if not node then - return + throwing.register_arrow("arrow_dig", { + itemcraft = "default:pick_wood", + description = "Dig Arrow", + tiles = {"throwing_arrow_dig.png", "throwing_arrow_dig.png", "throwing_arrow_dig_back.png", "throwing_arrow_dig_front.png", "throwing_arrow_dig_2.png", "throwing_arrow_dig.png"}, + target = throwing.target_node, + on_hit_sound = "throwing_dig_arrow", + on_hit = function(pos, _, node, _, hitter) + return minetest.dig_node(pos) end - return minetest.dig_node(pos) - end) + }) end if get_setting("dig_arrow_admin") then - throwing.register_arrow("arrow_dig_admin", nil, nil, "Admin Dig Arrow", - {"throwing_arrow_dig.png", "throwing_arrow_dig.png", "throwing_arrow_dig_back.png", "throwing_arrow_dig_front.png", "throwing_arrow_dig_2.png", "throwing_arrow_dig.png"}, nil, - function(pos, _, node, _, _) - if not node then - return - end - minetest.remove_node(pos) - end, nil, {not_in_creative_inventory = 1}) + throwing.register_arrow("arrow_dig_admin", { + description = "Admin Dig Arrow", + tiles = {"throwing_arrow_dig.png", "throwing_arrow_dig.png", "throwing_arrow_dig_back.png", "throwing_arrow_dig_front.png", "throwing_arrow_dig_2.png", "throwing_arrow_dig.png"}, + target = throwing.target_node, + on_hit = function(pos, _, node, _, _) + minetest.remove_node(pos) + end, + groups = {not_in_creative_inventory = 1} + }) end if get_setting("teleport_arrow") then - throwing.register_arrow("arrow_teleport", "default:diamond", 1, "Teleport Arrow", - {"throwing_arrow_teleport.png", "throwing_arrow_teleport.png", "throwing_arrow_teleport_back.png", "throwing_arrow_teleport_front.png", "throwing_arrow_teleport_2.png", "throwing_arrow_teleport.png"}, "throwing_teleport_arrow", - function(_, last_pos, node, _, hitter) - if not node then - return - end - if minetest.get_node(last_pos).name ~= "air" then - minetest.log("warning", "[throwing] BUG: node at last_pos was not air") - return - end + throwing.register_arrow("arrow_teleport", { + itemcraft = "default:diamond", + description = "Teleport Arrow", + tiles = {"throwing_arrow_teleport.png", "throwing_arrow_teleport.png", "throwing_arrow_teleport_back.png", "throwing_arrow_teleport_front.png", "throwing_arrow_teleport_2.png", "throwing_arrow_teleport.png"}, + on_hit_sound = "throwing_teleport_arrow", + on_hit = function(_, last_pos, _, _, hitter) + if minetest.get_node(last_pos).name ~= "air" then + minetest.log("warning", "[throwing] BUG: node at last_pos was not air") + return + end - hitter:moveto(last_pos) - end) + hitter:moveto(last_pos) + end + }) end if get_setting("fire_arrow") then - throwing.register_arrow("arrow_fire", "default:torch", 1, "Torch Arrow", - {"throwing_arrow_fire.png", "throwing_arrow_fire.png", "throwing_arrow_fire_back.png", "throwing_arrow_fire_front.png", "throwing_arrow_fire_2.png", "throwing_arrow_fire.png"}, "default_place_node", - function(_, last_pos, node, _, hitter) - if not node then - return - end - if minetest.get_node(last_pos).name ~= "air" then - minetest.log("warning", "[throwing] BUG: node at last_pos was not air") - return - end + throwing.register_arrow("arrow_fire", { + itemcraft = "default:torch", + description = "Torch Arrow", + tiles = {"throwing_arrow_fire.png", "throwing_arrow_fire.png", "throwing_arrow_fire_back.png", "throwing_arrow_fire_front.png", "throwing_arrow_fire_2.png", "throwing_arrow_fire.png"}, + on_hit_sound = "default_place_node", + on_hit = function(_, last_pos, _, _, hitter) + if minetest.get_node(last_pos).name ~= "air" then + minetest.log("warning", "[throwing] BUG: node at last_pos was not air") + return + end - local under_node_name = minetest.get_node({x = last_pos.x, y = last_pos.y-1, z = last_pos.z}).name - if under_node_name ~= "air" and name ~= "ignore" then - minetest.place_node(last_pos, {name="default:torch"}) - else - return false, "Attached node default:torch can not be placed" + local under_node_name = minetest.get_node({x = last_pos.x, y = last_pos.y-1, z = last_pos.z}).name + if under_node_name ~= "air" and name ~= "ignore" then + minetest.place_node(last_pos, {name="default:torch"}) + else + return false, "Attached node default:torch can not be placed" + end end - end) + }) end if get_setting("build_arrow") then - throwing.register_arrow("arrow_build", "default:obsidian_glass", 1, "Build Arrow", - {"throwing_arrow_build.png", "throwing_arrow_build.png", "throwing_arrow_build_back.png", "throwing_arrow_build_front.png", "throwing_arrow_build_2.png", "throwing_arrow_build.png"}, "throwing_build_arrow", - function(_, last_pos, node, _, hitter) - if not node then - return + throwing.register_arrow("arrow_build", { + itemcraft = "default:obsidian_glass", + description = "Build Arrow", + tiles = {"throwing_arrow_build.png", "throwing_arrow_build.png", "throwing_arrow_build_back.png", "throwing_arrow_build_front.png", "throwing_arrow_build_2.png", "throwing_arrow_build.png"}, + on_hit_sound = "throwing_build_arrow", + on_hit = function(_, last_pos, _, _, hitter) + if minetest.get_node(last_pos).name ~= "air" then + minetest.log("warning", "[throwing] BUG: node at last_pos was not air") + return + end + local playername = hitter:get_player_name() + if minetest.is_protected(last_pos, playername) then + minetest.record_protection_violation(last_pos, playername) + return false, "protected position" + end + return minetest.place_node(last_pos, {name="default:obsidian_glass"}) end - if minetest.get_node(last_pos).name ~= "air" then - minetest.log("warning", "[throwing] BUG: node at last_pos was not air") - return - end - local playername = hitter:get_player_name() - if minetest.is_protected(last_pos, playername) then - minetest.record_protection_violation(last_pos, playername) - return false, "protected position" - end - return minetest.place_node(last_pos, {name="default:obsidian_glass"}) - end) + }) end