Merge pull request 'Add crossbows' (#10) from MineClone2/MineClone2:master into master
Reviewed-on: https://git.minetest.land/NO11/MineClone2/pulls/10
@ -43,7 +43,7 @@ S("An arrow fired from a bow has a regular damage of 1-9. At full charge, there'
|
||||
S("Arrows might get stuck on solid blocks and can be retrieved again. They are also capable of pushing wooden buttons."),
|
||||
_doc_items_usagehelp = S("To use arrows as ammunition for a bow, just put them anywhere in your inventory, they will be used up automatically. To use arrows as ammunition for a dispenser, place them in the dispenser's inventory. To retrieve an arrow that sticks in a block, simply walk close to it."),
|
||||
inventory_image = "mcl_bows_arrow_inv.png",
|
||||
groups = { ammo=1, ammo_bow=1, ammo_bow_regular=1 },
|
||||
groups = { ammo=1, ammo_bow=1, ammo_bow_regular=1, ammo_crossbow=1 },
|
||||
_on_dispense = function(itemstack, dispenserpos, droppos, dropnode, dropdir)
|
||||
-- Shoot arrow
|
||||
local shootpos = vector.add(dispenserpos, vector.multiply(dropdir, 0.51))
|
||||
@ -324,7 +324,9 @@ function ARROW_ENTITY.on_step(self, dtime)
|
||||
end
|
||||
if not obj:is_player() then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
if self._piercing == 0 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
454
mods/ITEMS/mcl_bows/crossbow.lua
Normal file
@ -0,0 +1,454 @@
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
mcl_bows_s = {}
|
||||
|
||||
-- local arrows = {
|
||||
-- ["mcl_bows:arrow"] = "mcl_bows:arrow_entity",
|
||||
-- }
|
||||
|
||||
local GRAVITY = 9.81
|
||||
local BOW_DURABILITY = 385
|
||||
|
||||
-- Charging time in microseconds
|
||||
local _BOW_CHARGE_TIME_HALF = 350000 -- bow level 1
|
||||
local _BOW_CHARGE_TIME_FULL = 900000 -- bow level 2 (full charge)
|
||||
|
||||
local BOW_CHARGE_TIME_HALF = 350000 -- bow level 1
|
||||
local BOW_CHARGE_TIME_FULL = 900000 -- bow level 2 (full charge)
|
||||
|
||||
-- Factor to multiply with player speed while player uses bow
|
||||
-- This emulates the sneak speed.
|
||||
local PLAYER_USE_CROSSBOW_SPEED = tonumber(minetest.settings:get("movement_speed_crouch")) / tonumber(minetest.settings:get("movement_speed_walk"))
|
||||
|
||||
-- TODO: Use Minecraft speed (ca. 53 m/s)
|
||||
-- Currently nerfed because at full speed the arrow would easily get out of the range of the loaded map.
|
||||
local BOW_MAX_SPEED = 68
|
||||
|
||||
local function play_load_sound(id, pos)
|
||||
minetest.sound_play("mcl_bows_crossbow_drawback_"..id, {pos=pos, max_hear_distance=12}, true)
|
||||
end
|
||||
|
||||
--[[ Store the charging state of each player.
|
||||
keys: player name
|
||||
value:
|
||||
nil = not charging or player not existing
|
||||
number: currently charging, the number is the time from minetest.get_us_time
|
||||
in which the charging has started
|
||||
]]
|
||||
local bow_load = {}
|
||||
|
||||
-- Another player table, this one stores the wield index of the bow being charged
|
||||
local bow_index = {}
|
||||
|
||||
function mcl_bows_s.shoot_arrow_crossbow(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, crossbow_stack, collectable)
|
||||
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity")
|
||||
if power == nil then
|
||||
power = BOW_MAX_SPEED --19
|
||||
end
|
||||
if damage == nil then
|
||||
damage = 3
|
||||
end
|
||||
local knockback
|
||||
if crossbow_stack then
|
||||
local enchantments = mcl_enchanting.get_enchantments(crossbow_stack)
|
||||
if enchantments.piercing then
|
||||
obj:get_luaentity()._piercing = 1 * enchantments.piercing
|
||||
else
|
||||
obj:get_luaentity()._piercing = 0
|
||||
end
|
||||
end
|
||||
obj:set_velocity({x=dir.x*power, y=dir.y*power, z=dir.z*power})
|
||||
obj:set_acceleration({x=0, y=-GRAVITY, z=0})
|
||||
obj:set_yaw(yaw-math.pi/2)
|
||||
local le = obj:get_luaentity()
|
||||
le._shooter = shooter
|
||||
le._source_object = shooter
|
||||
le._damage = damage
|
||||
le._is_critical = is_critical
|
||||
le._startpos = pos
|
||||
le._knockback = knockback
|
||||
le._collectable = collectable
|
||||
minetest.sound_play("mcl_bows_crossbow_shoot", {pos=pos, max_hear_distance=16}, true)
|
||||
if shooter and shooter:is_player() then
|
||||
if obj:get_luaentity().player == "" then
|
||||
obj:get_luaentity().player = shooter
|
||||
end
|
||||
obj:get_luaentity().node = shooter:get_inventory():get_stack("main", 1):get_name()
|
||||
end
|
||||
return obj
|
||||
end
|
||||
|
||||
local function get_arrow(player)
|
||||
local inv = player:get_inventory()
|
||||
local arrow_stack, arrow_stack_id
|
||||
for i=1, inv:get_size("main") do
|
||||
local it = inv:get_stack("main", i)
|
||||
if not it:is_empty() and minetest.get_item_group(it:get_name(), "ammo_crossbow") ~= 0 then
|
||||
arrow_stack = it
|
||||
arrow_stack_id = i
|
||||
break
|
||||
end
|
||||
end
|
||||
return arrow_stack, arrow_stack_id
|
||||
end
|
||||
|
||||
local function player_shoot_arrow(itemstack, player, power, damage, is_critical)
|
||||
local has_multishot_enchantment = mcl_enchanting.has_enchantment(player:get_wielded_item(), "multishot")
|
||||
local arrow_itemstring = wielditem:get_meta():get("arrow")
|
||||
|
||||
if not arrow_itemstring then
|
||||
return false
|
||||
end
|
||||
|
||||
local playerpos = player:get_pos()
|
||||
local dir = player:get_look_dir()
|
||||
local yaw = player:get_look_horizontal()
|
||||
|
||||
if has_multishot_enchantment then
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, {x=dir.x, y=dir.y, z=dir.z + .2}, yaw, player, power, damage, is_critical, player:get_wielded_item(), false)
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, {x=dir.x, y=dir.y, z=dir.z - .2}, yaw, player, power, damage, is_critical, player:get_wielded_item(), false)
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, dir, yaw, player, power, damage, is_critical, player:get_wielded_item(), true)
|
||||
else
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, dir, yaw, player, power, damage, is_critical, player:get_wielded_item(), true)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Bow item, uncharged state
|
||||
minetest.register_tool("mcl_bows:crossbow", {
|
||||
description = S("Corssbow"),
|
||||
_tt_help = S("Launches arrows"),
|
||||
_doc_items_longdesc = S("Bows are ranged weapons to shoot arrows at your foes.").."\n"..
|
||||
S("The speed and damage of the arrow increases the longer you charge. The regular damage of the arrow is between 1 and 9. At full charge, there's also a 20% of a critical hit, dealing 10 damage instead."),
|
||||
_doc_items_usagehelp = S("To use the bow, you first need to have at least one arrow anywhere in your inventory (unless in Creative Mode). Hold down the right mouse button to charge, release to shoot."),
|
||||
_doc_items_durability = BOW_DURABILITY,
|
||||
inventory_image = "mcl_bows_crossbow.png",
|
||||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 4,
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_place = function(itemstack, player, pointed_thing)
|
||||
if pointed_thing and pointed_thing.type == "node" then
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
if player and not player:get_player_control().sneak then
|
||||
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
||||
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, player, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
on_secondary_use = function(itemstack)
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1},
|
||||
_mcl_uses = 326,
|
||||
})
|
||||
|
||||
minetest.register_tool("mcl_bows:crossbow_loaded", {
|
||||
description = S("Corssbow"),
|
||||
_tt_help = S("Launches arrows"),
|
||||
_doc_items_longdesc = S("Corssbow are ranged weapons to shoot arrows at your foes.").."\n"..
|
||||
S("The speed and damage of the arrow increases the longer you charge. The regular damage of the arrow is between 1 and 9. At full charge, there's also a 20% of a critical hit, dealing 10 damage instead."),
|
||||
_doc_items_usagehelp = S("To use the corssbow, you first need to have at least one arrow anywhere in your inventory (unless in Creative Mode). Hold down the right mouse button to charge, release to load an arrow into the chamber, then to shoot press left mouse."),
|
||||
_doc_items_durability = BOW_DURABILITY,
|
||||
inventory_image = "mcl_bows_crossbow_3.png",
|
||||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 4,
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_place = function(itemstack, player, pointed_thing)
|
||||
if pointed_thing and pointed_thing.type == "node" then
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
if player and not player:get_player_control().sneak then
|
||||
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
||||
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, player, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
on_secondary_use = function(itemstack)
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1},
|
||||
_mcl_uses = 326,
|
||||
})
|
||||
|
||||
-- Iterates through player inventory and resets all the bows in "charging" state back to their original stage
|
||||
local function reset_bows(player)
|
||||
local inv = player:get_inventory()
|
||||
local list = inv:get_list("main")
|
||||
for place, stack in pairs(list) do
|
||||
if stack:get_name() == "mcl_bows:crossbow" or stack:get_name() == "mcl_bows:crossbow_enchanted" then
|
||||
stack:get_meta():set_string("active", "")
|
||||
elseif stack:get_name()=="mcl_bows:crossbow_0" or stack:get_name()=="mcl_bows:crossbow_1" or stack:get_name()=="mcl_bows:crossbow_2" then
|
||||
stack:set_name("mcl_bows:crossbow")
|
||||
stack:get_meta():set_string("active", "")
|
||||
list[place] = stack
|
||||
elseif stack:get_name()=="mcl_bows:crossbow_0_enchanted" or stack:get_name()=="mcl_bows:crossbow_1_enchanted" or stack:get_name()=="mcl_bows:crossbow_2_enchanted" then
|
||||
stack:set_name("mcl_bows:crossbow_enchanted")
|
||||
stack:get_meta():set_string("active", "")
|
||||
list[place] = stack
|
||||
end
|
||||
end
|
||||
inv:set_list("main", list)
|
||||
end
|
||||
|
||||
-- Resets the bow charging state and player speed. To be used when the player is no longer charging the bow
|
||||
local function reset_bow_state(player, also_reset_bows)
|
||||
bow_load[player:get_player_name()] = nil
|
||||
bow_index[player:get_player_name()] = nil
|
||||
if minetest.get_modpath("playerphysics") then
|
||||
playerphysics.remove_physics_factor(player, "speed", "mcl_bows:use_crossbow")
|
||||
end
|
||||
if also_reset_bows then
|
||||
reset_bows(player)
|
||||
end
|
||||
end
|
||||
|
||||
-- Bow in charging state
|
||||
for level=0, 2 do
|
||||
minetest.register_tool("mcl_bows:crossbow_"..level, {
|
||||
description = S("Crossbow"),
|
||||
_doc_items_create_entry = false,
|
||||
inventory_image = "mcl_bows_crossbow_"..level..".png",
|
||||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 0, -- Pointing range to 0 to prevent punching with bow :D
|
||||
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, enchantability=1},
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_drop = function(itemstack, dropper, pos)
|
||||
reset_bow_state(dropper)
|
||||
itemstack:get_meta():set_string("active", "")
|
||||
if mcl_enchanting.is_enchanted(itemstack:get_name()) then
|
||||
itemstack:set_name("mcl_bows:crossbow_enchanted")
|
||||
else
|
||||
itemstack:set_name("mcl_bows:crossbow")
|
||||
end
|
||||
minetest.item_drop(itemstack, dropper, pos)
|
||||
itemstack:take_item()
|
||||
return itemstack
|
||||
end,
|
||||
-- Prevent accidental interaction with itemframes and other nodes
|
||||
on_place = function(itemstack)
|
||||
return itemstack
|
||||
end,
|
||||
_mcl_uses = 385,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
controls.register_on_release(function(player, key, time)
|
||||
if key~="RMB" then return end
|
||||
--local inv = minetest.get_inventory({type="player", name=player:get_player_name()})
|
||||
local wielditem = player:get_wielded_item()
|
||||
if wielditem:get_name()=="mcl_bows:crossbow_2" and get_arrow(player) or wielditem:get_name()=="mcl_bows:crossbow_2" and minetest.is_creative_enabled(player:get_player_name()) or wielditem:get_name()=="mcl_bows:crossbow_2_enchanted" and get_arrow(player) or wielditem:get_name()=="mcl_bows:crossbow_2_enchanted" and minetest.is_creative_enabled(player:get_player_name()) then
|
||||
local arrow_stack, arrow_stack_id = get_arrow(player)
|
||||
local arrow_itemstring
|
||||
|
||||
if minetest.is_creative_enabled(player:get_player_name()) then
|
||||
if arrow_stack then
|
||||
arrow_itemstring = arrow_stack:get_name()
|
||||
else
|
||||
arrow_itemstring = "mcl_bows:arrow"
|
||||
end
|
||||
else
|
||||
arrow_itemstring = arrow_stack:get_name()
|
||||
arrow_stack:take_item()
|
||||
player:get_inventory():set_stack("main", arrow_stack_id, arrow_stack)
|
||||
end
|
||||
|
||||
wielditem:get_meta():set_string("arrow", arrow_itemstring)
|
||||
|
||||
if wielditem:get_name()=="mcl_bows:crossbow_2" then
|
||||
wielditem:set_name("mcl_bows:crossbow_loaded")
|
||||
else
|
||||
wielditem:set_name("mcl_bows:crossbow_loaded_enchanted")
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
minetest.sound_play("mcl_bows_crossbow_load", {pos=player:get_pos(), max_hear_distance=16}, true)
|
||||
else
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end)
|
||||
|
||||
controls.register_on_press(function(player, key, time)
|
||||
if key~="LMB" then return end
|
||||
wielditem = player:get_wielded_item()
|
||||
if wielditem:get_name()=="mcl_bows:crossbow_loaded" or wielditem:get_name()=="mcl_bows:crossbow_loaded_enchanted" then
|
||||
local enchanted = mcl_enchanting.is_enchanted(wielditem:get_name())
|
||||
local speed, damage
|
||||
local p_load = bow_load[player:get_player_name()]
|
||||
local charge
|
||||
-- Type sanity check
|
||||
if type(p_load) == "number" then
|
||||
charge = minetest.get_us_time() - p_load
|
||||
else
|
||||
-- In case something goes wrong ...
|
||||
-- Just assume minimum charge.
|
||||
charge = 0
|
||||
minetest.log("warning", "[mcl_bows] Player "..player:get_player_name().." fires arrow with non-numeric bow_load!")
|
||||
end
|
||||
charge = math.max(math.min(charge, BOW_CHARGE_TIME_FULL), 0)
|
||||
|
||||
local charge_ratio = charge / BOW_CHARGE_TIME_FULL
|
||||
charge_ratio = math.max(math.min(charge_ratio, 1), 0)
|
||||
|
||||
-- Calculate damage and speed
|
||||
-- Fully charged
|
||||
local is_critical = false
|
||||
speed = BOW_MAX_SPEED
|
||||
local r = math.random(1,5)
|
||||
if r == 1 then
|
||||
-- 20% chance for critical hit
|
||||
damage = 10
|
||||
is_critical = true
|
||||
else
|
||||
damage = 9
|
||||
end
|
||||
|
||||
local has_shot = player_shoot_arrow(wielditem, player, speed, damage, is_critical)
|
||||
|
||||
if enchanted then
|
||||
wielditem:set_name("mcl_bows:crossbow_enchanted")
|
||||
else
|
||||
wielditem:set_name("mcl_bows:crossbow")
|
||||
end
|
||||
|
||||
if has_shot and not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
local durability = BOW_DURABILITY
|
||||
local unbreaking = mcl_enchanting.get_enchantment(wielditem, "unbreaking")
|
||||
local multishot = mcl_enchanting.get_enchantment(wielditem, "multishot")
|
||||
if unbreaking > 0 then
|
||||
durability = durability * (unbreaking + 1)
|
||||
end
|
||||
if multishot then
|
||||
durability = durability / 3
|
||||
end
|
||||
wielditem:add_wear(65535/durability)
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end)
|
||||
|
||||
controls.register_on_hold(function(player, key, time)
|
||||
local name = player:get_player_name()
|
||||
local creative = minetest.is_creative_enabled(name)
|
||||
if key ~= "RMB" then
|
||||
return
|
||||
end
|
||||
--local inv = minetest.get_inventory({type="player", name=name})
|
||||
local wielditem = player:get_wielded_item()
|
||||
local enchantments = mcl_enchanting.get_enchantments(wielditem)
|
||||
if enchantments.quick_charge then
|
||||
BOW_CHARGE_TIME_HALF = _BOW_CHARGE_TIME_HALF - (enchantments.quick_charge * 0.13 * 1000000 * .5)
|
||||
BOW_CHARGE_TIME_FULL = _BOW_CHARGE_TIME_FULL - (enchantments.quick_charge * 0.13 * 1000000)
|
||||
else
|
||||
BOW_CHARGE_TIME_HALF = _BOW_CHARGE_TIME_HALF
|
||||
BOW_CHARGE_TIME_FULL = _BOW_CHARGE_TIME_FULL
|
||||
end
|
||||
|
||||
if bow_load[name] == nil and (wielditem:get_name()=="mcl_bows:crossbow" or wielditem:get_name()=="mcl_bows:crossbow_enchanted") and wielditem:get_meta():get("active") and (creative or get_arrow(player)) then
|
||||
local enchanted = mcl_enchanting.is_enchanted(wielditem:get_name())
|
||||
if enchanted then
|
||||
wielditem:set_name("mcl_bows:crossbow_0_enchanted")
|
||||
play_load_sound(0, player:get_pos())
|
||||
else
|
||||
wielditem:set_name("mcl_bows:crossbow_0")
|
||||
play_load_sound(0, player:get_pos())
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
if minetest.get_modpath("playerphysics") then
|
||||
-- Slow player down when using bow
|
||||
playerphysics.add_physics_factor(player, "speed", "mcl_bows:use_crossbow", PLAYER_USE_CROSSBOW_SPEED)
|
||||
end
|
||||
bow_load[name] = minetest.get_us_time()
|
||||
bow_index[name] = player:get_wield_index()
|
||||
else
|
||||
if player:get_wield_index() == bow_index[name] then
|
||||
if type(bow_load[name]) == "number" then
|
||||
if wielditem:get_name() == "mcl_bows:crossbow_0" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_HALF then
|
||||
wielditem:set_name("mcl_bows:crossbow_1")
|
||||
play_load_sound(1, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_0_enchanted" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_HALF then
|
||||
wielditem:set_name("mcl_bows:crossbow_1_enchanted")
|
||||
play_load_sound(1, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_1" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_FULL then
|
||||
wielditem:set_name("mcl_bows:crossbow_2")
|
||||
play_load_sound(2, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_1_enchanted" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_FULL then
|
||||
wielditem:set_name("mcl_bows:crossbow_2_enchanted")
|
||||
play_load_sound(2, player:get_pos())
|
||||
end
|
||||
else
|
||||
if wielditem:get_name() == "mcl_bows:crossbow_0" or wielditem:get_name() == "mcl_bows:crossbow_1" or wielditem:get_name() == "mcl_bows:crossbow_2" then
|
||||
wielditem:set_name("mcl_bows:crossbow")
|
||||
play_load_sound(1, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_0_enchanted" or wielditem:get_name() == "mcl_bows:crossbow_1_enchanted" or wielditem:get_name() == "mcl_bows:crossbow_2_enchanted" then
|
||||
wielditem:set_name("mcl_bows:crossbow_enchanted")
|
||||
play_load_sound(1, player:get_pos())
|
||||
end
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
else
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local name = player:get_player_name()
|
||||
local wielditem = player:get_wielded_item()
|
||||
local wieldindex = player:get_wield_index()
|
||||
--local controls = player:get_player_control()
|
||||
if type(bow_load[name]) == "number" and ((wielditem:get_name()~="mcl_bows:crossbow_0" and wielditem:get_name()~="mcl_bows:crossbow_1" and wielditem:get_name()~="mcl_bows:crossbow_2" and wielditem:get_name()~="mcl_bows:crossbow_0_enchanted" and wielditem:get_name()~="mcl_bows:crossbow_1_enchanted" and wielditem:get_name()~="mcl_bows:crossbow_2_enchanted") or wieldindex ~= bow_index[name]) then
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
reset_bows(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
reset_bow_state(player, true)
|
||||
end)
|
||||
|
||||
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
|
||||
minetest.register_craft({
|
||||
output = "mcl_bows:crossbow",
|
||||
recipe = {
|
||||
{"mcl_core:stick", "mcl_core:iron_ingot", "mcl_core:stick"},
|
||||
{"mcl_mobitems:string", "mcl_bows:arrow", "mcl_mobitems:string"},
|
||||
{"", "mcl_core:stick", ""},
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
minetest.register_craft({
|
||||
type = "fuel",
|
||||
recipe = "group:bow",
|
||||
burntime = 15,
|
||||
})
|
||||
|
||||
-- Add entry aliases for the Help
|
||||
if minetest.get_modpath("doc") then
|
||||
doc.add_entry_alias("tools", "mcl_bows:crossbow", "tools", "mcl_bows:crossbow_0")
|
||||
doc.add_entry_alias("tools", "mcl_bows:crossbow", "tools", "mcl_bows:crossbow_1")
|
||||
doc.add_entry_alias("tools", "mcl_bows:crossbow", "tools", "mcl_bows:crossbow_2")
|
||||
end
|
@ -1,5 +1,11 @@
|
||||
--Bow
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/arrow.lua")
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/bow.lua")
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/rocket.lua")
|
||||
|
||||
--Crossbow
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/crossbow.lua")
|
||||
|
||||
--Compatiblility with older MineClone worlds
|
||||
minetest.register_alias("mcl_throwing:bow", "mcl_bows:bow")
|
||||
minetest.register_alias("mcl_throwing:arrow", "mcl_bows:arrow")
|
||||
|
BIN
mods/ITEMS/mcl_bows/models/mcl_bows_rocket.b3d
Normal file
10
mods/ITEMS/mcl_bows/models/mcl_bows_rocket.mtl
Normal file
@ -0,0 +1,10 @@
|
||||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl None
|
||||
Ns 500
|
||||
Ka 0.8 0.8 0.8
|
||||
Kd 0.8 0.8 0.8
|
||||
Ks 0.8 0.8 0.8
|
||||
d 1
|
||||
illum 2
|
1016
mods/ITEMS/mcl_bows/models/mcl_bows_rocket.obj
Normal file
706
mods/ITEMS/mcl_bows/rocket.lua
Normal file
@ -0,0 +1,706 @@
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
local math = math
|
||||
local vector = vector
|
||||
|
||||
-- Time in seconds after which a stuck arrow is deleted
|
||||
local ARROW_TIMEOUT = 1
|
||||
-- Time after which stuck arrow is rechecked for being stuck
|
||||
local STUCK_RECHECK_TIME = 0.1
|
||||
|
||||
--local GRAVITY = 9.81
|
||||
|
||||
local YAW_OFFSET = -math.pi/2
|
||||
|
||||
local function dir_to_pitch(dir)
|
||||
--local dir2 = vector.normalize(dir)
|
||||
local xz = math.abs(dir.x) + math.abs(dir.z)
|
||||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
local function random_arrow_positions(positions, placement)
|
||||
if positions == "x" then
|
||||
return math.random(-4, 4)
|
||||
elseif positions == "y" then
|
||||
return math.random(0, 10)
|
||||
end
|
||||
if placement == "front" and positions == "z" then
|
||||
return 3
|
||||
elseif placement == "back" and positions == "z" then
|
||||
return -3
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function damage_explosion(self, damagemulitplier)
|
||||
mcl_explosions.explode(self.object:get_pos(), 3, {})
|
||||
local objects = minetest.get_objects_inside_radius(self.object:get_pos(), 8)
|
||||
for _,obj in pairs(objects) do
|
||||
if obj:is_player() then
|
||||
mcl_util.deal_damage(obj, damagemulitplier - vector.distance(self.object:get_pos(), obj:get_pos()), {type = "explosion"})
|
||||
elseif obj:get_luaentity()._cmi_is_mob then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=damagemulitplier - vector.distance(self.object:get_pos(), obj:get_pos())},
|
||||
}, self.object:get_velocity())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function particle_explosion(self)
|
||||
local particle_pattern = math.random(1, 3)
|
||||
local fpitch = 0
|
||||
local true_type = ""
|
||||
local type = math.random(1,2)
|
||||
local size = math.random(1,3)
|
||||
local colors = {"red", "yellow", "blue", "green", "white"}
|
||||
local this_colors = {colors[math.random(#colors)], colors[math.random(#colors)], colors[math.random(#colors)]}
|
||||
|
||||
if size == 1 then
|
||||
fpitch = math.random(200, 300)
|
||||
elseif size == 2 then
|
||||
fpitch = math.random(100, 130)
|
||||
else
|
||||
fpitch = math.random(60, 70)
|
||||
end
|
||||
|
||||
if type == 1 then
|
||||
true_type = "Popper"
|
||||
else
|
||||
true_type = "Floof"
|
||||
end
|
||||
|
||||
if type == 1 then
|
||||
minetest.sound_play("mcl_bows_firework", {
|
||||
pos = self.object:get_pos(),
|
||||
max_hear_distance = 100,
|
||||
gain = 3.0,
|
||||
pitch = fpitch/100
|
||||
}, true)
|
||||
else
|
||||
minetest.sound_play("mcl_bows_firework_soft", {
|
||||
pos = self.object:get_pos(),
|
||||
max_hear_distance = 100,
|
||||
gain = 4.0,
|
||||
pitch = fpitch/100
|
||||
}, true)
|
||||
end
|
||||
|
||||
if particle_pattern == 1 then
|
||||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-7 * size,-7 * size,-7 * size),
|
||||
maxvel = vector.new(7 * size,7 * size,7 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[1]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-2 * size,-2 * size,-2 * size),
|
||||
maxvel = vector.new(2 * size,2 * size,2 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[2]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 100 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-14 * size,-14 * size,-14 * size),
|
||||
maxvel = vector.new(14 * size,14 * size,14 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[3]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
elseif particle_pattern == 2 then
|
||||
|
||||
minetest.add_particlespawner({
|
||||
amount = 240 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-5 * size,-5 * size,-5 * size),
|
||||
maxvel = vector.new(5 * size,5 * size,5 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[1]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 500 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-2 * size,-2 * size,-2 * size),
|
||||
maxvel = vector.new(2 * size,2 * size,2 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[2]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 350 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-3 * size,-3 * size,-3 * size),
|
||||
maxvel = vector.new(3 * size,3 * size,3 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[3]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
elseif particle_pattern == 3 then
|
||||
|
||||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-6 * size,-4 * size,-6 * size),
|
||||
maxvel = vector.new(6 * size,4 * size,6 * size),
|
||||
minexptime = .6 * size,
|
||||
maxexptime = .9 * size,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[1]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 120 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-8 * size,6 * size,-8 * size),
|
||||
maxvel = vector.new(8 * size,6 * size,8 * size),
|
||||
minexptime = .6 * size,
|
||||
maxexptime = .9 * size,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[2]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 130 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-3 * size,3 * size,-3 * size),
|
||||
maxvel = vector.new(3 * size,3 * size,3 * size),
|
||||
minexptime = .6 * size,
|
||||
maxexptime = .9 * size,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[3]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
end
|
||||
|
||||
return size
|
||||
|
||||
end
|
||||
|
||||
local mod_awards = minetest.get_modpath("awards") and minetest.get_modpath("mcl_achievements")
|
||||
local mod_button = minetest.get_modpath("mesecons_button")
|
||||
|
||||
minetest.register_craftitem("mcl_bows:rocket", {
|
||||
description = S("Arrow"),
|
||||
_tt_help = S("Ammunition").."\n"..S("Damage from bow: 1-10").."\n"..S("Damage from dispenser: 3"),
|
||||
_doc_items_longdesc = S("Arrows are ammunition for bows and dispensers.").."\n"..
|
||||
S("An arrow fired from a bow has a regular damage of 1-9. At full charge, there's a 20% chance of a critical hit dealing 10 damage instead. An arrow fired from a dispenser always deals 3 damage.").."\n"..
|
||||
S("Arrows might get stuck on solid blocks and can be retrieved again. They are also capable of pushing wooden buttons."),
|
||||
_doc_items_usagehelp = S("To use arrows as ammunition for a bow, just put them anywhere in your inventory, they will be used up automatically. To use arrows as ammunition for a dispenser, place them in the dispenser's inventory. To retrieve an arrow that sticks in a block, simply walk close to it."),
|
||||
inventory_image = "mcl_bows_rocket.png",
|
||||
groups = { ammo=1, ammo_crossbow=1, ammo_bow_regular=1 },
|
||||
_on_dispense = function(itemstack, dispenserpos, droppos, dropnode, dropdir)
|
||||
-- Shoot arrow
|
||||
local shootpos = vector.add(dispenserpos, vector.multiply(dropdir, 0.51))
|
||||
local yaw = math.atan2(dropdir.z, dropdir.x) + YAW_OFFSET
|
||||
mcl_bows.shoot_arrow(itemstack:get_name(), shootpos, dropdir, yaw, nil, 19, 3)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
|
||||
local ARROW_ENTITY={
|
||||
physical = true,
|
||||
pointable = false,
|
||||
visual = "mesh",
|
||||
mesh = "mcl_bows_rocket.obj",
|
||||
visual_size = {x=2.5, y=2.5},
|
||||
textures = {"mcl_bows_rocket.png"},
|
||||
collisionbox = {-0.19, -0.125, -0.19, 0.19, 0.125, 0.19},
|
||||
collide_with_objects = false,
|
||||
_fire_damage_resistant = true,
|
||||
|
||||
_lastpos={},
|
||||
_startpos=nil,
|
||||
_damage=1, -- Damage on impact
|
||||
_is_critical=false, -- Whether this arrow would deal critical damage
|
||||
_stuck=false, -- Whether arrow is stuck
|
||||
_fuse=nil,-- Amount of time (in seconds) the arrow has been stuck so far
|
||||
_fuserechecktimer=nil,-- An additional timer for periodically re-checking the stuck status of an arrow
|
||||
_stuckin=nil, --Position of node in which arow is stuck.
|
||||
_shooter=nil, -- ObjectRef of player or mob who shot it
|
||||
_is_arrow = true,
|
||||
|
||||
_viscosity=0, -- Viscosity of node the arrow is currently in
|
||||
_deflection_cooloff=0, -- Cooloff timer after an arrow deflection, to prevent many deflections in quick succession
|
||||
}
|
||||
|
||||
-- Destroy arrow entity self at pos and drops it as an item
|
||||
local function spawn_item(self, pos)
|
||||
if not minetest.is_creative_enabled("") then
|
||||
local item = minetest.add_item(pos, "mcl_bows:rocket")
|
||||
item:set_velocity({x=0, y=0, z=0})
|
||||
item:set_yaw(self.object:get_yaw())
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
local function damage_particles(pos, is_critical)
|
||||
if is_critical then
|
||||
minetest.add_particlespawner({
|
||||
amount = 15,
|
||||
time = 0.1,
|
||||
minpos = {x=pos.x-0.5, y=pos.y-0.5, z=pos.z-0.5},
|
||||
maxpos = {x=pos.x+0.5, y=pos.y+0.5, z=pos.z+0.5},
|
||||
minvel = {x=-0.1, y=-0.1, z=-0.1},
|
||||
maxvel = {x=0.1, y=0.1, z=0.1},
|
||||
minacc = {x=0, y=0, z=0},
|
||||
maxacc = {x=0, y=0, z=0},
|
||||
minexptime = 1,
|
||||
maxexptime = 2,
|
||||
minsize = 1.5,
|
||||
maxsize = 1.5,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_crit.png^[colorize:#bc7a57:127",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_step(self, dtime)
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
|
||||
self._time_in_air = self._time_in_air + .001
|
||||
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
local dpos = table.copy(pos) -- digital pos
|
||||
dpos = vector.round(dpos)
|
||||
local node = minetest.get_node(dpos)
|
||||
|
||||
if not self._fuse then
|
||||
self._fuse = 0
|
||||
end
|
||||
if not self._fuserechecktimer then
|
||||
self._fuserechecktimer = 0
|
||||
end
|
||||
|
||||
self._fuse = self._fuse + dtime
|
||||
self._fuserechecktimer = self._fuserechecktimer + dtime
|
||||
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
self._stuck = true
|
||||
end
|
||||
if self._stuck then
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
-- Drop arrow as item when it is no longer stuck
|
||||
-- FIXME: Arrows are a bit slow to react and continue to float in mid air for a few seconds.
|
||||
if self._fuserechecktimer > STUCK_RECHECK_TIME then
|
||||
local stuckin_def
|
||||
if self._stuckin then
|
||||
stuckin_def = minetest.registered_nodes[minetest.get_node(self._stuckin).name]
|
||||
end
|
||||
-- TODO: In MC, arrow just falls down without turning into an item
|
||||
if stuckin_def and stuckin_def.walkable == false then
|
||||
spawn_item(self, pos)
|
||||
return
|
||||
end
|
||||
self._fuserechecktimer = 0
|
||||
end
|
||||
-- Pickup arrow if player is nearby (not in Creative Mode)
|
||||
local objects = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _,obj in ipairs(objects) do
|
||||
if obj:is_player() then
|
||||
if self._collectable and not minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
if obj:get_inventory():room_for_item("main", "mcl_bows:rocket") then
|
||||
obj:get_inventory():add_item("main", "mcl_bows:rocket")
|
||||
minetest.sound_play("item_drop_pickup", {
|
||||
pos = pos,
|
||||
max_hear_distance = 16,
|
||||
gain = 1.0,
|
||||
}, true)
|
||||
end
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for object "collision". Done every tick (hopefully this is not too stressing)
|
||||
else
|
||||
|
||||
if self._in_player == false then
|
||||
minetest.add_particlespawner({
|
||||
amount = 1,
|
||||
time = .0001,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-0.1,-0.1,-0.1),
|
||||
maxvel = vector.new(0.1,0.1,0.1),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 0.5,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_rocket_particle.png",
|
||||
glow = 1,
|
||||
})
|
||||
end
|
||||
-- We just check for any hurtable objects nearby.
|
||||
-- The radius of 3 is fairly liberal, but anything lower than than will cause
|
||||
-- arrow to hilariously go through mobs often.
|
||||
-- TODO: Implement an ACTUAL collision detection (engine support needed).
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1.5)
|
||||
local closest_object
|
||||
local closest_distance
|
||||
|
||||
if self._deflection_cooloff > 0 then
|
||||
self._deflection_cooloff = self._deflection_cooloff - dtime
|
||||
end
|
||||
|
||||
-- Iterate through all objects and remember the closest attackable object
|
||||
for k, obj in pairs(objs) do
|
||||
local ok = false
|
||||
-- Arrows can only damage players and mobs
|
||||
if obj:is_player() then
|
||||
ok = true
|
||||
elseif obj:get_luaentity() then
|
||||
if (obj:get_luaentity()._cmi_is_mob or obj:get_luaentity()._hittable_by_projectile) then
|
||||
ok = true
|
||||
end
|
||||
end
|
||||
|
||||
if ok then
|
||||
local dist = vector.distance(pos, obj:get_pos())
|
||||
if not closest_object or not closest_distance then
|
||||
closest_object = obj
|
||||
closest_distance = dist
|
||||
elseif dist < closest_distance then
|
||||
closest_object = obj
|
||||
closest_distance = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If an attackable object was found, we will damage the closest one only
|
||||
|
||||
if closest_object then
|
||||
local obj = closest_object
|
||||
local is_player = obj:is_player()
|
||||
local lua = obj:get_luaentity()
|
||||
if obj == self._shooter and self._time_in_air > 1.02 or obj ~= self._shooter and (is_player or (lua and (lua._cmi_is_mob or lua._hittable_by_projectile))) then
|
||||
if obj:get_hp() > 0 then
|
||||
-- Check if there is no solid node between arrow and object
|
||||
local ray = minetest.raycast(self.object:get_pos(), obj:get_pos(), true)
|
||||
for pointed_thing in ray do
|
||||
if pointed_thing.type == "object" and pointed_thing.ref == closest_object then
|
||||
-- Target reached! We can proceed now.
|
||||
break
|
||||
elseif pointed_thing.type == "node" then
|
||||
local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name
|
||||
local def = minetest.registered_nodes[nn]
|
||||
if (not def) or def.walkable then
|
||||
-- There's a node in the way. Delete arrow without damage
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Punch target object but avoid hurting enderman.
|
||||
if not lua or lua.name ~= "mobs_mc:enderman" then
|
||||
if self._in_player == false then
|
||||
damage_particles(self.object:get_pos(), self._is_critical)
|
||||
end
|
||||
if mcl_burning.is_burning(self.object) then
|
||||
mcl_burning.set_on_fire(obj, 5)
|
||||
end
|
||||
if self._in_player == false then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, self.object:get_velocity())
|
||||
if obj:is_player() then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if is_player then
|
||||
if self._shooter and self._shooter:is_player() and self._in_player == false then
|
||||
-- “Ding” sound for hitting another player
|
||||
minetest.sound_play({name="mcl_bows_hit_player", gain=0.1}, {to_player=self._shooter:get_player_name()}, true)
|
||||
end
|
||||
end
|
||||
|
||||
if lua then
|
||||
local entity_name = lua.name
|
||||
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
|
||||
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
|
||||
-- TODO: This achievement should be given for the kill, not just a hit
|
||||
if self._shooter and self._shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
|
||||
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
|
||||
awards.unlock(self._shooter:get_player_name(), "mcl:snipeSkeleton")
|
||||
end
|
||||
end
|
||||
end
|
||||
if self._in_player == false then
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
end
|
||||
end
|
||||
if not obj:is_player() then
|
||||
mcl_burning.extinguish(self.object)
|
||||
if self._piercing == 0 then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for node collision
|
||||
if self._lastpos.x~=nil and not self._stuck then
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local vel = self.object:get_velocity()
|
||||
-- Arrow has stopped in one axis, so it probably hit something.
|
||||
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
|
||||
if (math.abs(vel.x) < 0.0001) or (math.abs(vel.z) < 0.0001) or (math.abs(vel.y) < 0.00001) then
|
||||
-- Check for the node to which the arrow is pointing
|
||||
local dir
|
||||
if math.abs(vel.y) < 0.00001 then
|
||||
if self._lastpos.y < pos.y then
|
||||
dir = {x=0, y=1, z=0}
|
||||
else
|
||||
dir = {x=0, y=-1, z=0}
|
||||
end
|
||||
else
|
||||
dir = minetest.facedir_to_dir(minetest.dir_to_facedir(minetest.yaw_to_dir(self.object:get_yaw()-YAW_OFFSET)))
|
||||
end
|
||||
self._stuckin = vector.add(dpos, dir)
|
||||
local snode = minetest.get_node(self._stuckin)
|
||||
local sdef = minetest.registered_nodes[snode.name]
|
||||
|
||||
-- If node is non-walkable, unknown or ignore, don't make arrow stuck.
|
||||
-- This causes a deflection in the engine.
|
||||
if not sdef or sdef.walkable == false or snode.name == "ignore" then
|
||||
self._stuckin = nil
|
||||
if self._deflection_cooloff <= 0 then
|
||||
-- Lose 1/3 of velocity on deflection
|
||||
local newvel = vector.multiply(vel, 0.6667)
|
||||
|
||||
self.object:set_velocity(newvel)
|
||||
-- Reset deflection cooloff timer to prevent many deflections happening in quick succession
|
||||
self._deflection_cooloff = 1.0
|
||||
end
|
||||
else
|
||||
|
||||
-- Node was walkable, make arrow stuck
|
||||
self._stuck = true
|
||||
self._fuserechecktimer = 0
|
||||
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
|
||||
if mcl_burning.is_burning(self.object) and snode.name == "mcl_tnt:tnt" then
|
||||
tnt.ignite(self._stuckin)
|
||||
end
|
||||
|
||||
-- Push the button! Push, push, push the button!
|
||||
if mod_button and minetest.get_item_group(node.name, "button") > 0 and minetest.get_item_group(node.name, "button_push_by_arrow") == 1 then
|
||||
local bdir = minetest.wallmounted_to_dir(node.param2)
|
||||
-- Check the button orientation
|
||||
if vector.equals(vector.add(dpos, bdir), self._stuckin) then
|
||||
mesecon.push_button(dpos, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif (def and def.liquidtype ~= "none") then
|
||||
-- Slow down arrow in liquids
|
||||
local v = def.liquid_viscosity
|
||||
if not v then
|
||||
v = 0
|
||||
end
|
||||
--local old_v = self._viscosity
|
||||
self._viscosity = v
|
||||
local vpenalty = math.max(0.1, 0.98 - 0.1 * v)
|
||||
if math.abs(vel.x) > 0.001 then
|
||||
vel.x = vel.x * vpenalty
|
||||
end
|
||||
if math.abs(vel.z) > 0.001 then
|
||||
vel.z = vel.z * vpenalty
|
||||
end
|
||||
self.object:set_velocity(vel)
|
||||
end
|
||||
end
|
||||
|
||||
-- Update yaw
|
||||
if not self._stuck then
|
||||
local vel = self.object:get_velocity()
|
||||
local yaw = minetest.dir_to_yaw(vel)+YAW_OFFSET
|
||||
local pitch = dir_to_pitch(vel)
|
||||
self.object:set_rotation({ x = 0, y = yaw, z = pitch })
|
||||
end
|
||||
|
||||
-- Update internal variable
|
||||
self._lastpos={x=pos.x, y=pos.y, z=pos.z}
|
||||
end
|
||||
|
||||
-- Force recheck of stuck arrows when punched.
|
||||
-- Otherwise, punching has no effect.
|
||||
function ARROW_ENTITY.on_punch(self)
|
||||
if self._stuck then
|
||||
self._fuserechecktimer = STUCK_RECHECK_TIME
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.get_staticdata(self)
|
||||
local out = {
|
||||
lastpos = self._lastpos,
|
||||
startpos = self._startpos,
|
||||
damage = self._damage,
|
||||
is_critical = self._is_critical,
|
||||
stuck = self._stuck,
|
||||
stuckin = self._stuckin,
|
||||
}
|
||||
if self._stuck then
|
||||
-- If _fuse is missing for some reason, assume the maximum
|
||||
if not self._fuse then
|
||||
self._fuse = ARROW_TIMEOUT
|
||||
end
|
||||
out.stuckstarttime = minetest.get_gametime() - self._fuse
|
||||
end
|
||||
if self._shooter and self._shooter:is_player() then
|
||||
out.shootername = self._shooter:get_player_name()
|
||||
end
|
||||
return minetest.serialize(out)
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
|
||||
self._time_in_air = 1.0
|
||||
self._in_player = false
|
||||
local data = minetest.deserialize(staticdata)
|
||||
if data then
|
||||
self._stuck = data.stuck
|
||||
if data.stuck then
|
||||
if data.stuckstarttime then
|
||||
-- First, check if the stuck arrow is aleady past its life timer.
|
||||
-- If yes, delete it.
|
||||
self._fuse = minetest.get_gametime() - data.stuckstarttime
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self._fuse = 2
|
||||
-- Perform a stuck recheck on the next step.
|
||||
self._fuserechecktimer = STUCK_RECHECK_TIME
|
||||
|
||||
self._stuckin = data.stuckin
|
||||
end
|
||||
|
||||
-- Get the remaining arrow state
|
||||
self._lastpos = data.lastpos
|
||||
self._startpos = data.startpos
|
||||
self._damage = data.damage
|
||||
self._is_critical = data.is_critical
|
||||
if data.shootername then
|
||||
local shooter = minetest.get_player_by_name(data.shootername)
|
||||
if shooter and shooter:is_player() then
|
||||
self._shooter = shooter
|
||||
end
|
||||
end
|
||||
end
|
||||
self.object:set_armor_groups({ immortal = 1 })
|
||||
end
|
||||
|
||||
minetest.register_entity("mcl_bows:rocket_entity", ARROW_ENTITY)
|
||||
|
||||
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
|
||||
minetest.register_craft({
|
||||
output = "mcl_bows:rocket 1",
|
||||
recipe = {
|
||||
{"mcl_core:paper"},
|
||||
{"mcl_fireworks:rocket_2"},
|
||||
{"mcl_bows:arrow"},
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object("mcl_bows:rocket_entity", "craftitems", "mcl_bows:rocket")
|
||||
end
|
BIN
mods/ITEMS/mcl_bows/sounds/mcl_bows_crossbow_drawback_0.ogg
Normal file
BIN
mods/ITEMS/mcl_bows/sounds/mcl_bows_crossbow_drawback_1.ogg
Normal file
BIN
mods/ITEMS/mcl_bows/sounds/mcl_bows_crossbow_drawback_2.ogg
Normal file
BIN
mods/ITEMS/mcl_bows/sounds/mcl_bows_crossbow_load.ogg
Normal file
BIN
mods/ITEMS/mcl_bows/sounds/mcl_bows_crossbow_shoot.ogg
Normal file
BIN
mods/ITEMS/mcl_bows/sounds/mcl_bows_firework.ogg
Normal file
BIN
mods/ITEMS/mcl_bows/sounds/mcl_bows_firework_soft.ogg
Normal file
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_crossbow.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_crossbow_0.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_crossbow_1.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_crossbow_2.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_crossbow_3.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_firework_blue.png
Normal file
After Width: | Height: | Size: 630 B |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_firework_green.png
Normal file
After Width: | Height: | Size: 627 B |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_firework_red.png
Normal file
After Width: | Height: | Size: 622 B |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_firework_white.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_firework_yellow.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_rocket.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
mods/ITEMS/mcl_bows/textures/mcl_bows_rocket_particle.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
@ -379,8 +379,8 @@ mcl_enchanting.enchantments.mending = {
|
||||
inv_tool_tab = true,
|
||||
}
|
||||
|
||||
-- requires missing MineClone2 feature
|
||||
--[[mcl_enchanting.enchantments.multishot = {
|
||||
|
||||
mcl_enchanting.enchantments.multishot = {
|
||||
name = S("Multishot"),
|
||||
max_level = 1,
|
||||
primary = {crossbow = true},
|
||||
@ -396,10 +396,10 @@ mcl_enchanting.enchantments.mending = {
|
||||
power_range_table = {{20, 50}},
|
||||
inv_combat_tab = true,
|
||||
inv_tool_tab = false,
|
||||
}]]--
|
||||
}
|
||||
|
||||
-- requires missing MineClone2 feature
|
||||
--[[mcl_enchanting.enchantments.piercing = {
|
||||
mcl_enchanting.enchantments.piercing = {
|
||||
name = S("Piercing"),
|
||||
max_level = 4,
|
||||
primary = {crossbow = true},
|
||||
@ -415,7 +415,7 @@ mcl_enchanting.enchantments.mending = {
|
||||
power_range_table = {{1, 50}, {11, 50}, {21, 50}, {31, 50}},
|
||||
inv_combat_tab = true,
|
||||
inv_tool_tab = false,
|
||||
}]]--
|
||||
}
|
||||
|
||||
-- implemented in mcl_bows
|
||||
mcl_enchanting.enchantments.power = {
|
||||
@ -456,7 +456,7 @@ mcl_enchanting.enchantments.punch = {
|
||||
}
|
||||
|
||||
-- requires missing MineClone2 feature
|
||||
--[[mcl_enchanting.enchantments.quick_charge = {
|
||||
mcl_enchanting.enchantments.quick_charge = {
|
||||
name = S("Quick Charge"),
|
||||
max_level = 3,
|
||||
primary = {crossbow = true},
|
||||
@ -472,7 +472,7 @@ mcl_enchanting.enchantments.punch = {
|
||||
power_range_table = {{12, 50}, {32, 50}, {52, 50}},
|
||||
inv_combat_tab = true,
|
||||
inv_tool_tab = false,
|
||||
}]]--
|
||||
}
|
||||
|
||||
-- unimplemented
|
||||
--[[mcl_enchanting.enchantments.respiration = {
|
||||
|
@ -230,7 +230,7 @@ local function spawn_mobs(pos, elapsed)
|
||||
|
||||
-- spawn up to 4 mobs in random air blocks
|
||||
if air then
|
||||
local max = 4
|
||||
local max = 200
|
||||
if spawn_count_overrides[mob] then
|
||||
max = spawn_count_overrides[mob]
|
||||
end
|
||||
@ -387,4 +387,3 @@ minetest.register_lbm({
|
||||
respawn_doll(pos)
|
||||
end,
|
||||
})
|
||||
|
||||
|
@ -463,4 +463,4 @@ function mcl_potions.register_arrow(name, desc, color, def)
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object("mcl_bows:arrow_entity", "craftitems", "mcl_bows:arrow")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,7 +9,7 @@ local animation_blend = 0
|
||||
local function get_mouse_button(player)
|
||||
local controls = player:get_player_control()
|
||||
local get_wielded_item_name = player:get_wielded_item():get_name()
|
||||
if controls.RMB and not string.find(get_wielded_item_name, "mcl_bows:bow") or controls.LMB then
|
||||
if controls.RMB and not string.find(get_wielded_item_name, "mcl_bows:bow") and not string.find(get_wielded_item_name, "mcl_bows:crossbow") or controls.LMB then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
@ -212,9 +212,9 @@ minetest.register_globalstep(function(dtime)
|
||||
player_set_animation(player, "swim_walk_mine", animation_speed_mod)
|
||||
elseif not controls.sneak and head_in_water and is_sprinting == true then
|
||||
player_set_animation(player, "swim_walk", animation_speed_mod)
|
||||
elseif string.find(player:get_wielded_item():get_name(), "mcl_bows:bow") and controls.RMB and controls.sneak then
|
||||
elseif string.find(player:get_wielded_item():get_name(), "mcl_bows:bow") and controls.RMB and controls.sneak or string.find(player:get_wielded_item():get_name(), "mcl_bows:crossbow_") and controls.sneak then
|
||||
player_set_animation(player, "bow_sneak", animation_speed_mod)
|
||||
elseif string.find(player:get_wielded_item():get_name(), "mcl_bows:bow") and controls.RMB then
|
||||
elseif string.find(player:get_wielded_item():get_name(), "mcl_bows:bow") and controls.RMB or string.find(player:get_wielded_item():get_name(), "mcl_bows:crossbow_") then
|
||||
player_set_animation(player, "bow_walk", animation_speed_mod)
|
||||
elseif is_sprinting == true and get_mouse_button(player) == true and not controls.sneak and not head_in_water then
|
||||
player_set_animation(player, "run_walk_mine", animation_speed_mod)
|
||||
|
@ -127,6 +127,7 @@ minetest.register_globalstep(function(dtime)
|
||||
for _,player in pairs(get_connected_players()) do
|
||||
|
||||
--[[
|
||||
|
||||
_ _ _
|
||||
__ _ _ __ (_)_ __ ___ __ _| |_(_) ___ _ __ ___
|
||||
/ _` | '_ \| | '_ ` _ \ / _` | __| |/ _ \| '_ \/ __|
|
||||
@ -220,6 +221,10 @@ minetest.register_globalstep(function(dtime)
|
||||
player:set_bone_position("Wield_Item", vector.new(0,3.9,1.3), vector.new(90,0,0))
|
||||
elseif string.find(wielded:get_name(), "mcl_bows:bow") then
|
||||
player:set_bone_position("Wield_Item", vector.new(.5,4.5,-1.6), vector.new(90,0,20))
|
||||
elseif string.find(wielded:get_name(), "mcl_bows:crossbow_loaded") then
|
||||
player:set_bone_position("Wield_Item", vector.new(-1.5,5.7,1.8), vector.new(64,90,0))
|
||||
elseif string.find(wielded:get_name(), "mcl_bows:crossbow") then
|
||||
player:set_bone_position("Wield_Item", vector.new(-1.5,5.7,1.8), vector.new(90,90,0))
|
||||
else
|
||||
player:set_bone_position("Wield_Item", vector.new(-1.5,4.9,1.8), vector.new(135,0,90))
|
||||
end
|
||||
@ -231,6 +236,14 @@ minetest.register_globalstep(function(dtime)
|
||||
if string.find(wielded:get_name(), "mcl_bows:bow") and control.RMB then
|
||||
player:set_bone_position("Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(pitch+90,-30,pitch * -1 * .35))
|
||||
player:set_bone_position("Arm_Left_Pitch_Control", vector.new(3.5,5.785,0), vector.new(pitch+90,43,pitch * .35))
|
||||
-- controls right and left arms pitch when holing a loaded crossbow
|
||||
elseif string.find(wielded:get_name(), "mcl_bows:crossbow_loaded") then
|
||||
player:set_bone_position("Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(pitch+90,-30,pitch * -1 * .35))
|
||||
player:set_bone_position("Arm_Left_Pitch_Control", vector.new(3.5,5.785,0), vector.new(pitch+90,43,pitch * .35))
|
||||
-- controls right and left arms pitch when loading a crossbow
|
||||
elseif string.find(wielded:get_name(), "mcl_bows:crossbow_") then
|
||||
player:set_bone_position("Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(45,-20,25))
|
||||
player:set_bone_position("Arm_Left_Pitch_Control", vector.new(3,5.785,0), vector.new(55,20,-45))
|
||||
-- when punching
|
||||
elseif control.LMB and not parent then
|
||||
player:set_bone_position("Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(pitch,0,0))
|
||||
|