4 ["mcl_throwing:arrow"] = "mcl_throwing:arrow_entity",
8 local BOW_DURABILITY
= 385
10 -- Charging time in microseconds
11 local BOW_CHARGE_TIME_HALF
= 500000 -- bow level 1
12 local BOW_CHARGE_TIME_FULL
= 1000000 -- bow level 2 (full charge)
14 -- TODO: Use Minecraft speed (ca. 53 m/s)
15 -- Currently nerfed because at full speed the arrow would easily get out of the range of the loaded map.
16 local BOW_MAX_SPEED
= 26
18 --[[ Store the charging state of each player.
21 nil = not charging or player not existing
22 number: currently charging, the number is the time from minetest.get_us_time
23 in which the charging has started
27 -- Another player table, this one stores the wield index of the bow being charged
30 mcl_throwing
.shoot_arrow
= function(arrow_item
, pos
, dir
, yaw
, shooter
, power
, damage
)
31 local obj
= minetest
.add_entity({x
=pos
.x
,y
=pos
.y
,z
=pos
.z
}, arrows
[arrow_item
])
38 obj
:setvelocity({x
=dir
.x
*power
, y
=dir
.y
*power
, z
=dir
.z
*power
})
39 obj
:setacceleration({x
=dir
.x
*-3, y
=-GRAVITY
, z
=dir
.z
*-3})
40 obj
:setyaw(yaw
-math
.pi
/2)
41 local le
= obj
:get_luaentity()
45 minetest
.sound_play("mcl_throwing_bow_shoot", {pos
=pos
})
46 if shooter
~= nil then
47 if obj
:get_luaentity().player
== "" then
48 obj
:get_luaentity().player
= shooter
50 obj
:get_luaentity().node
= shooter
:get_inventory():get_stack("main", 1):get_name()
55 local get_arrow
= function(player
)
56 local inv
= player
:get_inventory()
57 local arrow_stack
, arrow_stack_id
58 for i
=1, inv
:get_size("main") do
59 local it
= inv
:get_stack("main", i
)
60 if not it
:is_empty() and minetest
.get_item_group(it
:get_name(), "ammo_bow") ~= 0 then
66 return arrow_stack
, arrow_stack_id
69 local player_shoot_arrow
= function(itemstack
, player
, power
, damage
)
70 local arrow_stack
, arrow_stack_id
= get_arrow(player
)
71 local arrow_itemstring
72 if not minetest
.settings
:get_bool("creative_mode") then
73 if not arrow_stack
then
76 arrow_itemstring
= arrow_stack
:get_name()
77 arrow_stack
:take_item()
78 local inv
= player
:get_inventory()
79 inv
:set_stack("main", arrow_stack_id
, arrow_stack
)
81 local playerpos
= player
:getpos()
82 local dir
= player
:get_look_dir()
83 local yaw
= player
:get_look_horizontal()
85 if not arrow_itemstring
then
86 arrow_itemstring
= "mcl_throwing:arrow"
88 mcl_throwing
.shoot_arrow(arrow_itemstring
, {x
=playerpos
.x
,y
=playerpos
.y
+1.5,z
=playerpos
.z
}, dir
, yaw
, player
, power
, damage
)
92 -- Bow item, uncharged state
93 minetest
.register_tool("mcl_throwing:bow", {
95 _doc_items_longdesc
= [[Bows are ranged weapons to shoot arrows at your foes.
96 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.]],
97 _doc_items_usagehelp
= [[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.]],
98 _doc_items_durability
= BOW_DURABILITY
,
99 inventory_image
= "mcl_throwing_bow.png",
101 -- Trick to disable melee damage to entities.
102 -- Range not set to 0 (unlike the others) so it can be placed into item frames
104 -- Trick to disable digging as well
105 on_use
= function() end,
106 groups
= {weapon
=1,weapon_ranged
=1},
109 -- Bow in charging state
111 minetest
.register_tool("mcl_throwing:bow_"..level
, {
113 _doc_items_create_entry
= false,
114 inventory_image
= "mcl_throwing_bow_"..level
..".png",
116 range
= 0, -- Pointing range to 0 to prevent punching with bow :D
117 groups
= {not_in_creative_inventory
=1, not_in_craft_guide
=1},
118 on_drop
= function(itemstack
, dropper
, pos
)
119 bow_load
[player
:get_player_name()] = nil
120 bow_index
[player
:get_player_name()] = nil
121 itemstack
:set_name("mcl_throwing:bow")
122 minetest
.item_drop(itemstack
, dropper
, pos
)
123 itemstack
:take_item()
129 -- Resets all the bows in "charging" state back to their original stage
130 local reset_bows
= function(player
)
131 local inv
= player
:get_inventory()
132 local list
= inv
:get_list("main")
133 for place
, stack
in pairs(list
) do
134 if stack
:get_name()=="mcl_throwing:bow_0" or stack
:get_name()=="mcl_throwing:bow_1" or stack
:get_name()=="mcl_throwing:bow_2" then
135 stack
:set_name("mcl_throwing:bow")
139 inv
:set_list("main", list
)
142 controls
.register_on_release(function(player
, key
, time
)
143 if key
~="RMB" then return end
144 local inv
= minetest
.get_inventory({type="player", name
=player
:get_player_name()})
145 local wielditem
= player
:get_wielded_item()
146 if (wielditem
:get_name()=="mcl_throwing:bow_0" or wielditem
:get_name()=="mcl_throwing:bow_1" or wielditem
:get_name()=="mcl_throwing:bow_2") then
147 local has_shot
= false
150 local p_load
= bow_load
[player
:get_player_name()]
153 if type(p_load
) == "number" then
154 charge
= minetest
.get_us_time() - p_load
156 -- In case something goes wrong ...
157 -- Just assume minimum charge.
159 minetest
.log("warning", "[mcl_throwing] Player "..player
:get_player_name().." fires arrow with non-numeric bow_load!")
161 charge
= math
.max(math
.min(charge
, BOW_CHARGE_TIME_FULL
), 0)
163 local charge_ratio
= charge
/ BOW_CHARGE_TIME_FULL
164 charge_ratio
= math
.max(math
.min(charge_ratio
, 1), 0)
166 -- Calculate damage and speed
168 if charge
>= BOW_CHARGE_TIME_FULL
then
169 speed
= BOW_MAX_SPEED
170 local r
= math
.random(1,5)
172 -- 20% chance for critical hit
179 -- Linear speed and damage increase
180 speed
= math
.max(4, BOW_MAX_SPEED
* charge_ratio
)
181 damage
= math
.max(1, math
.floor(9 * charge_ratio
))
184 has_shot
= player_shoot_arrow(wielditem
, player
, speed
, damage
)
186 wielditem
:set_name("mcl_throwing:bow")
187 if has_shot
and minetest
.settings
:get_bool("creative_mode") == false then
188 wielditem
:add_wear(65535/BOW_DURABILITY
)
190 player
:set_wielded_item(wielditem
)
192 bow_load
[player
:get_player_name()] = nil
193 bow_index
[player
:get_player_name()] = nil
197 controls
.register_on_hold(function(player
, key
, time
)
201 local name
= player
:get_player_name()
202 local inv
= minetest
.get_inventory({type="player", name
=name
})
203 local wielditem
= player
:get_wielded_item()
204 if bow_load
[name
] == nil and wielditem
:get_name()=="mcl_throwing:bow" and (minetest
.settings
:get_bool("creative_mode") or inv
:contains_item("main", "mcl_throwing:arrow")) then
205 wielditem
:set_name("mcl_throwing:bow_0")
206 player
:set_wielded_item(wielditem
)
207 bow_load
[name
] = minetest
.get_us_time()
208 bow_index
[name
] = player
:get_wield_index()
210 if player
:get_wield_index() == bow_index
[name
] then
211 if type(bow_load
[name
]) == "number" then
212 if wielditem
:get_name() == "mcl_throwing:bow_0" and minetest
.get_us_time() - bow_load
[name
] >= BOW_CHARGE_TIME_HALF
then
213 wielditem
:set_name("mcl_throwing:bow_1")
214 elseif wielditem
:get_name() == "mcl_throwing:bow_1" and minetest
.get_us_time() - bow_load
[name
] >= BOW_CHARGE_TIME_FULL
then
215 wielditem
:set_name("mcl_throwing:bow_2")
218 if wielditem
:get_name() == "mcl_throwing:bow_0" or wielditem
:get_name() == "mcl_throwing:bow_1" or wielditem
:get_name() == "mcl_throwing:bow_2" then
219 wielditem
:set_name("mcl_throwing:bow")
222 player
:set_wielded_item(wielditem
)
226 bow_index
[name
] = nil
231 minetest
.register_globalstep(function(dtime
)
232 for _
, player
in pairs(minetest
.get_connected_players()) do
233 local name
= player
:get_player_name()
234 local wielditem
= player
:get_wielded_item()
235 local wieldindex
= player
:get_wield_index()
236 local controls
= player
:get_player_control()
237 if type(bow_load
[name
]) == "number" and ((wielditem
:get_name()~="mcl_throwing:bow_0" and wielditem
:get_name()~="mcl_throwing:bow_1" and wielditem
:get_name()~="mcl_throwing:bow_2") or wieldindex
~= bow_index
[name
]) then
240 bow_index
[name
] = nil
245 minetest
.register_on_joinplayer(function(player
)
249 minetest
.register_on_leaveplayer(function(player
)
251 bow_load
[player
:get_player_name()] = nil
252 bow_index
[player
:get_player_name()] = nil
255 if minetest
.get_modpath("mcl_core") and minetest
.get_modpath("mcl_mobitems") then
256 minetest
.register_craft({
257 output
= 'mcl_throwing:bow',
259 {'', 'mcl_core:stick', 'mcl_mobitems:string'},
260 {'mcl_core:stick', '', 'mcl_mobitems:string'},
261 {'', 'mcl_core:stick', 'mcl_mobitems:string'},
264 minetest
.register_craft({
265 output
= 'mcl_throwing:bow',
267 {'mcl_mobitems:string', 'mcl_core:stick', ''},
268 {'mcl_mobitems:string', '', 'mcl_core:stick'},
269 {'mcl_mobitems:string', 'mcl_core:stick', ''},
274 minetest
.register_craft({
276 recipe
= "mcl_throwing:bow",
280 -- Add entry aliases for the Help
281 if minetest
.get_modpath("doc") then
282 doc
.add_entry_alias("tools", "mcl_throwing:bow", "tools", "mcl_throwing:bow_0")
283 doc
.add_entry_alias("tools", "mcl_throwing:bow", "tools", "mcl_throwing:bow_1")
284 doc
.add_entry_alias("tools", "mcl_throwing:bow", "tools", "mcl_throwing:bow_2")