Revamp Crossbows

This commit is contained in:
epCode 2021-10-13 12:26:54 -07:00
parent f856b4d06c
commit dc8c5dbfcc
22 changed files with 2052 additions and 2 deletions

View File

@ -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."), 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."), _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", 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) _on_dispense = function(itemstack, dispenserpos, droppos, dropnode, dropdir)
-- Shoot arrow -- Shoot arrow
local shootpos = vector.add(dispenserpos, vector.multiply(dropdir, 0.51)) local shootpos = vector.add(dispenserpos, vector.multiply(dropdir, 0.51))
@ -324,8 +324,10 @@ function ARROW_ENTITY.on_step(self, dtime)
end end
if not obj:is_player() then if not obj:is_player() then
mcl_burning.extinguish(self.object) mcl_burning.extinguish(self.object)
if self._piercing == 0 then
self.object:remove() self.object:remove()
end end
end
return return
end end
end end

View 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)
minetest.sound_play("mcl_bows_crossbow_drawback_"..id, {pos=pos, max_hear_distance=16}, 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()) 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)
wielditem:set_name("mcl_bows:crossbow_loaded")
player:set_wielded_item(wielditem)
minetest.sound_play("mcl_bows_crossbow_load", {pos=pos, max_hear_distance=16}, true)
elseif wielditem:get_name()=="mcl_bows:crossbow_2_enchanted" or wielditem:get_name()=="mcl_bows:crossbow_2_enchanted" and minetest.is_creative_enabled(player:get_player_name()) then
wielditem:set_name("mcl_bows:crossbow_loaded_enchanted")
player:set_wielded_item(wielditem)
minetest.sound_play("mcl_bows_crossbow_load", {pos=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)
else
wielditem:set_name("mcl_bows:crossbow_0")
play_load_sound(0)
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)
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)
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)
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)
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)
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)
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

View File

@ -1,5 +1,11 @@
--Bow
dofile(minetest.get_modpath("mcl_bows") .. "/arrow.lua") dofile(minetest.get_modpath("mcl_bows") .. "/arrow.lua")
dofile(minetest.get_modpath("mcl_bows") .. "/bow.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:bow", "mcl_bows:bow")
minetest.register_alias("mcl_throwing:arrow", "mcl_bows:arrow") minetest.register_alias("mcl_throwing:arrow", "mcl_bows:arrow")

Binary file not shown.

View 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,562 @@
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)
local objects = minetest.get_objects_inside_radius(self.object:get_pos(), 10)
for _,obj in pairs(objects) do
if obj:is_player() then
mcl_util.deal_damage(obj, 17 - 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=17 - vector.distance(self.object:get_pos(), obj:get_pos())},
}, self.object:get_velocity())
end
end
end
local function particle_explosion(self)
minetest.add_particlespawner({
amount = 100,
time = 0.0001,
minpos = self.object:get_pos(),
maxpos = self.object:get_pos(),
minvel = vector.new(-4,-4,-4),
maxvel = vector.new(4,4,4),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 2,
maxsize = 3,
collisiondetection = false,
vertical = false,
texture = "mcl_bows_firework_red.png",
glow = 14,
})
minetest.add_particlespawner({
amount = 100,
time = 0.0001,
minpos = self.object:get_pos(),
maxpos = self.object:get_pos(),
minvel = vector.new(-9,-9,-9),
maxvel = vector.new(9,9,9),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 2,
maxsize = 3,
collisiondetection = false,
vertical = false,
texture = "mcl_bows_firework_blue.png",
glow = 14,
})
minetest.add_particlespawner({
amount = 100,
time = 0.0001,
minpos = self.object:get_pos(),
maxpos = self.object:get_pos(),
minvel = vector.new(-2,-2,-2),
maxvel = vector.new(2,2,2),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 2,
maxsize = 3,
collisiondetection = false,
vertical = false,
texture = "mcl_bows_firework_green.png",
glow = 14,
})
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
particle_explosion(self)
damage_explosion(self)
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
particle_explosion(self)
damage_explosion(self)
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
particle_explosion(self)
damage_explosion(self)
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 4",
recipe = {
{"mcl_core:flint"},
{"mcl_core:stick"},
{"mcl_mobitems:feather"}
}
})
end
if minetest.get_modpath("doc_identifier") then
doc.sub.identifier.register_object("mcl_bows:rocket_entity", "craftitems", "mcl_bows:rocket")
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB