Add snow particles on snowball impact
[MineClone/MineClone2.git] / mods / ITEMS / mcl_throwing / init.lua
blobe588363f46bedeeb2f79d4e9d5868950f73d4408
1 mcl_throwing = {}
3 local S = minetest.get_translator("mcl_throwing")
4 local mod_death_messages = minetest.get_modpath("mcl_death_messages")
5 local mod_fishing = minetest.get_modpath("mcl_fishing")
7 --
8 -- Snowballs and other throwable items
9 --
11 local GRAVITY = tonumber(minetest.settings:get("movement_gravity"))
13 local entity_mapping = {
14 ["mcl_throwing:flying_bobber"] = "mcl_throwing:flying_bobber_entity",
15 ["mcl_throwing:snowball"] = "mcl_throwing:snowball_entity",
16 ["mcl_throwing:egg"] = "mcl_throwing:egg_entity",
17 ["mcl_throwing:ender_pearl"] = "mcl_throwing:ender_pearl_entity",
20 local velocities = {
21 ["mcl_throwing:flying_bobber_entity"] = 5,
22 ["mcl_throwing:snowball_entity"] = 22,
23 ["mcl_throwing:egg_entity"] = 22,
24 ["mcl_throwing:ender_pearl_entity"] = 22,
27 mcl_throwing.throw = function(throw_item, pos, dir, velocity, thrower)
28 if velocity == nil then
29 velocity = velocities[throw_item]
30 end
31 if velocity == nil then
32 velocity = 22
33 end
35 local itemstring = ItemStack(throw_item):get_name()
36 local obj = minetest.add_entity(pos, entity_mapping[itemstring])
37 obj:set_velocity({x=dir.x*velocity, y=dir.y*velocity, z=dir.z*velocity})
38 obj:set_acceleration({x=dir.x*-3, y=-GRAVITY, z=dir.z*-3})
39 if thrower then
40 obj:get_luaentity()._thrower = thrower
41 end
42 return obj
43 end
45 -- Throw item
46 local player_throw_function = function(entity_name, velocity)
47 local func = function(item, player, pointed_thing)
48 local playerpos = player:get_pos()
49 local dir = player:get_look_dir()
50 local obj = mcl_throwing.throw(item, {x=playerpos.x, y=playerpos.y+1.5, z=playerpos.z}, dir, velocity, player:get_player_name())
51 if not minetest.settings:get_bool("creative_mode") then
52 item:take_item()
53 end
54 return item
55 end
56 return func
57 end
59 local dispense_function = function(stack, dispenserpos, droppos, dropnode, dropdir)
60 -- Launch throwable item
61 local shootpos = vector.add(dispenserpos, vector.multiply(dropdir, 0.51))
62 mcl_throwing.throw(stack:get_name(), shootpos, dropdir)
63 end
65 -- Staticdata handling because objects may want to be reloaded
66 local get_staticdata = function(self)
67 local thrower
68 -- Only save thrower if it's a player name
69 if type(self._thrower) == "string" then
70 thrower = self._thrower
71 end
72 local data = {
73 _lastpos = self._lastpos,
74 _thrower = thrower,
76 return minetest.serialize(data)
77 end
79 local on_activate = function(self, staticdata, dtime_s)
80 local data = minetest.deserialize(staticdata)
81 if data then
82 self._lastpos = data._lastpos
83 self._thrower = data._thrower
84 end
85 end
87 -- The snowball entity
88 local snowball_ENTITY={
89 physical = false,
90 timer=0,
91 textures = {"mcl_throwing_snowball.png"},
92 visual_size = {x=0.5, y=0.5},
93 collisionbox = {0,0,0,0,0,0},
94 pointable = false,
96 get_staticdata = get_staticdata,
97 on_activate = on_activate,
98 _thrower = nil,
100 _lastpos={},
102 local egg_ENTITY={
103 physical = false,
104 timer=0,
105 textures = {"mcl_throwing_egg.png"},
106 visual_size = {x=0.45, y=0.45},
107 collisionbox = {0,0,0,0,0,0},
108 pointable = false,
110 get_staticdata = get_staticdata,
111 on_activate = on_activate,
112 _thrower = nil,
114 _lastpos={},
116 -- Ender pearl entity
117 local pearl_ENTITY={
118 physical = false,
119 timer=0,
120 textures = {"mcl_throwing_ender_pearl.png"},
121 visual_size = {x=0.9, y=0.9},
122 collisionbox = {0,0,0,0,0,0},
123 pointable = false,
125 get_staticdata = get_staticdata,
126 on_activate = on_activate,
128 _lastpos={},
129 _thrower = nil, -- Player ObjectRef of the player who threw the ender pearl
132 local flying_bobber_ENTITY={
133 physical = false,
134 timer=0,
135 textures = {"mcl_fishing_bobber.png"}, --FIXME: Replace with correct texture.
136 visual_size = {x=0.5, y=0.5},
137 collisionbox = {0,0,0,0,0,0},
138 pointable = false,
140 get_staticdata = get_staticdata,
141 on_activate = on_activate,
143 _lastpos={},
144 _thrower = nil,
145 objtype="fishing",
148 local check_object_hit = function(self, pos, dmg)
149 for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
151 local entity = object:get_luaentity()
153 if entity
154 and entity.name ~= self.object:get_luaentity().name then
156 if object:is_player() and self._thrower ~= object:get_player_name() then
157 -- TODO: Deal knockback
158 self.object:remove()
159 return true
160 elseif entity._cmi_is_mob == true and (self._thrower ~= object) then
161 -- FIXME: Knockback is broken
162 object:punch(self.object, 1.0, {
163 full_punch_interval = 1.0,
164 damage_groups = dmg,
165 }, nil)
166 return true
170 return false
173 local snowball_particles = function(pos, vel)
174 local vel = vector.normalize(vector.multiply(vel, -1))
175 minetest.add_particlespawner({
176 amount = 20,
177 time = 0.001,
178 minpos = pos,
179 maxpos = pos,
180 minvel = vector.add({x=-2, y=3, z=-2}, vel),
181 maxvel = vector.add({x=2, y=5, z=2}, vel),
182 minacc = {x=0, y=-9.81, z=0},
183 maxacc = {x=0, y=-9.81, z=0},
184 minexptime = 1,
185 maxexptime = 3,
186 minsize = 0.7,
187 maxsize = 0.7,
188 collisiondetection = true,
189 collision_removal = true,
190 object_collision = false,
191 texture = "weather_pack_snow_snowflake"..math.random(1,2)..".png",
195 -- Snowball on_step()--> called when snowball is moving.
196 local snowball_on_step = function(self, dtime)
197 self.timer=self.timer+dtime
198 local pos = self.object:get_pos()
199 local node = minetest.get_node(pos)
200 local def = minetest.registered_nodes[node.name]
202 -- Destroy when hitting a solid node
203 if self._lastpos.x~=nil then
204 if (def and def.walkable) or not def then
205 minetest.sound_play("mcl_throwing_snowball_impact_hard", { pos = self.object:get_pos(), max_hear_distance=16, gain=0.7 }, true)
206 snowball_particles(self._lastpos, self.object:get_velocity())
207 self.object:remove()
208 return
212 if check_object_hit(self, pos, {snowball_vulnerable = 3}) then
213 minetest.sound_play("mcl_throwing_snowball_impact_soft", { pos = self.object:get_pos(), max_hear_distance=16, gain=0.7 }, true)
214 snowball_particles(pos, self.object:get_velocity())
215 self.object:remove()
216 return
219 self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set _lastpos-->Node will be added at last pos outside the node
222 -- Movement function of egg
223 local egg_on_step = function(self, dtime)
224 self.timer=self.timer+dtime
225 local pos = self.object:get_pos()
226 local node = minetest.get_node(pos)
227 local def = minetest.registered_nodes[node.name]
229 -- Destroy when hitting a solid node with chance to spawn chicks
230 if self._lastpos.x~=nil then
231 if (def and def.walkable) or not def then
232 -- 1/8 chance to spawn a chick
233 -- FIXME: Chicks have a quite good chance to spawn in walls
234 local r = math.random(1,8)
236 -- Turn given object into a child
237 local make_child= function(object)
238 local ent = object:get_luaentity()
239 object:set_properties({
240 visual_size = { x = ent.base_size.x/2, y = ent.base_size.y/2 },
241 collisionbox = {
242 ent.base_colbox[1]/2,
243 ent.base_colbox[2]/2,
244 ent.base_colbox[3]/2,
245 ent.base_colbox[4]/2,
246 ent.base_colbox[5]/2,
247 ent.base_colbox[6]/2,
250 ent.child = true
252 if r == 1 then
253 make_child(minetest.add_entity(self._lastpos, "mobs_mc:chicken"))
255 -- BONUS ROUND: 1/32 chance to spawn 3 additional chicks
256 local r = math.random(1,32)
257 if r == 1 then
258 local offsets = {
259 { x=0.7, y=0, z=0 },
260 { x=-0.7, y=0, z=-0.7 },
261 { x=-0.7, y=0, z=0.7 },
263 for o=1, 3 do
264 local pos = vector.add(self._lastpos, offsets[o])
265 make_child(minetest.add_entity(pos, "mobs_mc:chicken"))
269 minetest.sound_play("mcl_throwing_egg_impact", { pos = self.object:get_pos(), max_hear_distance=10, gain=0.5 }, true)
270 self.object:remove()
271 return
275 -- Destroy when hitting a mob or player (no chick spawning)
276 if check_object_hit(self, pos) then
277 minetest.sound_play("mcl_throwing_egg_impact", { pos = self.object:get_pos(), max_hear_distance=10, gain=0.5 }, true)
278 self.object:remove()
279 return
282 self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set lastpos-->Node will be added at last pos outside the node
285 -- Movement function of ender pearl
286 local pearl_on_step = function(self, dtime)
287 self.timer=self.timer+dtime
288 local pos = self.object:get_pos()
289 pos.y = math.floor(pos.y)
290 local node = minetest.get_node(pos)
291 local nn = node.name
292 local def = minetest.registered_nodes[node.name]
294 -- Destroy when hitting a solid node
295 if self._lastpos.x~=nil then
296 local walkable = (def and def.walkable)
298 -- No teleport for hitting ignore for now. Otherwise the player could get stuck.
299 -- FIXME: This also means the player loses an ender pearl for throwing into unloaded areas
300 if node.name == "ignore" then
301 self.object:remove()
302 -- Activate when hitting a solid node or a plant
303 elseif walkable or nn == "mcl_core:vine" or nn == "mcl_core:deadbush" or minetest.get_item_group(nn, "flower") ~= 0 or minetest.get_item_group(nn, "sapling") ~= 0 or minetest.get_item_group(nn, "plant") ~= 0 or minetest.get_item_group(nn, "mushroom") ~= 0 or not def then
304 local player = minetest.get_player_by_name(self._thrower)
305 if player then
306 -- Teleport and hurt player
308 -- First determine good teleport position
309 local dir = {x=0, y=0, z=0}
311 local v = self.object:get_velocity()
312 if walkable then
313 local vc = table.copy(v) -- vector for calculating
314 -- Node is walkable, we have to find a place somewhere outside of that node
315 vc = vector.normalize(vc)
317 -- Zero-out the two axes with a lower absolute value than
318 -- the axis with the strongest force
319 local lv, ld
320 lv, ld = math.abs(vc.y), "y"
321 if math.abs(vc.x) > lv then
322 lv, ld = math.abs(vc.x), "x"
324 if math.abs(vc.z) > lv then
325 lv, ld = math.abs(vc.z), "z"
327 if ld ~= "x" then vc.x = 0 end
328 if ld ~= "y" then vc.y = 0 end
329 if ld ~= "z" then vc.z = 0 end
331 -- Final tweaks to the teleporting pos, based on direction
332 -- Impact from the side
333 dir.x = vc.x * -1
334 dir.z = vc.z * -1
336 -- Special case: top or bottom of node
337 if vc.y > 0 then
338 -- We need more space when impact is from below
339 dir.y = -2.3
340 elseif vc.y < 0 then
341 -- Standing on top
342 dir.y = 0.5
345 -- If node was not walkable, no modification to pos is made.
347 -- Final teleportation position
348 local telepos = vector.add(pos, dir)
349 local telenode = minetest.get_node(telepos)
351 --[[ It may be possible that telepos is walkable due to the algorithm.
352 Especially when the ender pearl is faster horizontally than vertical.
353 This applies final fixing, just to be sure we're not in a walkable node ]]
354 if not minetest.registered_nodes[telenode.name] or minetest.registered_nodes[telenode.name].walkable then
355 if v.y < 0 then
356 telepos.y = telepos.y + 0.5
357 else
358 telepos.y = telepos.y - 2.3
362 local oldpos = player:get_pos()
363 -- Teleport and hurt player
364 player:set_pos(telepos)
365 player:set_hp(player:get_hp() - 5, { type = "fall", from = "mod" })
367 -- 5% chance to spawn endermite at the player's origin
368 local r = math.random(1,20)
369 if r == 1 then
370 minetest.add_entity(oldpos, "mobs_mc:endermite")
374 self.object:remove()
375 return
378 self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set lastpos-->Node will be added at last pos outside the node
381 -- Movement function of flying bobber
382 local flying_bobber_on_step = function(self, dtime)
383 self.timer=self.timer+dtime
384 local pos = self.object:get_pos()
385 local node = minetest.get_node(pos)
386 local def = minetest.registered_nodes[node.name]
387 --local player = minetest.get_player_by_name(self._thrower)
389 -- Destroy when hitting a solid node
390 if self._lastpos.x~=nil then
391 if (def and (def.walkable or def.liquidtype == "flowing" or def.liquidtype == "source")) or not def then
392 local make_child= function(object)
393 local ent = object:get_luaentity()
394 ent.player = self._thrower
395 ent.child = true
397 make_child(minetest.add_entity(self._lastpos, "mcl_fishing:bobber_entity"))
398 self.object:remove()
399 return
402 self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set lastpos-->Node will be added at last pos outside the node
405 snowball_ENTITY.on_step = snowball_on_step
406 egg_ENTITY.on_step = egg_on_step
407 pearl_ENTITY.on_step = pearl_on_step
408 flying_bobber_ENTITY.on_step = flying_bobber_on_step
410 minetest.register_entity("mcl_throwing:snowball_entity", snowball_ENTITY)
411 minetest.register_entity("mcl_throwing:egg_entity", egg_ENTITY)
412 minetest.register_entity("mcl_throwing:ender_pearl_entity", pearl_ENTITY)
413 minetest.register_entity("mcl_throwing:flying_bobber_entity", flying_bobber_ENTITY)
415 local how_to_throw = S("Use the punch key to throw.")
417 -- Snowball
418 minetest.register_craftitem("mcl_throwing:snowball", {
419 description = S("Snowball"),
420 _tt_help = S("Throwable"),
421 _doc_items_longdesc = S("Snowballs can be thrown or launched from a dispenser for fun. Hitting something with a snowball does nothing."),
422 _doc_items_usagehelp = how_to_throw,
423 inventory_image = "mcl_throwing_snowball.png",
424 stack_max = 16,
425 groups = { weapon_ranged = 1 },
426 on_use = player_throw_function("mcl_throwing:snowball_entity"),
427 _on_dispense = dispense_function,
430 -- Egg
431 minetest.register_craftitem("mcl_throwing:egg", {
432 description = S("Egg"),
433 _tt_help = S("Throwable").."\n"..S("Chance to hatch chicks when broken"),
434 _doc_items_longdesc = S("Eggs can be thrown or launched from a dispenser and breaks on impact. There is a small chance that 1 or even 4 chicks will pop out of the egg."),
435 _doc_items_usagehelp = how_to_throw,
436 inventory_image = "mcl_throwing_egg.png",
437 stack_max = 16,
438 on_use = player_throw_function("mcl_throwing:egg_entity"),
439 _on_dispense = dispense_function,
440 groups = { craftitem = 1 },
443 -- Ender Pearl
444 minetest.register_craftitem("mcl_throwing:ender_pearl", {
445 description = S("Ender Pearl"),
446 _tt_help = S("Throwable").."\n"..minetest.colorize("#FFFF00", S("Teleports you on impact for cost of 5 HP")),
447 _doc_items_longdesc = S("An ender pearl is an item which can be used for teleportation at the cost of health. It can be thrown and teleport the thrower to its impact location when it hits a solid block or a plant. Each teleportation hurts the user by 5 hit points."),
448 _doc_items_usagehelp = how_to_throw,
449 wield_image = "mcl_throwing_ender_pearl.png",
450 inventory_image = "mcl_throwing_ender_pearl.png",
451 stack_max = 16,
452 on_use = player_throw_function("mcl_throwing:ender_pearl_entity"),
453 groups = { transport = 1 },