minetest_x_bows/init.lua
2022-10-09 22:24:36 -04:00

441 lines
12 KiB
Lua

minetest = minetest--[[@as Minetest]]
ItemStack = ItemStack--[[@as ItemStack]]
vector = vector--[[@as Vector]]
default = default--[[@as MtgDefault]]
local mod_start_time = minetest.get_us_time()
local bow_charged_timer = 0
x_bows = {
pvp = minetest.settings:get_bool('enable_pvp') or false,
creative = minetest.settings:get_bool('creative_mode') or false,
mesecons = minetest.get_modpath('mesecons'),
hbhunger = minetest.get_modpath('hbhunger'),
playerphysics = minetest.get_modpath('playerphysics'),
player_monoids = minetest.get_modpath('player_monoids'),
registered_arrows = {},
registered_bows = {},
player_bow_sneak = {},
settings = {
x_bows_attach_arrows_to_entities = minetest.settings:get_bool('x_bows_attach_arrows_to_entities', false)
}
}
function x_bows.is_creative(name)
return x_bows.creative or minetest.check_player_privs(name, {creative = true})
end
---Reset charged bow to uncharged bow, this will return the arrow item to the inventory also
---@param player ObjectRef Player Ref
---@param includeWielded? boolean Will include reset for wielded bow also
---@return nil
local function reset_charged_bow(player, includeWielded)
local _includeWielded = includeWielded or false
local inv = player:get_inventory()
---@cast inv InvRef
if inv:contains_item('main', 'x_bows:bow_wood_charged') then
local inv_list = inv:get_list('main')
for i, st in ipairs(inv_list) do
local reset = _includeWielded or player:get_wield_index() ~= i
if not st:is_empty() and x_bows.registered_bows[st:get_name()] and reset then
local item_meta = st:get_meta()
local arrow = item_meta:get_string('arrow')
-- return arrow
if arrow ~= '' then
if inv:room_for_item('main', {name=arrow}) then
inv:add_item('main', arrow)
else
minetest.item_drop(ItemStack({name=arrow, count=1}), player, player:get_pos())
end
end
-- reset bow to uncharged bow
inv:set_stack('main', i, ItemStack({
name=x_bows.registered_bows[st:get_name()].name,
count=st:get_count(),
wear=st:get_wear()
}))
end
end
end
end
minetest.register_on_joinplayer(function(player)
reset_charged_bow(player, true)
end)
function x_bows.register_bow(name, def)
if name == nil or name == '' then
return false
end
def.name = 'x_bows:' .. name
def.name_charged = 'x_bows:' .. name .. '_charged'
def.description = def.description or name
def.uses = def.uses or 150
x_bows.registered_bows[def.name_charged] = def
-- not charged bow
minetest.register_tool(def.name, {
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: '
.. (1 / def.crit_chance) * 100 .. '%'),
inventory_image = def.inventory_image or 'x_bows_bow_wood.png',
-- on_use = function(itemstack, user, pointed_thing)
-- end,
on_place = x_bows.load,
on_secondary_use = x_bows.load,
groups = {bow = 1, flammable = 1},
-- range = 0
})
-- charged bow
minetest.register_tool(def.name_charged, {
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: '
.. (1 / def.crit_chance) * 100 .. '%'),
inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png',
on_use = x_bows.shoot,
groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1},
on_drop = function(itemstack, dropper, pos)
local item_meta = itemstack:get_meta()
local arrow = item_meta:get_string('arrow')
-- return arrow
if arrow ~= '' then
minetest.item_drop(ItemStack({name=arrow, count=1}), dropper, {x=pos.x + 0.5, y=pos.y + 0.5, z=pos.z + 0.5})
end
itemstack:set_name(def.name)
-- returns leftover itemstack
return minetest.item_drop(itemstack, dropper, pos)
end
})
-- recipes
if def.recipe then
minetest.register_craft({
output = def.name,
recipe = def.recipe
})
end
end
function x_bows.register_arrow(name, def)
if name == nil or name == '' then
return false
end
def.name = 'x_bows:' .. name
def.description = def.description or name
x_bows.registered_arrows[def.name] = def
minetest.register_craftitem('x_bows:' .. name, {
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Damage: '
.. def.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', 'Charge Time: '
.. def.tool_capabilities.full_punch_interval .. 's'),
inventory_image = def.inventory_image,
groups = {arrow = 1, flammable = 1}
})
-- recipes
if def.craft then
minetest.register_craft({
output = def.name ..' ' .. (def.craft_count or 4),
recipe = def.craft
})
end
end
function x_bows.load(itemstack, user, pointed_thing)
local time_load = minetest.get_us_time()
local inv = user:get_inventory()
local inv_list = inv:get_list('main')
local bow_name = itemstack:get_name()
local bow_def = x_bows.registered_bows[bow_name .. '_charged']
local itemstack_arrows = {}
if pointed_thing.under then
local node = minetest.get_node(pointed_thing.under)
local node_def = minetest.registered_nodes[node.name]
if node_def and node_def.on_rightclick then
return node_def.on_rightclick(pointed_thing.under, node, user, itemstack, pointed_thing)
end
end
for _, st in ipairs(inv_list) do
if not st:is_empty() and x_bows.registered_arrows[st:get_name()] then
table.insert(itemstack_arrows, st)
end
end
-- take 1st found arrow in the list
local itemstack_arrow = itemstack_arrows[1]
if itemstack_arrow and bow_def then
local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities
minetest.after(0, function(v_user, v_bow_name, v_time_load)
local wielded_item = v_user:get_wielded_item()
local wielded_item_name = wielded_item:get_name()
if wielded_item_name == v_bow_name then
local meta = wielded_item:get_meta()
meta:set_string('arrow', itemstack_arrow:get_name())
meta:set_string('time_load', tostring(v_time_load))
wielded_item:set_name(v_bow_name .. '_charged')
v_user:set_wielded_item(wielded_item)
if not x_bows.is_creative(user:get_player_name()) then
inv:remove_item('main', itemstack_arrow:get_name())
end
end
end, user, bow_name, time_load)
-- sound plays when charge time reaches full punch interval time
-- @TODO: find a way to prevent this from playing when not fully charged
minetest.after(_tool_capabilities.full_punch_interval, function(v_user, v_bow_name)
local wielded_item = v_user:get_wielded_item()
local wielded_item_name = wielded_item:get_name()
if wielded_item_name == v_bow_name .. '_charged' then
minetest.sound_play('x_bows_bow_loaded', {
to_player = user:get_player_name(),
gain = 0.6
})
end
end, user, bow_name)
minetest.sound_play('x_bows_bow_load', {
to_player = user:get_player_name(),
gain = 0.6
})
return itemstack
end
end
function x_bows.shoot(itemstack, user, pointed_thing)
local time_shoot = minetest.get_us_time();
local meta = itemstack:get_meta()
local meta_arrow = meta:get_string('arrow')
local time_load = tonumber(meta:get_string('time_load'))
local tflp = (time_shoot - time_load) / 1000000
if not x_bows.registered_arrows[meta_arrow] then
return itemstack
end
local bow_name_charged = itemstack:get_name()
local bow_name = x_bows.registered_bows[bow_name_charged].name
local uses = x_bows.registered_bows[bow_name_charged].uses
local crit_chance = x_bows.registered_bows[bow_name_charged].crit_chance
local _tool_capabilities = x_bows.registered_arrows[meta_arrow].tool_capabilities
local staticdata = {
arrow = meta_arrow,
user_name = user:get_player_name(),
is_critical_hit = false,
_tool_capabilities = _tool_capabilities,
_tflp = tflp,
}
-- crits, only on full punch interval
if crit_chance and crit_chance > 1 and tflp >= _tool_capabilities.full_punch_interval then
if math.random(1, crit_chance) == 1 then
staticdata.is_critical_hit = true
end
end
local sound_name = 'x_bows_bow_shoot'
if staticdata.is_critical_hit then
sound_name = 'x_bows_bow_shoot_crit'
end
meta:set_string('arrow', '')
itemstack:set_name(bow_name)
local pos = user:get_pos()
local dir = user:get_look_dir()
local obj = minetest.add_entity(
{
x = pos.x,
y = pos.y + 1.5,
z = pos.z
},
'x_bows:arrow_entity',
minetest.serialize(staticdata)
)
if not obj then
return itemstack
end
local strength_multiplier = tflp
if strength_multiplier > _tool_capabilities.full_punch_interval then
strength_multiplier = 1
end
local strength = 30 * strength_multiplier
obj:set_velocity(vector.multiply(dir, strength))
obj:set_acceleration({x = dir.x * -3, y = -10, z = dir.z * -3})
obj:set_yaw(minetest.dir_to_yaw(dir))
if not x_bows.is_creative(user:get_player_name()) then
itemstack:add_wear(65535 / uses)
end
minetest.sound_play(sound_name, {
gain = 0.3,
pos = user:get_pos(),
max_hear_distance = 10
})
return itemstack
end
function x_bows.particle_effect(pos, type)
if type == 'arrow' then
return minetest.add_particlespawner({
amount = 1,
time = 0.1,
minpos = pos,
maxpos = pos,
minexptime = 1,
maxexptime = 1,
minsize = 2,
maxsize = 2,
texture = 'x_bows_arrow_particle.png',
animation = {
type = 'vertical_frames',
aspect_w = 8,
aspect_h = 8,
length = 1,
},
glow = 1
})
elseif type == 'arrow_crit' then
return minetest.add_particlespawner({
amount = 3,
time = 0.1,
minpos = pos,
maxpos = pos,
minexptime = 0.5,
maxexptime = 0.5,
minsize = 2,
maxsize = 2,
texture = 'x_bows_arrow_particle.png^[colorize:#B22222:127',
animation = {
type = 'vertical_frames',
aspect_w = 8,
aspect_h = 8,
length = 1,
},
glow = 1
})
elseif type == 'bubble' then
return minetest.add_particlespawner({
amount = 1,
time = 1,
minpos = pos,
maxpos = 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 = 0.5,
maxsize = 1,
texture = 'x_bows_bubble.png'
})
elseif type == 'arrow_tipped' then
return minetest.add_particlespawner({
amount = 5,
time = 1,
minpos = vector.subtract(pos, 0.5),
maxpos = vector.add(pos, 0.5),
minexptime = 0.4,
maxexptime = 0.8,
minvel = {x=-0.4, y=0.4, z=-0.4},
maxvel = {x=0.4, y=0.6, z=0.4},
minacc = {x=0.2, y=0.4, z=0.2},
maxacc = {x=0.4, y=0.6, z=0.4},
minsize = 4,
maxsize = 6,
texture = 'x_bows_arrow_tipped_particle.png^[colorize:#008000:127',
animation = {
type = 'vertical_frames',
aspect_w = 8,
aspect_h = 8,
length = 1,
},
glow = 1
})
end
end
-- sneak, fov adjustments when bow is charged
minetest.register_globalstep(function(dtime)
bow_charged_timer = bow_charged_timer + dtime
if bow_charged_timer > 0.5 then
for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
local stack = player:get_wielded_item()
local item = stack:get_name()
if not item then
return
end
if not x_bows.player_bow_sneak[name] then
x_bows.player_bow_sneak[name] = {}
end
if item == 'x_bows:bow_wood_charged' and not x_bows.player_bow_sneak[name].sneak then
if x_bows.playerphysics then
playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25)
elseif x_bows.player_monoids then
player_monoids.speed:add_change(player, 0.25, 'x_bows:bow_wood_charged')
end
x_bows.player_bow_sneak[name].sneak = true
player:set_fov(0.9, true, 0.4)
elseif item ~= 'x_bows:bow_wood_charged' and x_bows.player_bow_sneak[name].sneak then
if x_bows.playerphysics then
playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged')
elseif x_bows.player_monoids then
player_monoids.speed:del_change(player, 'x_bows:bow_wood_charged')
end
x_bows.player_bow_sneak[name].sneak = false
player:set_fov(0, true, 0.4)
end
reset_charged_bow(player)
end
bow_charged_timer = 0
end
end)
local path = minetest.get_modpath('x_bows')
dofile(path .. '/nodes.lua')
dofile(path .. '/arrow.lua')
dofile(path .. '/items.lua')
local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000
print('[Mod] x_bows loaded.. ['.. mod_end_time ..'s]')