minetest_x_bows/init.lua
2021-03-01 13:44:27 -05:00

449 lines
13 KiB
Lua

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,
},
})