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")
8 -- Snowballs and other throwable items
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",
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
]
31 if velocity
== nil then
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})
40 obj
:get_luaentity()._thrower
= thrower
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
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
)
65 -- Staticdata handling because objects may want to be reloaded
66 local get_staticdata
= function(self
)
68 -- Only save thrower if it's a player name
69 if type(self
._thrower
) == "string" then
70 thrower
= self
._thrower
73 _lastpos
= self
._lastpos
,
76 return minetest
.serialize(data
)
79 local on_activate
= function(self
, staticdata
, dtime_s
)
80 local data
= minetest
.deserialize(staticdata
)
82 self
._lastpos
= data
._lastpos
83 self
._thrower
= data
._thrower
87 -- The snowball entity
88 local snowball_ENTITY
={
91 textures
= {"mcl_throwing_snowball.png"},
92 visual_size
= {x
=0.5, y
=0.5},
93 collisionbox
= {0,0,0,0,0,0},
96 get_staticdata
= get_staticdata
,
97 on_activate
= on_activate
,
105 textures
= {"mcl_throwing_egg.png"},
106 visual_size
= {x
=0.45, y
=0.45},
107 collisionbox
= {0,0,0,0,0,0},
110 get_staticdata
= get_staticdata
,
111 on_activate
= on_activate
,
116 -- Ender pearl entity
120 textures
= {"mcl_throwing_ender_pearl.png"},
121 visual_size
= {x
=0.9, y
=0.9},
122 collisionbox
= {0,0,0,0,0,0},
125 get_staticdata
= get_staticdata
,
126 on_activate
= on_activate
,
129 _thrower
= nil, -- Player ObjectRef of the player who threw the ender pearl
132 local flying_bobber_ENTITY
={
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},
140 get_staticdata
= get_staticdata
,
141 on_activate
= on_activate
,
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()
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
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,
173 local snowball_particles
= function(pos
, vel
)
174 local vel
= vector
.normalize(vector
.multiply(vel
, -1))
175 minetest
.add_particlespawner({
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},
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())
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())
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 },
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,
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)
260 { x
=-0.7, y
=0, z
=-0.7 },
261 { x
=-0.7, y
=0, z
=0.7 },
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)
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)
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
)
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
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
)
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()
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
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
336 -- Special case: top or bottom of node
338 -- We need more space when impact is from below
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
356 telepos
.y
= telepos
.y
+ 0.5
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)
370 minetest
.add_entity(oldpos
, "mobs_mc:endermite")
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
397 make_child(minetest
.add_entity(self
._lastpos
, "mcl_fishing:bobber_entity"))
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.")
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",
425 groups
= { weapon_ranged
= 1 },
426 on_use
= player_throw_function("mcl_throwing:snowball_entity"),
427 _on_dispense
= dispense_function
,
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",
438 on_use
= player_throw_function("mcl_throwing:egg_entity"),
439 _on_dispense
= dispense_function
,
440 groups
= { craftitem
= 1 },
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",
452 on_use
= player_throw_function("mcl_throwing:ender_pearl_entity"),
453 groups
= { transport
= 1 },