commit 28a3bb066102e57edf06e706afd5ebab9c9c3c89 Author: Juraj Vajda Date: Mon Mar 1 13:44:27 2021 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..ac018e0 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,20 @@ +unused_args = false +allow_defined_top = true + +globals = { + "minetest", + "tnt", + "explosions" +} + +read_globals = { + string = {fields = {"split"}}, + table = {fields = {"copy", "getn"}}, + + -- Builtin + "vector", "ItemStack", + "dump", "DIR_DELIM", "VoxelArea", "Settings", + + -- MTG + "default", "sfinv", "creative", +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..511c329 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,24 @@ +# LICENSE + +- Code: GPLv3.0 +- Textures: CC BY-SA 4.0 +- Sounds: They have different licenses, see below. + +## Sounds + +### Draw Bow +- farbows_draw_bow.ogg +- Author: Mike Koenig +- http://soundbible.com/2107-Drawing-Bow-Back.html +- License: Attribution 3.0 + +### Fire Arrow +- farbows_fire_arrow.ogg +- Author: Mike Koenig +- http://soundbible.com/2108-Shoot-Arrow.html +- License: Attribution 3.0 + +### Hit Arrow +- farbows_hit_arrow.ogg +- Author: runs +- GPLv3.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3b9098 --- /dev/null +++ b/README.md @@ -0,0 +1,198 @@ +# RCBOWS + +## function rcbows.register_arrow(name, def) +Example: +``` +rcbows.register_arrow("farbows:e_arrow", { + damage = 5, + inventory_arrow = { + name = "farbows:inv_arrow", + description = S("Arrow"), + inventory_image = "farbows_arrow.png", + stack_max = 64, --optional, 99 by default + } + sounds = { + max_hear_distance = 10, + gain = 0.4, + }, +}) +``` +## function rcbows.register_bow(name, def) +Example: +``` +rcbows.register_bow("farbows:bow_wood", { + description = S("Wooden Bow"), + image = "farbows_bow_wood.png", + strength = 30, + uses = 150, + charge_time = 0.5, + recipe = { + {"", "group:wood", "farming:string"}, + {"group:wood", "", "farming:string"}, + {"", "group:wood", "farming:string"}, + }, + base_texture = "farbows_base_bow_wood.png", + overlay_empty = "farbows_overlay_empty.png", + overlay_charged = "farbows_overlay_charged.png", + arrows = "farbows:e_arrow", + sounds = { + max_hear_distance = 10, + gain = 0.4, + } +}) +``` + +### Arrows + +You can define "arrows" as a single arrow (string) or a table of arrows. + +In this case the order matters. The first ones have preference over the last ones when charging the bow. + +I.e: +``` +arrows = {"farbows:e_arrow", ""farbows:ice_arrow""}, +``` + +### Viewfinder + +You can define a viewfinder for a bow. This produces a zoom effect. + +``` + viewfinder = { + zoom = 15, --level of zoom; by default 15. + texture = "" --optional + } +``` + +- When the bow charged, toogle the viewfinder with the secondary use (right-click). +- You can define an optional texture to being showed. If you define texture as empty (""), you get the default rcbows viewfinder texture. + +## Audio + +1. If you define ``sounds={}``, you get the default sounds. + +For no sound at all do not declare 'sounds'. + +Also you can set the sound parameters 'max_hear_distance' and 'gain'. + +In example: +``` +sounds = { + max_hear_distance = 10, + gain = 0.4, +} +``` + +2. You also can define your own soundfiles. + +You can set "soundfile_draw_bow" and/or "soundfile_fire_arrow" for bows, and "soundfile_hit_arrow" for arrows. + +In example for a Bow: +``` +sounds = { + soundfile_draw_bow = "my_draw_bow" + soundfile_fire_arrow = "my_fire_arrow" + max_hear_distance = 5, + --set the gain by default (0.5) +} +``` + +In example for a Arrow: +``` +sounds = { + soundfile_hit_arrow = "my_hit_arrow" + max_hear_distance = 5, + --set the gain by default (0.5) +} +``` + +## Drop + +By default the arrow drops the inventory_arrow when reachs a solid node. + +If you want to define another item to drop, define it with 'drop': +``` +rcbows.register_arrow("farbows:e_arrow", { + damage = 5, + inventory_arrow = { + name = "farbows:inv_arrow", + description = S("Arrow"), + inventory_image = "farbows_arrow.png", + } + drop = "farbows_drop_arrow" +}) +``` + +If you want not any drop at all, add: +``` +drop = "", +``` + +## Arrow Effects +You can define some arrow effects +### replace_node +Replace the hit node for this one. +### trail_particle +Particle texture to create an arrow trail. + +It can be a string with "texture" only, or a table for animated textures: {texture = "texture", animation = "animation"}. +### explosion +It requires "tnt" or "explosion" mods as an optional dependency. + +It is a table in where to define: +- mod = "tnt" or "explosions", +- radius +- damage = It is "damage_radius" for the "tnt" mod or "strength" for "explosions" + + +In example: +``` +rcbows.register_arrow("farbows:fire_arrow", { + damage = 7, + inventory_arrow = { + name = "farbows:inv_fire_arrow", + description = S("Fire Arrow"), + inventory_image = "farbows_arrow_fire.png", + }, + drop = "farbows:inv_arrow", + effects = { + replace_node = "fire:basic_flame", + trail_particle = "farbows_particle_fire.png", + explosion = { + mod = "tnt", + radius= 10, + damage = 1, + } + } +}) +``` +### water +An effect that extinguishes the flames. + +It requires "fire" mod as an optional dependency. + +It is a table in where to define: +- flame_node = The name of the flame node to extinguish +- radius +- particles = A water particles effect [optional] + +``` +rcbows.register_arrow("farbows:water_arrow", { + projectile_texture = "farbows_water_arrow", + damage = 2, + inventory_arrow = { + name = "farbows:inv_water_arrow", + description = S("Water Arrow"), + inventory_image = "farbows_arrow_water.png", + }, + drop = "bucket:bucket_empty", + effects = { + trail_particle = "default_water.png", + water = { + radius = 5, + flame_node = "fire:basic_flame", + particles = true, + }, + } +}) +``` diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..6226477 --- /dev/null +++ b/init.lua @@ -0,0 +1,449 @@ +rcbows = {} + +local S = minetest.get_translator(minetest.get_current_modname()) + +--CONSTANTS +local DEFAULT_MAX_HEAR_DISTANCE = 10 +local DEFAULT_GAIN = 0.5 + +function rcbows.spawn_arrow(user, strength, itemstack) + local pos = user:get_pos() + pos.y = pos.y + 1.5 -- camera offset + local dir = user:get_look_dir() + local yaw = user:get_look_horizontal() + local meta = itemstack:get_meta() + local arrow = meta:get_string("rcbows:charged_arrow") + local obj = nil + if pos and arrow then + obj = minetest.add_entity(pos, arrow) + end + if not obj then + return + end + local lua_ent = obj:get_luaentity() + lua_ent.shooter_name = user:get_player_name() + obj:set_yaw(yaw - 0.5 * math.pi) + local velocity = vector.multiply(dir, strength) + obj:set_velocity(velocity) + return true +end + +function rcbows.launch_arrow(user, name, def, itemstack) + if not rcbows.spawn_arrow(user, def.strength, itemstack) then --throw arrow (spawn arrow entity) + return -- something failed + end + if def.sounds then + local user_pos = user:get_pos() + if not def.sounds.soundfile_fire_arrow then + def.sounds.soundfile_fire_arrow = "rcbows_fire_arrow" + end + rcbows.make_sound("pos", user_pos, def.sounds.soundfile_fire_arrow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE) + end + itemstack:set_name(name) + itemstack:set_wear(itemstack:get_wear() + 0x10000 / def.uses) + if def.viewfinder then --reset the viewfinder-zoom + if not(user:get_fov() == 0) then + user:set_fov(0) + remove_viewfinder(user, itemstack:get_meta():get_int( "rcbows:viewfinder_id")) + end + end + return itemstack +end + +local function show_viewfinder(player, texture) + local hud_id = player:hud_add({ + hud_elem_type = "image", + text = texture, + position = {x=0, y=0}, + scale = {x=-100, y=-100}, + alignment = {x=1, y=1}, + offset = {x=0, y=0} + }) + return hud_id +end + +function remove_viewfinder(player, hud_id) + if hud_id then + player:hud_remove(hud_id) + end +end + +function rcbows.register_bow(name, def) + assert(type(def.description) == "string") + assert(type(def.image) == "string") + assert(type(def.strength) == "number") + assert(def.uses > 0) + + local function reload_bow(itemstack, user, pointed_thing) + local inv = user:get_inventory() + local arrow, inventory_arrows, inventory_arrow, inv_arrow_name + local inv_list = inv:get_list("main") + if type(def.arrows) == 'table' then --more than one arrow? + for i = 1, #def.arrows do + arrow = def.arrows[i] + inv_arrow_name = minetest.registered_entities[arrow].inventory_arrow_name + if inv:contains_item("main", inv_arrow_name) then + if not inventory_arrows then + inventory_arrows = {} + end + inventory_arrows[#inventory_arrows+1] = {arrow = arrow, inv_arrow_name = inv_arrow_name} + end + end + else + arrow = def.arrows + inventory_arrow = minetest.registered_entities[def.arrows].inventory_arrow_name + end + if not inventory_arrow and not inventory_arrows then + return + end + if inventory_arrows then --more than one arrow? + for i = 1, #inv_list do + if inventory_arrow then + break + end + for j = 1, #inventory_arrows do + local inv_arrow = inventory_arrows[j] + if inv_list[i]:get_name() == inv_arrow.inv_arrow_name then + arrow = inv_arrow.arrow + inventory_arrow = inv_arrow.inv_arrow_name + break + end + end + end + if not inventory_arrow then + return + end + end + if not inv:remove_item("main", inventory_arrow):is_empty() then + minetest.after(def.charge_time or 0, function(v_user, v_name) + local wielded_item = v_user:get_wielded_item() + local wielded_item_name = wielded_item:get_name() + if wielded_item_name == v_name then + local meta = wielded_item:get_meta() + meta:set_string("rcbows:charged_arrow", arrow) --save the arrow in the meta + wielded_item:set_name(v_name .. "_charged") + v_user:set_wielded_item(wielded_item) + end + end, user, name) + if def.sounds then + local user_pos = user:get_pos() + if not def.sounds.soundfile_draw_bow then + def.sounds.soundfile_draw_bow = "rcbows_draw_bow" + end + rcbows.make_sound("pos", user_pos, def.sounds.soundfile_draw_bow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE) + end + return itemstack + end + end + + minetest.register_tool(name, { + description = def.description .. " ".. S("(place to reload)"), + inventory_image = def.image .. "^" .. def.overlay_empty, + + on_use = function() end, + on_place = reload_bow, + on_secondary_use = reload_bow, + }) + + if def.recipe then + minetest.register_craft({ + output = name, + recipe = def.recipe + }) + end + + local charged_name = name .. "_charged" + + minetest.register_tool(charged_name, { + description = def.description .. " " .. S("(use to fire)"), + inventory_image = def.base_texture .. "^" ..def.overlay_charged, + groups = {not_in_creative_inventory=1}, + + on_use = function(itemstack, user, pointed_thing) + return rcbows.launch_arrow(user, name, def, itemstack) + end, + + on_secondary_use = function(itemstack, user, pointed_thing) --viewfinder + if not def.viewfinder then + return + end + if user:get_fov() == 0 then + user:set_fov(def.viewfinder.zoom or 15) + if def.viewfinder.texture then + local viewfinder_texture = def.viewfinder.texture + if viewfinder_texture == "" then + viewfinder_texture = "rcbows_viewfinder.png" + end + itemstack:get_meta():set_int("rcbows:viewfinder_id", show_viewfinder(user, viewfinder_texture)) + end + else + user:set_fov(0) + if def.viewfinder.texture then + remove_viewfinder(user, itemstack:get_meta():get_int( "rcbows:viewfinder_id")) + end + end + return itemstack + end + }) +end + +function rcbows.register_arrow(name, def) + minetest.register_entity(name, { + hp_max = 4, -- possible to catch the arrow (pro skills) + physical = false, -- use Raycast + collisionbox = {-0.1, -0.1, -0.1, 0.1, 0.1, 0.1}, + visual = "wielditem", + textures = {def.inventory_arrow.name}, + visual_size = {x = 0.2, y = 0.15}, + old_pos = nil, + velocity = nil, + liquidflag = nil, + shooter_name = "", + waiting_for_removal = false, + inventory_arrow_name = def.inventory_arrow.name, + groups = {arrow = 1}, + + on_activate = function(self) + self.object:set_acceleration({x = 0, y = -9.81, z = 0}) + end, + + on_step = function(self, dtime) + if self.waiting_for_removal then + self.object:remove() + return + end + local pos = self.object:get_pos() + self.old_pos = self.old_pos or pos + local velocity = self.object:get_velocity() + if def.sounds and not(def.sounds.soundfile_hit_arrow) then + def.sounds.soundfile_hit_arrow = "rcbows_hit_arrow" + end + local cast = minetest.raycast(self.old_pos, pos, true, true) + local thing = cast:next() + while thing do + if thing.type == "object" and thing.ref ~= self.object then + if not thing.ref:is_player() or thing.ref:get_player_name() ~= self.shooter_name then + thing.ref:punch(self.object, 1.0, { + full_punch_interval = 0.5, + damage_groups = {fleshy = def.damage or 1} + }) + self.waiting_for_removal = true + self.object:remove() + if def.sounds then + local thing_pos = thing.ref:get_pos() + if thing_pos then + rcbows.make_sound("pos", thing_pos, def.sounds.soundfile_hit_arrow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE) + end + end + + -- no effects or not owner, nothing to do. + -- some effects should also happen if hitting an other object. like tnt, water etc. + if not def.effects or minetest.is_protected(pos, self.shooter_name) then + return + end + + rcbows.boom_effect(def, pos) -- BOOM + rcbows.water_effect(def, pos) -- water - extinguish fires + + return + end + elseif thing.type == "node" then + local node_name = minetest.get_node(thing.under).name + local drawtype = minetest.registered_nodes[node_name]["drawtype"] + if drawtype == 'liquid' then + if not self.liquidflag then + self.velocity = velocity + self.liquidflag = true + local liquidviscosity = minetest.registered_nodes[node_name]["liquid_viscosity"] + local drag = 1/(liquidviscosity*6) + self.object:set_velocity(vector.multiply(velocity, drag)) + self.object:set_acceleration({x = 0, y = -1.0, z = 0}) + rcbows.splash(self.old_pos, "rcbows_bubble.png") + end + elseif self.liquidflag then + self.liquidflag = false + if self.velocity then + self.object:set_velocity(self.velocity) + end + self.object:set_acceleration({x = 0, y = -9.81, z = 0}) + end + if minetest.registered_items[node_name].walkable then + if not(def.drop) then + minetest.item_drop(ItemStack(def.inventory_arrow), nil, vector.round(self.old_pos)) + else + if not(def.drop == "") then + minetest.item_drop(ItemStack(def.drop), nil, vector.round(self.old_pos)) + end + end + self.waiting_for_removal = true + self.object:remove() + + if def.sounds then + if pos then + rcbows.make_sound("pos", pos, def.sounds.soundfile_hit_arrow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE) + end + end + + -- no effects or not owner, nothing to do. + if not def.effects or minetest.is_protected(pos, self.shooter_name) then + return + end + + --replace node + if def.effects.replace_node then + minetest.set_node(pos, {name = def.effects.replace_node}) + end + + rcbows.boom_effect(def, pos) -- BOOM + rcbows.water_effect(def, pos) -- water - extinguish fires + + return + end + end + thing = cast:next() + end + if def.effects and def.effects.trail_particle then + rcbows.trail(self.old_pos, pos, def.effects.trail_particle) + end + self.old_pos = pos + end, + }) + minetest.register_craftitem(def.inventory_arrow.name, { + description = def.inventory_arrow.description, + inventory_image = def.inventory_arrow.inventory_image, + stack_max = def.stack_max or 99, + }) +end + +--SOUND SYSTEM + +function rcbows.make_sound(dest_type, dest, soundfile, gain, max_hear_distance) + if dest_type == "object" then + minetest.sound_play(soundfile, {object = dest, gain = gain or DEFAULT_GAIN, max_hear_distance = max_hear_distance or DEFAULT_MAX_HEAR_DISTANCE,}) + elseif dest_type == "player" then + local player_name = dest:get_player_name() + minetest.sound_play(soundfile, {to_player = player_name, gain = gain or DEFAULT_GAIN, max_hear_distance = max_hear_distance or DEFAULT_MAX_HEAR_DISTANCE,}) + elseif dest_type == "pos" then + minetest.sound_play(soundfile, {pos = dest, gain = gain or DEFAULT_GAIN, max_hear_distance = max_hear_distance or DEFAULT_MAX_HEAR_DISTANCE,}) + end +end + +--ARROW EFFECTS + +function rcbows.boom_effect(def, pos) + if def.effects.explosion and def.effects.explosion.mod then + local mod_name = def.effects.explosion.mod + if minetest.get_modpath(mod_name) ~= nil then + if mod_name == "tnt" then + tnt.boom(pos, {radius = def.effects.explosion.radius, damage_radius = def.effects.explosion.damage}) + elseif mod_name == "explosions" then + explosions.explode(pos, {radius = def.effects.explosion.radius, strength = def.effects.explosion.damage}) + end + end + end +end + +function rcbows.water_effect(def, pos) + if def.effects.water then + if def.effects.water.particles then + rcbows.splash(pos, "rcbows_water.png") + end + local radius = def.effects.water.radius or 5 + local flames = minetest.find_nodes_in_area({x=pos.x -radius, y=pos.y -radius, z=pos.z -radius}, {x=pos.x+radius, y=pos.y+radius, z=pos.z+radius}, {def.effects.water.flame_node}) + if flames and #flames > 0 then + for i=1, #flames do + minetest.set_node(flames[i], {name="air"}) + if def.effects.water.particles then + rcbows.splash(flames[i], "rcbows_water.png") + end + end + end + end +end + +--PARTICLES EFFECTS + +function rcbows.trail(old_pos, pos, trail_particle) + local texture, animation + if type(trail_particle) == 'table' then + texture = trail_particle.texture + animation = trail_particle.animation + else + texture = trail_particle + animation = "" + end + minetest.add_particlespawner({ + texture = texture, + amount = 20, + time = 0.2, + minpos = old_pos, + maxpos = pos, + --minvel = {x=1, y=0, z=1}, + --maxvel = {x=1, y=0, z=1}, + --minacc = {x=1, y=0, z=1}, + --maxacc = {x=1, y=0, z=1}, + minexptime = 0.2, + maxexptime = 0.5, + minsize = 0.5, + maxsize = 1.5, + collisiondetection = false, + vertical = false, + glow = 14, + animation = animation, + }) +end + +function rcbows.splash(old_pos, splash_particle) + minetest.add_particlespawner({ + amount = 5, + time = 1, + minpos = old_pos, + maxpos = old_pos, + minvel = {x=1, y=1, z=0}, + maxvel = {x=1, y=1, z=0}, + minacc = {x=1, y=1, z=1}, + maxacc = {x=1, y=1, z=1}, + minexptime = 0.2, + maxexptime = 0.5, + minsize = 1, + maxsize = 1, + collisiondetection = false, + vertical = false, + texture = splash_particle, + playername = "singleplayer" + }) +end + +rcbows.register_bow("rcbows:bow_wood", { + description = S("Wooden Bow"), + image = "rcbows_pulling_0.png", + strength = 30, + uses = 150, + charge_time = 0.5, + recipe = { + {"", "group:wood", "farming:string"}, + {"group:wood", "", "farming:string"}, + {"", "group:wood", "farming:string"}, + }, + base_texture = "rcbows_pulling_0.png", + overlay_empty = "rcbows_standby.png", + overlay_charged = "rcbows_pulling_2.png", + arrows = "rcbows:arrow", + sounds = { + max_hear_distance = 10, + gain = 0.4, + } +}) + +rcbows.register_arrow("rcbows:arrow", { + damage = 5, + inventory_arrow = { + name = "rcbows:inv_arrow", + description = S("Arrow"), + inventory_image = "rcbows_arrow.png" + }, + sounds = { + max_hear_distance = 10, + gain = 0.4, + }, +}) \ No newline at end of file diff --git a/locale/rcbows.es.tr b/locale/rcbows.es.tr new file mode 100644 index 0000000..202eb20 --- /dev/null +++ b/locale/rcbows.es.tr @@ -0,0 +1,3 @@ +# textdomain: rcbows +(place to reload)=(dcha. ratón para recargar) +(use to fire)=(izda. ratón para usar) diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..deb8356 --- /dev/null +++ b/mod.conf @@ -0,0 +1,3 @@ +name = rcbows +depends = +optional_depends = tnt, explosions diff --git a/sounds/rcbows_draw_bow.ogg b/sounds/rcbows_draw_bow.ogg new file mode 100644 index 0000000..6f92da1 Binary files /dev/null and b/sounds/rcbows_draw_bow.ogg differ diff --git a/sounds/rcbows_fire_arrow.ogg b/sounds/rcbows_fire_arrow.ogg new file mode 100644 index 0000000..cf6c8c6 Binary files /dev/null and b/sounds/rcbows_fire_arrow.ogg differ diff --git a/sounds/rcbows_hit_arrow.ogg b/sounds/rcbows_hit_arrow.ogg new file mode 100644 index 0000000..e966216 Binary files /dev/null and b/sounds/rcbows_hit_arrow.ogg differ diff --git a/textures/rcbows_arrow.png b/textures/rcbows_arrow.png new file mode 100644 index 0000000..9d765a7 Binary files /dev/null and b/textures/rcbows_arrow.png differ diff --git a/textures/rcbows_bubble.png b/textures/rcbows_bubble.png new file mode 100755 index 0000000..b153ab1 Binary files /dev/null and b/textures/rcbows_bubble.png differ diff --git a/textures/rcbows_pulling_0.png b/textures/rcbows_pulling_0.png new file mode 100644 index 0000000..f65e706 Binary files /dev/null and b/textures/rcbows_pulling_0.png differ diff --git a/textures/rcbows_pulling_1.png b/textures/rcbows_pulling_1.png new file mode 100644 index 0000000..01a3dd8 Binary files /dev/null and b/textures/rcbows_pulling_1.png differ diff --git a/textures/rcbows_pulling_2.png b/textures/rcbows_pulling_2.png new file mode 100644 index 0000000..db24421 Binary files /dev/null and b/textures/rcbows_pulling_2.png differ diff --git a/textures/rcbows_standby.png b/textures/rcbows_standby.png new file mode 100644 index 0000000..e372294 Binary files /dev/null and b/textures/rcbows_standby.png differ diff --git a/textures/rcbows_viewfinder.png b/textures/rcbows_viewfinder.png new file mode 100644 index 0000000..b694cd9 Binary files /dev/null and b/textures/rcbows_viewfinder.png differ diff --git a/textures/rcbows_water.png b/textures/rcbows_water.png new file mode 100755 index 0000000..cd8cbc6 Binary files /dev/null and b/textures/rcbows_water.png differ