1 local S
= minetest
.get_translator("mcl_bows")
6 -- ["mcl_bows:arrow"] = "mcl_bows:arrow_entity",
10 local BOW_DURABILITY
= 385
12 -- Charging time in microseconds
13 local BOW_CHARGE_TIME_HALF
= 200000 -- bow level 1
14 local BOW_CHARGE_TIME_FULL
= 500000 -- bow level 2 (full charge)
16 -- Factor to multiply with player speed while player uses bow
17 -- This emulates the sneak speed.
18 local PLAYER_USE_BOW_SPEED
= tonumber(minetest
.settings
:get("movement_speed_crouch")) / tonumber(minetest
.settings
:get("movement_speed_walk"))
20 -- TODO: Use Minecraft speed (ca. 53 m/s)
21 -- Currently nerfed because at full speed the arrow would easily get out of the range of the loaded map.
22 local BOW_MAX_SPEED
= 40
24 --[[ Store the charging state of each player.
27 nil = not charging or player not existing
28 number: currently charging, the number is the time from minetest.get_us_time
29 in which the charging has started
33 -- Another player table, this one stores the wield index of the bow being charged
36 mcl_bows
.shoot_arrow
= function(arrow_item
, pos
, dir
, yaw
, shooter
, power
, damage
, is_critical
)
37 local obj
= minetest
.add_entity({x
=pos
.x
,y
=pos
.y
,z
=pos
.z
}, arrow_item
.."_entity")
39 power
= BOW_MAX_SPEED
--19
44 obj
:set_velocity({x
=dir
.x
*power
, y
=dir
.y
*power
, z
=dir
.z
*power
})
45 obj
:set_acceleration({x
=0, y
=-GRAVITY
, z
=0})
46 obj
:set_yaw(yaw
-math
.pi
/2)
47 local le
= obj
:get_luaentity()
50 le
._is_critical
= is_critical
52 minetest
.sound_play("mcl_bows_bow_shoot", {pos
=pos
}, true)
53 if shooter
~= nil and shooter
:is_player() then
54 if obj
:get_luaentity().player
== "" then
55 obj
:get_luaentity().player
= shooter
57 obj
:get_luaentity().node
= shooter
:get_inventory():get_stack("main", 1):get_name()
62 local get_arrow
= function(player
)
63 local inv
= player
:get_inventory()
64 local arrow_stack
, arrow_stack_id
65 for i
=1, inv
:get_size("main") do
66 local it
= inv
:get_stack("main", i
)
67 if not it
:is_empty() and minetest
.get_item_group(it
:get_name(), "ammo_bow") ~= 0 then
73 return arrow_stack
, arrow_stack_id
76 local player_shoot_arrow
= function(itemstack
, player
, power
, damage
, is_critical
)
77 local arrow_stack
, arrow_stack_id
= get_arrow(player
)
78 local arrow_itemstring
80 if minetest
.is_creative_enabled(player
:get_player_name()) then
82 arrow_itemstring
= arrow_stack
:get_name()
84 arrow_itemstring
= "mcl_bows:arrow"
87 if not arrow_stack
then
90 arrow_itemstring
= arrow_stack
:get_name()
91 arrow_stack
:take_item()
92 local inv
= player
:get_inventory()
93 inv
:set_stack("main", arrow_stack_id
, arrow_stack
)
95 if not arrow_itemstring
then
98 local playerpos
= player
:get_pos()
99 local dir
= player
:get_look_dir()
100 local yaw
= player
:get_look_horizontal()
102 mcl_bows
.shoot_arrow(arrow_itemstring
, {x
=playerpos
.x
,y
=playerpos
.y
+1.5,z
=playerpos
.z
}, dir
, yaw
, player
, power
, damage
, is_critical
)
106 -- Bow item, uncharged state
107 minetest
.register_tool("mcl_bows:bow", {
108 description
= S("Bow"),
109 _tt_help
= S("Launches arrows"),
110 _doc_items_longdesc
= S("Bows are ranged weapons to shoot arrows at your foes.").."\n"..
111 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."),
112 _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."),
113 _doc_items_durability
= BOW_DURABILITY
,
114 inventory_image
= "mcl_bows_bow.png",
115 wield_scale
= { x
= 1.8, y
= 1.8, z
= 1 },
117 -- Trick to disable melee damage to entities.
118 -- Range not set to 0 (unlike the others) so it can be placed into item frames
120 -- Trick to disable digging as well
121 on_use
= function() end,
122 groups
= {weapon
=1,weapon_ranged
=1},
125 -- Iterates through player inventory and resets all the bows in "charging" state back to their original stage
126 local reset_bows
= function(player
)
127 local inv
= player
:get_inventory()
128 local list
= inv
:get_list("main")
129 for place
, stack
in pairs(list
) do
130 if stack
:get_name()=="mcl_bows:bow_0" or stack
:get_name()=="mcl_bows:bow_1" or stack
:get_name()=="mcl_bows:bow_2" then
131 stack
:set_name("mcl_bows:bow")
135 inv
:set_list("main", list
)
138 -- Resets the bow charging state and player speed. To be used when the player is no longer charging the bow
139 local reset_bow_state
= function(player
, also_reset_bows
)
140 bow_load
[player
:get_player_name()] = nil
141 bow_index
[player
:get_player_name()] = nil
142 if minetest
.get_modpath("playerphysics") then
143 playerphysics
.remove_physics_factor(player
, "speed", "mcl_bows:use_bow")
145 if also_reset_bows
then
150 -- Bow in charging state
152 minetest
.register_tool("mcl_bows:bow_"..level
, {
153 description
= S("Bow"),
154 _doc_items_create_entry
= false,
155 inventory_image
= "mcl_bows_bow_"..level
..".png",
156 wield_scale
= { x
= 1.8, y
= 1.8, z
= 1 },
158 range
= 0, -- Pointing range to 0 to prevent punching with bow :D
159 groups
= {not_in_creative_inventory
=1, not_in_craft_guide
=1},
160 on_drop
= function(itemstack
, dropper
, pos
)
161 reset_bow_state(dropper
)
162 itemstack
:set_name("mcl_bows:bow")
163 minetest
.item_drop(itemstack
, dropper
, pos
)
164 itemstack
:take_item()
167 -- Prevent accidental interaction with itemframes and other nodes
168 on_place
= function(itemstack
)
175 controls
.register_on_release(function(player
, key
, time
)
176 if key
~="RMB" then return end
177 local inv
= minetest
.get_inventory({type="player", name
=player
:get_player_name()})
178 local wielditem
= player
:get_wielded_item()
179 if (wielditem
:get_name()=="mcl_bows:bow_0" or wielditem
:get_name()=="mcl_bows:bow_1" or wielditem
:get_name()=="mcl_bows:bow_2") then
180 local has_shot
= false
183 local p_load
= bow_load
[player
:get_player_name()]
186 if type(p_load
) == "number" then
187 charge
= minetest
.get_us_time() - p_load
189 -- In case something goes wrong ...
190 -- Just assume minimum charge.
192 minetest
.log("warning", "[mcl_bows] Player "..player
:get_player_name().." fires arrow with non-numeric bow_load!")
194 charge
= math
.max(math
.min(charge
, BOW_CHARGE_TIME_FULL
), 0)
196 local charge_ratio
= charge
/ BOW_CHARGE_TIME_FULL
197 charge_ratio
= math
.max(math
.min(charge_ratio
, 1), 0)
199 -- Calculate damage and speed
201 local is_critical
= false
202 if charge
>= BOW_CHARGE_TIME_FULL
then
203 speed
= BOW_MAX_SPEED
204 local r
= math
.random(1,5)
206 -- 20% chance for critical hit
214 -- Linear speed and damage increase
215 speed
= math
.max(4, BOW_MAX_SPEED
* charge_ratio
)
216 damage
= math
.max(1, math
.floor(9 * charge_ratio
))
219 has_shot
= player_shoot_arrow(wielditem
, player
, speed
, damage
, is_critical
)
221 wielditem
:set_name("mcl_bows:bow")
222 if has_shot
and not minetest
.is_creative_enabled(player
:get_player_name()) then
223 wielditem
:add_wear(65535/BOW_DURABILITY
)
225 player
:set_wielded_item(wielditem
)
226 reset_bow_state(player
, true)
230 controls
.register_on_hold(function(player
, key
, time
)
231 local name
= player
:get_player_name()
232 local creative
= minetest
.is_creative_enabled(name
)
233 if key
~= "RMB" or not (creative
or get_arrow(player
)) then
236 local inv
= minetest
.get_inventory({type="player", name
=name
})
237 local wielditem
= player
:get_wielded_item()
238 if bow_load
[name
] == nil and wielditem
:get_name()=="mcl_bows:bow" and (creative
or get_arrow(player
)) then
239 wielditem
:set_name("mcl_bows:bow_0")
240 player
:set_wielded_item(wielditem
)
241 if minetest
.get_modpath("playerphysics") then
242 -- Slow player down when using bow
243 playerphysics
.add_physics_factor(player
, "speed", "mcl_bows:use_bow", PLAYER_USE_BOW_SPEED
)
245 bow_load
[name
] = minetest
.get_us_time()
246 bow_index
[name
] = player
:get_wield_index()
248 if player
:get_wield_index() == bow_index
[name
] then
249 if type(bow_load
[name
]) == "number" then
250 if wielditem
:get_name() == "mcl_bows:bow_0" and minetest
.get_us_time() - bow_load
[name
] >= BOW_CHARGE_TIME_HALF
then
251 wielditem
:set_name("mcl_bows:bow_1")
252 elseif wielditem
:get_name() == "mcl_bows:bow_1" and minetest
.get_us_time() - bow_load
[name
] >= BOW_CHARGE_TIME_FULL
then
253 wielditem
:set_name("mcl_bows:bow_2")
256 if wielditem
:get_name() == "mcl_bows:bow_0" or wielditem
:get_name() == "mcl_bows:bow_1" or wielditem
:get_name() == "mcl_bows:bow_2" then
257 wielditem
:set_name("mcl_bows:bow")
260 player
:set_wielded_item(wielditem
)
262 reset_bow_state(player
, true)
267 minetest
.register_globalstep(function(dtime
)
268 for _
, player
in pairs(minetest
.get_connected_players()) do
269 local name
= player
:get_player_name()
270 local wielditem
= player
:get_wielded_item()
271 local wieldindex
= player
:get_wield_index()
272 local controls
= player
:get_player_control()
273 if type(bow_load
[name
]) == "number" and ((wielditem
:get_name()~="mcl_bows:bow_0" and wielditem
:get_name()~="mcl_bows:bow_1" and wielditem
:get_name()~="mcl_bows:bow_2") or wieldindex
~= bow_index
[name
]) then
274 reset_bow_state(player
, true)
279 minetest
.register_on_joinplayer(function(player
)
283 minetest
.register_on_leaveplayer(function(player
)
284 reset_bow_state(player
, true)
287 if minetest
.get_modpath("mcl_core") and minetest
.get_modpath("mcl_mobitems") then
288 minetest
.register_craft({
289 output
= 'mcl_bows:bow',
291 {'', 'mcl_core:stick', 'mcl_mobitems:string'},
292 {'mcl_core:stick', '', 'mcl_mobitems:string'},
293 {'', 'mcl_core:stick', 'mcl_mobitems:string'},
296 minetest
.register_craft({
297 output
= 'mcl_bows:bow',
299 {'mcl_mobitems:string', 'mcl_core:stick', ''},
300 {'mcl_mobitems:string', '', 'mcl_core:stick'},
301 {'mcl_mobitems:string', 'mcl_core:stick', ''},
306 minetest
.register_craft({
308 recipe
= "mcl_bows:bow",
312 -- Add entry aliases for the Help
313 if minetest
.get_modpath("doc") then
314 doc
.add_entry_alias("tools", "mcl_bows:bow", "tools", "mcl_bows:bow_0")
315 doc
.add_entry_alias("tools", "mcl_bows:bow", "tools", "mcl_bows:bow_1")
316 doc
.add_entry_alias("tools", "mcl_bows:bow", "tools", "mcl_bows:bow_2")