Drop charging bows as uncharged bow
[MineClone/MineClone2.git] / mods / ITEMS / mcl_throwing / bow.lua
blob474a4bd3a22f732c78b50b1a3548d47b2cbbbb15
1 mcl_throwing = {}
3 local arrows = {
4 ["mcl_throwing:arrow"] = "mcl_throwing:arrow_entity",
7 local GRAVITY = 9.81
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.
19 keys: player name
20 value:
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
25 local bow_load = {}
27 -- Another player table, this one stores the wield index of the bow being charged
28 local bow_index = {}
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])
32 if power == nil then
33 power = 19
34 end
35 if damage == nil then
36 damage = 3
37 end
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()
42 le._shooter = shooter
43 le._damage = damage
44 le._startpos = pos
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
49 end
50 obj:get_luaentity().node = shooter:get_inventory():get_stack("main", 1):get_name()
51 end
52 return obj
53 end
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
61 arrow_stack = it
62 arrow_stack_id = i
63 break
64 end
65 end
66 return arrow_stack, arrow_stack_id
67 end
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
74 return false
75 end
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)
80 end
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"
87 end
88 mcl_throwing.shoot_arrow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, dir, yaw, player, power, damage)
89 return true
90 end
92 -- Bow item, uncharged state
93 minetest.register_tool("mcl_throwing:bow", {
94 description = "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",
100 stack_max = 1,
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
103 range = 1,
104 -- Trick to disable digging as well
105 on_use = function() end,
106 groups = {weapon=1,weapon_ranged=1},
109 -- Bow in charging state
110 for level=0, 2 do
111 minetest.register_tool("mcl_throwing:bow_"..level, {
112 description = "Bow",
113 _doc_items_create_entry = false,
114 inventory_image = "mcl_throwing_bow_"..level..".png",
115 stack_max = 1,
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()
124 return itemstack
125 end,
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")
136 list[place] = stack
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
149 local speed, damage
150 local p_load = bow_load[player:get_player_name()]
151 local charge
152 -- Type sanity check
153 if type(p_load) == "number" then
154 charge = minetest.get_us_time() - p_load
155 else
156 -- In case something goes wrong ...
157 -- Just assume minimum charge.
158 charge = 0
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
167 -- Fully charged
168 if charge >= BOW_CHARGE_TIME_FULL then
169 speed = BOW_MAX_SPEED
170 local r = math.random(1,5)
171 if r == 1 then
172 -- 20% chance for critical hit
173 damage = 10
174 else
175 damage = 9
177 -- Partially charged
178 else
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)
191 reset_bows(player)
192 bow_load[player:get_player_name()] = nil
193 bow_index[player:get_player_name()] = nil
195 end)
197 controls.register_on_hold(function(player, key, time)
198 if key ~= "RMB" then
199 return
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()
209 else
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")
217 else
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)
223 else
224 reset_bows(player)
225 bow_load[name] = nil
226 bow_index[name] = nil
229 end)
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
238 reset_bows(player)
239 bow_load[name] = nil
240 bow_index[name] = nil
243 end)
245 minetest.register_on_joinplayer(function(player)
246 reset_bows(player)
247 end)
249 minetest.register_on_leaveplayer(function(player)
250 reset_bows(player)
251 bow_load[player:get_player_name()] = nil
252 bow_index[player:get_player_name()] = nil
253 end)
255 if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
256 minetest.register_craft({
257 output = 'mcl_throwing:bow',
258 recipe = {
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',
266 recipe = {
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({
275 type = "fuel",
276 recipe = "mcl_throwing:bow",
277 burntime = 15,
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")