Fix bow in creative mode not having inf. ammo
[MineClone/MineClone2.git] / mods / ITEMS / mcl_bows / bow.lua
blob9f49eb158f995cb4e8e2b9f8686fb94f48e5db03
1 local S = minetest.get_translator("mcl_bows")
3 mcl_bows = {}
5 -- local arrows = {
6 -- ["mcl_bows:arrow"] = "mcl_bows:arrow_entity",
7 -- }
9 local GRAVITY = 9.81
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.
25 keys: player name
26 value:
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
31 local bow_load = {}
33 -- Another player table, this one stores the wield index of the bow being charged
34 local bow_index = {}
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")
38 if power == nil then
39 power = BOW_MAX_SPEED --19
40 end
41 if damage == nil then
42 damage = 3
43 end
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()
48 le._shooter = shooter
49 le._damage = damage
50 le._is_critical = is_critical
51 le._startpos = pos
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
56 end
57 obj:get_luaentity().node = shooter:get_inventory():get_stack("main", 1):get_name()
58 end
59 return obj
60 end
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
68 arrow_stack = it
69 arrow_stack_id = i
70 break
71 end
72 end
73 return arrow_stack, arrow_stack_id
74 end
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
81 if arrow_stack then
82 arrow_itemstring = arrow_stack:get_name()
83 else
84 arrow_itemstring = "mcl_bows:arrow"
85 end
86 else
87 if not arrow_stack then
88 return false
89 end
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)
94 end
95 if not arrow_itemstring then
96 return false
97 end
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)
103 return true
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 },
116 stack_max = 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
119 range = 1,
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")
132 list[place] = stack
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
146 reset_bows(player)
150 -- Bow in charging state
151 for level=0, 2 do
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 },
157 stack_max = 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()
165 return itemstack
166 end,
167 -- Prevent accidental interaction with itemframes and other nodes
168 on_place = function(itemstack)
169 return itemstack
170 end,
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
182 local speed, damage
183 local p_load = bow_load[player:get_player_name()]
184 local charge
185 -- Type sanity check
186 if type(p_load) == "number" then
187 charge = minetest.get_us_time() - p_load
188 else
189 -- In case something goes wrong ...
190 -- Just assume minimum charge.
191 charge = 0
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
200 -- Fully charged
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)
205 if r == 1 then
206 -- 20% chance for critical hit
207 damage = 10
208 is_critical = true
209 else
210 damage = 9
212 -- Partially charged
213 else
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)
228 end)
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
234 return
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()
247 else
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")
255 else
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)
261 else
262 reset_bow_state(player, true)
265 end)
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)
277 end)
279 minetest.register_on_joinplayer(function(player)
280 reset_bows(player)
281 end)
283 minetest.register_on_leaveplayer(function(player)
284 reset_bow_state(player, true)
285 end)
287 if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
288 minetest.register_craft({
289 output = 'mcl_bows:bow',
290 recipe = {
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',
298 recipe = {
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({
307 type = "fuel",
308 recipe = "mcl_bows:bow",
309 burntime = 15,
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")