Compare commits
76 Commits
3d01f5ad03
...
22980c2951
Author | SHA1 | Date |
---|---|---|
teknomunk | 22980c2951 | |
teknomunk | c5e057cb6b | |
teknomunk | 4893f1711a | |
teknomunk | ea229e8dbc | |
teknomunk | baa5fb5952 | |
teknomunk | 53a34747da | |
teknomunk | b8a115753e | |
teknomunk | 549693ff97 | |
teknomunk | 4f9faffcb3 | |
teknomunk | d878c57531 | |
teknomunk | d0dd880c51 | |
teknomunk | cf3354ef79 | |
the-real-herowl | 6af77081a7 | |
the-real-herowl | 7bb60dfeeb | |
the-real-herowl | ca095395a9 | |
the-real-herowl | 12f2147372 | |
teknomunk | b9784fdba1 | |
teknomunk | a666739d80 | |
teknomunk | 08646b1d13 | |
teknomunk | 1c4cf2f17c | |
teknomunk | 7bea6215d0 | |
teknomunk | 348b13764f | |
teknomunk | 5f8c378002 | |
teknomunk | 65e25391db | |
teknomunk | ead59d4ead | |
teknomunk | 049306ecc6 | |
teknomunk | cd6da3dcf2 | |
teknomunk | 7bdfcdc9dd | |
teknomunk | aebafd7af0 | |
teknomunk | 4a71d993d3 | |
teknomunk | aa5a696341 | |
teknomunk | 8d570958d4 | |
teknomunk | 74882d2bbf | |
teknomunk | b904204c78 | |
teknomunk | 1cceb7378d | |
teknomunk | c8512d29bd | |
teknomunk | 39e93b7a54 | |
teknomunk | d187b3bf55 | |
teknomunk | ed373e9f37 | |
teknomunk | 9f9c590c8d | |
teknomunk | ca63f1ce58 | |
teknomunk | 52946b3a26 | |
teknomunk | 6d838e7fbd | |
teknomunk | 36d20e6f48 | |
teknomunk | adb6e22ce5 | |
teknomunk | 29bfd6ce21 | |
teknomunk | e212f3dd08 | |
teknomunk | 0b500462ab | |
teknomunk | 55c8f4cb84 | |
teknomunk | 227bbebe3b | |
teknomunk | 26b858c463 | |
teknomunk | ca8f7eb700 | |
teknomunk | eca43272bb | |
teknomunk | a7342eb024 | |
teknomunk | 5e024bf57f | |
teknomunk | 05be7b0922 | |
teknomunk | 1b824cebb3 | |
teknomunk | 436033eba7 | |
teknomunk | 52d2c4a26e | |
teknomunk | 5bd1bcd425 | |
teknomunk | 16ddcf99eb | |
teknomunk | 3b0690dcc3 | |
teknomunk | 52381a1f58 | |
teknomunk | 149e69452e | |
teknomunk | f55f647f2e | |
teknomunk | 1ee97a2e73 | |
teknomunk | 6eab0433ab | |
teknomunk | 1d153f4f2a | |
teknomunk | d35246135b | |
teknomunk | 0140d1b81b | |
teknomunk | f43bbdd78b | |
teknomunk | d32d59cde2 | |
teknomunk | 01d3159dd3 | |
teknomunk | b1b3b082a1 | |
teknomunk | a1ebc2c040 | |
teknomunk | 1c07325c76 |
|
@ -1,6 +1,9 @@
|
|||
mcl_util = {}
|
||||
|
||||
dofile(minetest.get_modpath(minetest.get_current_modname()).."/roman_numerals.lua")
|
||||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
dofile(modpath.."/roman_numerals.lua")
|
||||
dofile(modpath.."/nodes.lua")
|
||||
|
||||
-- Updates all values in t using values from to*.
|
||||
function table.update(t, ...)
|
||||
|
@ -126,24 +129,6 @@ function mcl_util.validate_vector (vect)
|
|||
return false
|
||||
end
|
||||
|
||||
-- Minetest 5.3.0 or less can only measure the light level. This came in at 5.4
|
||||
-- This function has been known to fail in multiple places so the error handling is added increase safety and improve
|
||||
-- debugging. See:
|
||||
-- https://git.minetest.land/VoxeLibre/VoxeLibre/issues/1392
|
||||
function mcl_util.get_natural_light (pos, time)
|
||||
local status, retVal = pcall(minetest.get_natural_light, pos, time)
|
||||
if status then
|
||||
return retVal
|
||||
else
|
||||
minetest.log("warning", "Failed to get natural light at pos: " .. dump(pos) .. ", time: " .. dump(time))
|
||||
if (pos) then
|
||||
local node = minetest.get_node(pos)
|
||||
minetest.log("warning", "Node at pos: " .. dump(node.name))
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function mcl_util.file_exists(name)
|
||||
if type(name) ~= "string" then return end
|
||||
local f = io.open(name)
|
||||
|
@ -154,119 +139,6 @@ function mcl_util.file_exists(name)
|
|||
return true
|
||||
end
|
||||
|
||||
-- Based on minetest.rotate_and_place
|
||||
|
||||
--[[
|
||||
Attempt to predict the desired orientation of the pillar-like node
|
||||
defined by `itemstack`, and place it accordingly in one of 3 possible
|
||||
orientations (X, Y or Z).
|
||||
|
||||
Stacks are handled normally if the `infinitestacks`
|
||||
field is false or omitted (else, the itemstack is not changed).
|
||||
* `invert_wall`: if `true`, place wall-orientation on the ground and ground-
|
||||
orientation on wall
|
||||
|
||||
This function is a simplified version of minetest.rotate_and_place.
|
||||
The Minetest function is seen as inappropriate because this includes mirror
|
||||
images of possible orientations, causing problems with pillar shadings.
|
||||
]]
|
||||
function mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, infinitestacks, invert_wall)
|
||||
local unode = minetest.get_node_or_nil(pointed_thing.under)
|
||||
if not unode then
|
||||
return
|
||||
end
|
||||
local undef = minetest.registered_nodes[unode.name]
|
||||
if undef and undef.on_rightclick and not invert_wall then
|
||||
undef.on_rightclick(pointed_thing.under, unode, placer,
|
||||
itemstack, pointed_thing)
|
||||
return
|
||||
end
|
||||
local fdir = minetest.dir_to_facedir(placer:get_look_dir())
|
||||
local wield_name = itemstack:get_name()
|
||||
|
||||
local above = pointed_thing.above
|
||||
local under = pointed_thing.under
|
||||
local is_x = (above.x ~= under.x)
|
||||
local is_y = (above.y ~= under.y)
|
||||
local is_z = (above.z ~= under.z)
|
||||
|
||||
local anode = minetest.get_node_or_nil(above)
|
||||
if not anode then
|
||||
return
|
||||
end
|
||||
local pos = pointed_thing.above
|
||||
local node = anode
|
||||
|
||||
if undef and undef.buildable_to then
|
||||
pos = pointed_thing.under
|
||||
node = unode
|
||||
end
|
||||
|
||||
if minetest.is_protected(pos, placer:get_player_name()) then
|
||||
minetest.record_protection_violation(pos, placer:get_player_name())
|
||||
return
|
||||
end
|
||||
|
||||
local ndef = minetest.registered_nodes[node.name]
|
||||
if not ndef or not ndef.buildable_to then
|
||||
return
|
||||
end
|
||||
|
||||
local p2
|
||||
if is_y then
|
||||
p2 = 0
|
||||
elseif is_x then
|
||||
p2 = 12
|
||||
elseif is_z then
|
||||
p2 = 6
|
||||
end
|
||||
minetest.set_node(pos, {name = wield_name, param2 = p2})
|
||||
|
||||
if not infinitestacks then
|
||||
itemstack:take_item()
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
-- Wrapper of above function for use as `on_place` callback (Recommended).
|
||||
-- Similar to minetest.rotate_node.
|
||||
function mcl_util.rotate_axis(itemstack, placer, pointed_thing)
|
||||
mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing,
|
||||
minetest.is_creative_enabled(placer:get_player_name()),
|
||||
placer:get_player_control().sneak)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Returns position of the neighbor of a double chest node
|
||||
-- or nil if node is invalid.
|
||||
-- This function assumes that the large chest is actually intact
|
||||
-- * pos: Position of the node to investigate
|
||||
-- * param2: param2 of that node
|
||||
-- * side: Which "half" the investigated node is. "left" or "right"
|
||||
function mcl_util.get_double_container_neighbor_pos(pos, param2, side)
|
||||
if side == "right" then
|
||||
if param2 == 0 then
|
||||
return {x = pos.x - 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 1 then
|
||||
return {x = pos.x, y = pos.y, z = pos.z + 1}
|
||||
elseif param2 == 2 then
|
||||
return {x = pos.x + 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 3 then
|
||||
return {x = pos.x, y = pos.y, z = pos.z - 1}
|
||||
end
|
||||
else
|
||||
if param2 == 0 then
|
||||
return {x = pos.x + 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 1 then
|
||||
return {x = pos.x, y = pos.y, z = pos.z - 1}
|
||||
elseif param2 == 2 then
|
||||
return {x = pos.x - 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 3 then
|
||||
return {x = pos.x, y = pos.y, z = pos.z + 1}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Selects item stack to transfer from
|
||||
---@param src_inventory InvRef Source innentory to pull from
|
||||
---@param src_list string Name of source inventory list to pull from
|
||||
|
@ -424,61 +296,6 @@ function mcl_util.is_fuel(item)
|
|||
return minetest.get_craft_result({method = "fuel", width = 1, items = {item}}).time ~= 0
|
||||
end
|
||||
|
||||
-- Returns a on_place function for plants
|
||||
-- * condition: function(pos, node, itemstack)
|
||||
-- * A function which is called by the on_place function to check if the node can be placed
|
||||
-- * Must return true, if placement is allowed, false otherwise.
|
||||
-- * If it returns a string, placement is allowed, but will place this itemstring as a node instead
|
||||
-- * pos, node: Position and node table of plant node
|
||||
-- * itemstack: Itemstack to place
|
||||
function mcl_util.generate_on_place_plant_function(condition)
|
||||
return function(itemstack, placer, pointed_thing)
|
||||
if pointed_thing.type ~= "node" then
|
||||
-- no interaction possible with entities
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
if placer and not placer:get_player_control().sneak then
|
||||
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
||||
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
|
||||
local place_pos
|
||||
local def_under = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
|
||||
local def_above = minetest.registered_nodes[minetest.get_node(pointed_thing.above).name]
|
||||
if not def_under or not def_above then
|
||||
return itemstack
|
||||
end
|
||||
if def_under.buildable_to and def_under.name ~= itemstack:get_name() then
|
||||
place_pos = pointed_thing.under
|
||||
elseif def_above.buildable_to and def_above.name ~= itemstack:get_name() then
|
||||
place_pos = pointed_thing.above
|
||||
pointed_thing.under = pointed_thing.above
|
||||
else
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Check placement rules
|
||||
local result, param2 = condition(place_pos, node, itemstack)
|
||||
if result == true then
|
||||
local idef = itemstack:get_definition()
|
||||
local new_itemstack, success = minetest.item_place_node(itemstack, placer, pointed_thing, param2)
|
||||
|
||||
if success then
|
||||
if idef.sounds and idef.sounds.place then
|
||||
minetest.sound_play(idef.sounds.place, {pos = pointed_thing.above, gain = 1}, true)
|
||||
end
|
||||
end
|
||||
itemstack = new_itemstack
|
||||
end
|
||||
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
-- adjust the y level of an object to the center of its collisionbox
|
||||
-- used to get the origin position of entity explosions
|
||||
function mcl_util.get_object_center(obj)
|
||||
|
@ -750,243 +567,6 @@ function mcl_util.set_bone_position(obj, bone, pos, rot)
|
|||
end
|
||||
end
|
||||
|
||||
---Return a function to use in `on_place`.
|
||||
---
|
||||
---Allow to bypass the `buildable_to` node field in a `on_place` callback.
|
||||
---
|
||||
---You have to make sure that the nodes you return true for have `buildable_to = true`.
|
||||
---@param func fun(node_name: string): boolean Return `true` if node must not replace the buildable_to node which have `node_name`
|
||||
---@return fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: pointed_thing, param2: integer): ItemStack?
|
||||
function mcl_util.bypass_buildable_to(func)
|
||||
--------------------------
|
||||
-- MINETEST CODE: UTILS --
|
||||
--------------------------
|
||||
|
||||
local function copy_pointed_thing(pointed_thing)
|
||||
return {
|
||||
type = pointed_thing.type,
|
||||
above = pointed_thing.above and vector.copy(pointed_thing.above),
|
||||
under = pointed_thing.under and vector.copy(pointed_thing.under),
|
||||
ref = pointed_thing.ref,
|
||||
}
|
||||
end
|
||||
|
||||
local function user_name(user)
|
||||
return user and user:get_player_name() or ""
|
||||
end
|
||||
|
||||
-- Returns a logging function. For empty names, does not log.
|
||||
local function make_log(name)
|
||||
return name ~= "" and minetest.log or function() end
|
||||
end
|
||||
|
||||
local function check_attached_node(p, n, group_rating)
|
||||
local def = core.registered_nodes[n.name]
|
||||
local d = vector.zero()
|
||||
if group_rating == 3 then
|
||||
-- always attach to floor
|
||||
d.y = -1
|
||||
elseif group_rating == 4 then
|
||||
-- always attach to ceiling
|
||||
d.y = 1
|
||||
elseif group_rating == 2 then
|
||||
-- attach to facedir or 4dir direction
|
||||
if (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir") then
|
||||
-- Attach to whatever facedir is "mounted to".
|
||||
-- For facedir, this is where tile no. 5 point at.
|
||||
|
||||
-- The fallback vector here is in case 'facedir to dir' is nil due
|
||||
-- to voxelmanip placing a wallmounted node without resetting a
|
||||
-- pre-existing param2 value that is out-of-range for facedir.
|
||||
-- The fallback vector corresponds to param2 = 0.
|
||||
d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
|
||||
elseif (def.paramtype2 == "4dir" or
|
||||
def.paramtype2 == "color4dir") then
|
||||
-- Similar to facedir handling
|
||||
d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
|
||||
end
|
||||
elseif def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted" then
|
||||
-- Attach to whatever this node is "mounted to".
|
||||
-- This where tile no. 2 points at.
|
||||
|
||||
-- The fallback vector here is used for the same reason as
|
||||
-- for facedir nodes.
|
||||
d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
|
||||
else
|
||||
d.y = -1
|
||||
end
|
||||
local p2 = vector.add(p, d)
|
||||
local nn = core.get_node(p2).name
|
||||
local def2 = core.registered_nodes[nn]
|
||||
if def2 and not def2.walkable then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return function(itemstack, placer, pointed_thing, param2)
|
||||
-------------------
|
||||
-- MINETEST CODE --
|
||||
-------------------
|
||||
local def = itemstack:get_definition()
|
||||
if def.type ~= "node" or pointed_thing.type ~= "node" then
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local under = pointed_thing.under
|
||||
local oldnode_under = minetest.get_node_or_nil(under)
|
||||
local above = pointed_thing.above
|
||||
local oldnode_above = minetest.get_node_or_nil(above)
|
||||
local playername = user_name(placer)
|
||||
local log = make_log(playername)
|
||||
|
||||
if not oldnode_under or not oldnode_above then
|
||||
log("info", playername .. " tried to place"
|
||||
.. " node in unloaded position " .. minetest.pos_to_string(above))
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local olddef_under = minetest.registered_nodes[oldnode_under.name]
|
||||
olddef_under = olddef_under or minetest.nodedef_default
|
||||
local olddef_above = minetest.registered_nodes[oldnode_above.name]
|
||||
olddef_above = olddef_above or minetest.nodedef_default
|
||||
|
||||
if not olddef_above.buildable_to and not olddef_under.buildable_to then
|
||||
log("info", playername .. " tried to place"
|
||||
.. " node in invalid position " .. minetest.pos_to_string(above)
|
||||
.. ", replacing " .. oldnode_above.name)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
---------------------
|
||||
-- CUSTOMIZED CODE --
|
||||
---------------------
|
||||
|
||||
-- Place above pointed node
|
||||
local place_to = vector.copy(above)
|
||||
|
||||
-- If node under is buildable_to, check for callback result and place into it instead
|
||||
if olddef_under.buildable_to and not func(oldnode_under.name) then
|
||||
log("info", "node under is buildable to")
|
||||
place_to = vector.copy(under)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- MINETEST CODE --
|
||||
-------------------
|
||||
|
||||
if minetest.is_protected(place_to, playername) then
|
||||
log("action", playername
|
||||
.. " tried to place " .. def.name
|
||||
.. " at protected position "
|
||||
.. minetest.pos_to_string(place_to))
|
||||
minetest.record_protection_violation(place_to, playername)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local oldnode = minetest.get_node(place_to)
|
||||
local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
|
||||
|
||||
-- Calculate direction for wall mounted stuff like torches and signs
|
||||
if def.place_param2 ~= nil then
|
||||
newnode.param2 = def.place_param2
|
||||
elseif (def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted") and not param2 then
|
||||
local dir = vector.subtract(under, above)
|
||||
newnode.param2 = minetest.dir_to_wallmounted(dir)
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir" or
|
||||
def.paramtype2 == "4dir" or
|
||||
def.paramtype2 == "color4dir") and not param2 then
|
||||
local placer_pos = placer and placer:get_pos()
|
||||
if placer_pos then
|
||||
local dir = vector.subtract(above, placer_pos)
|
||||
newnode.param2 = minetest.dir_to_facedir(dir)
|
||||
log("info", "facedir: " .. newnode.param2)
|
||||
end
|
||||
end
|
||||
|
||||
local metatable = itemstack:get_meta():to_table().fields
|
||||
|
||||
-- Transfer color information
|
||||
if metatable.palette_index and not def.place_param2 then
|
||||
local color_divisor = nil
|
||||
if def.paramtype2 == "color" then
|
||||
color_divisor = 1
|
||||
elseif def.paramtype2 == "colorwallmounted" then
|
||||
color_divisor = 8
|
||||
elseif def.paramtype2 == "colorfacedir" then
|
||||
color_divisor = 32
|
||||
elseif def.paramtype2 == "color4dir" then
|
||||
color_divisor = 4
|
||||
elseif def.paramtype2 == "colordegrotate" then
|
||||
color_divisor = 32
|
||||
end
|
||||
if color_divisor then
|
||||
local color = math.floor(metatable.palette_index / color_divisor)
|
||||
local other = newnode.param2 % color_divisor
|
||||
newnode.param2 = color * color_divisor + other
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if the node is attached and if it can be placed there
|
||||
local an = minetest.get_item_group(def.name, "attached_node")
|
||||
if an ~= 0 and
|
||||
not check_attached_node(place_to, newnode, an) then
|
||||
log("action", "attached node " .. def.name ..
|
||||
" cannot be placed at " .. minetest.pos_to_string(place_to))
|
||||
return itemstack
|
||||
end
|
||||
|
||||
log("action", playername .. " places node "
|
||||
.. def.name .. " at " .. minetest.pos_to_string(place_to))
|
||||
|
||||
-- Add node and update
|
||||
minetest.add_node(place_to, newnode)
|
||||
|
||||
-- Play sound if it was done by a player
|
||||
if playername ~= "" and def.sounds and def.sounds.place then
|
||||
minetest.sound_play(def.sounds.place, {
|
||||
pos = place_to,
|
||||
exclude_player = playername,
|
||||
}, true)
|
||||
end
|
||||
|
||||
local take_item = true
|
||||
|
||||
-- Run callback
|
||||
if def.after_place_node then
|
||||
-- Deepcopy place_to and pointed_thing because callback can modify it
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if def.after_place_node(place_to_copy, placer, itemstack,
|
||||
pointed_thing_copy) then
|
||||
take_item = false
|
||||
end
|
||||
end
|
||||
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(minetest.registered_on_placenodes) do
|
||||
-- Deepcopy pos, node and pointed_thing because callback can modify them
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local newnode_copy = {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2}
|
||||
local oldnode_copy = {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2}
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
|
||||
take_item = false
|
||||
end
|
||||
end
|
||||
|
||||
if take_item then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
--[[Check for a protection violation in a given area.
|
||||
--
|
||||
-- Applies is_protected() to a 3D lattice of points in the defined volume. The points are spaced
|
||||
|
@ -1032,33 +612,6 @@ function mcl_util.check_position_protection(position, player)
|
|||
return false
|
||||
end
|
||||
|
||||
local palette_indexes = {grass_palette_index = 0, foliage_palette_index = 0, water_palette_index = 0}
|
||||
function mcl_util.get_palette_indexes_from_pos(pos)
|
||||
local biome_data = minetest.get_biome_data(pos)
|
||||
local biome = biome_data.biome
|
||||
local biome_name = minetest.get_biome_name(biome)
|
||||
local reg_biome = minetest.registered_biomes[biome_name]
|
||||
if reg_biome and reg_biome._mcl_grass_palette_index and reg_biome._mcl_foliage_palette_index and reg_biome._mcl_water_palette_index then
|
||||
local gpi = reg_biome._mcl_grass_palette_index
|
||||
local fpi = reg_biome._mcl_foliage_palette_index
|
||||
local wpi = reg_biome._mcl_water_palette_index
|
||||
local palette_indexes = {grass_palette_index = gpi, foliage_palette_index = fpi, water_palette_index = wpi}
|
||||
return palette_indexes
|
||||
else
|
||||
return palette_indexes
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_colorwallmounted_rotation(pos)
|
||||
local colorwallmounted_node = minetest.get_node(pos)
|
||||
for i = 0, 32, 1 do
|
||||
local colorwallmounted_rotation = colorwallmounted_node.param2 - (i * 8)
|
||||
if colorwallmounted_rotation < 6 then
|
||||
return colorwallmounted_rotation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Move items from one inventory list to another, drop items that do not fit in provided pos and direction.
|
||||
---@param src_inv mt.InvRef
|
||||
---@param src_listname string
|
||||
|
@ -1131,3 +684,27 @@ if not vector.in_area then
|
|||
(pos.z >= min.z) and (pos.z <= max.z)
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.gen_uuid()
|
||||
-- Generate a random 128-bit ID that can be assumed to be unique
|
||||
-- To have a 1% chance of a collision, there would have to be 1.6x10^76 IDs generated
|
||||
-- https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
|
||||
local u = {}
|
||||
for i = 1,16 do
|
||||
u[#u + 1] = string.format("%02X",math.random(1,255))
|
||||
end
|
||||
return table.concat(u)
|
||||
end
|
||||
function mcl_util.get_entity_id(entity)
|
||||
if entity:is_player() then
|
||||
return entity:get_player_name()
|
||||
else
|
||||
local le = entity:get_luaentity()
|
||||
local id = le._uuid
|
||||
if not id then
|
||||
id = mcl_util.gen_uuid()
|
||||
le._uuid = id
|
||||
end
|
||||
return id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
-- Functions related to nodes and node definitions
|
||||
|
||||
-- Minetest 5.3.0 or less can only measure the light level. This came in at 5.4
|
||||
-- This function has been known to fail in multiple places so the error handling is added increase safety and improve
|
||||
-- debugging. See:
|
||||
-- https://git.minetest.land/VoxeLibre/VoxeLibre/issues/1392
|
||||
function mcl_util.get_natural_light (pos, time)
|
||||
local status, retVal = pcall(minetest.get_natural_light, pos, time)
|
||||
if status then
|
||||
return retVal
|
||||
else
|
||||
minetest.log("warning", "Failed to get natural light at pos: " .. dump(pos) .. ", time: " .. dump(time))
|
||||
if (pos) then
|
||||
local node = minetest.get_node(pos)
|
||||
minetest.log("warning", "Node at pos: " .. dump(node.name))
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Based on minetest.rotate_and_place
|
||||
|
||||
--[[
|
||||
Attempt to predict the desired orientation of the pillar-like node
|
||||
defined by `itemstack`, and place it accordingly in one of 3 possible
|
||||
orientations (X, Y or Z).
|
||||
|
||||
Stacks are handled normally if the `infinitestacks`
|
||||
field is false or omitted (else, the itemstack is not changed).
|
||||
* `invert_wall`: if `true`, place wall-orientation on the ground and ground-
|
||||
orientation on wall
|
||||
|
||||
This function is a simplified version of minetest.rotate_and_place.
|
||||
The Minetest function is seen as inappropriate because this includes mirror
|
||||
images of possible orientations, causing problems with pillar shadings.
|
||||
]]
|
||||
function mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, infinitestacks, invert_wall)
|
||||
local unode = minetest.get_node_or_nil(pointed_thing.under)
|
||||
if not unode then
|
||||
return
|
||||
end
|
||||
local undef = minetest.registered_nodes[unode.name]
|
||||
if undef and undef.on_rightclick and not invert_wall then
|
||||
undef.on_rightclick(pointed_thing.under, unode, placer,
|
||||
itemstack, pointed_thing)
|
||||
return
|
||||
end
|
||||
local wield_name = itemstack:get_name()
|
||||
|
||||
local above = pointed_thing.above
|
||||
local under = pointed_thing.under
|
||||
|
||||
local anode = minetest.get_node_or_nil(above)
|
||||
if not anode then
|
||||
return
|
||||
end
|
||||
local pos = pointed_thing.above
|
||||
local node = anode
|
||||
|
||||
if undef and undef.buildable_to then
|
||||
pos = pointed_thing.under
|
||||
node = unode
|
||||
end
|
||||
|
||||
if minetest.is_protected(pos, placer:get_player_name()) then
|
||||
minetest.record_protection_violation(pos, placer:get_player_name())
|
||||
return
|
||||
end
|
||||
|
||||
local ndef = minetest.registered_nodes[node.name]
|
||||
if not ndef or not ndef.buildable_to then
|
||||
return
|
||||
end
|
||||
|
||||
local p2
|
||||
if above.y ~= under.y then
|
||||
p2 = 0
|
||||
elseif above.x ~= under.x then
|
||||
p2 = 12
|
||||
elseif above.z ~= under.z then
|
||||
p2 = 6
|
||||
end
|
||||
minetest.set_node(pos, {name = wield_name, param2 = p2})
|
||||
|
||||
if not infinitestacks then
|
||||
itemstack:take_item()
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
-- Wrapper of above function for use as `on_place` callback (Recommended).
|
||||
-- Similar to minetest.rotate_node.
|
||||
function mcl_util.rotate_axis(itemstack, placer, pointed_thing)
|
||||
mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing,
|
||||
minetest.is_creative_enabled(placer:get_player_name()),
|
||||
placer:get_player_control().sneak)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Returns position of the neighbor of a double chest node
|
||||
-- or nil if node is invalid.
|
||||
-- This function assumes that the large chest is actually intact
|
||||
-- * pos: Position of the node to investigate
|
||||
-- * param2: param2 of that node
|
||||
-- * side: Which "half" the investigated node is. "left" or "right"
|
||||
function mcl_util.get_double_container_neighbor_pos(pos, param2, side)
|
||||
local sign = (side == "right" and 1 or -1)
|
||||
|
||||
if param2 == 0 then
|
||||
return vector.offset(pos, -sign, 0, 0)
|
||||
elseif param2 == 1 then
|
||||
return vector.offset(pos, 0, 0, sign)
|
||||
elseif param2 == 2 then
|
||||
return vector.offset(pos, sign, 0, 0)
|
||||
elseif param2 == 3 then
|
||||
return vector.offset(pos, 0, 0, -sign)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns a on_place function for plants
|
||||
-- * condition: function(pos, node, itemstack)
|
||||
-- * A function which is called by the on_place function to check if the node can be placed
|
||||
-- * Must return true, if placement is allowed, false otherwise.
|
||||
-- * If it returns a string, placement is allowed, but will place this itemstring as a node instead
|
||||
-- * pos, node: Position and node table of plant node
|
||||
-- * itemstack: Itemstack to place
|
||||
function mcl_util.generate_on_place_plant_function(condition)
|
||||
return function(itemstack, placer, pointed_thing)
|
||||
if pointed_thing.type ~= "node" then
|
||||
-- no interaction possible with entities
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
|
||||
if placer and not placer:get_player_control().sneak then
|
||||
if node_def and node_def.on_rightclick then
|
||||
return node_def.on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
|
||||
local place_pos
|
||||
local def_under = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
|
||||
local def_above = minetest.registered_nodes[minetest.get_node(pointed_thing.above).name]
|
||||
if not def_under or not def_above then
|
||||
return itemstack
|
||||
end
|
||||
if def_under.buildable_to and def_under.name ~= itemstack:get_name() then
|
||||
place_pos = pointed_thing.under
|
||||
elseif def_above.buildable_to and def_above.name ~= itemstack:get_name() then
|
||||
place_pos = pointed_thing.above
|
||||
pointed_thing.under = pointed_thing.above
|
||||
else
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Check placement rules
|
||||
local result, param2 = condition(place_pos, node, itemstack)
|
||||
if result == true then
|
||||
local idef = itemstack:get_definition()
|
||||
local new_itemstack, success = minetest.item_place_node(itemstack, placer, pointed_thing, param2)
|
||||
|
||||
if success then
|
||||
if idef.sounds and idef.sounds.place then
|
||||
minetest.sound_play(idef.sounds.place, {pos = pointed_thing.above, gain = 1}, true)
|
||||
end
|
||||
end
|
||||
itemstack = new_itemstack
|
||||
end
|
||||
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
---Return a function to use in `on_place`.
|
||||
---
|
||||
---Allow to bypass the `buildable_to` node field in a `on_place` callback.
|
||||
---
|
||||
---You have to make sure that the nodes you return true for have `buildable_to = true`.
|
||||
---@param func fun(node_name: string): boolean Return `true` if node must not replace the buildable_to node which have `node_name`
|
||||
---@return fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: pointed_thing, param2: integer): ItemStack?
|
||||
function mcl_util.bypass_buildable_to(func)
|
||||
-- Copied from minetest builtin
|
||||
-- https://github.com/minetest/minetest/blob/526a2f7b8c45504088e194a83d54a19045227bbd/builtin/game/item.lua#L5-L12
|
||||
local function copy_pointed_thing(pointed_thing)
|
||||
return {
|
||||
type = pointed_thing.type,
|
||||
above = pointed_thing.above and vector.copy(pointed_thing.above),
|
||||
under = pointed_thing.under and vector.copy(pointed_thing.under),
|
||||
ref = pointed_thing.ref,
|
||||
}
|
||||
end
|
||||
|
||||
-- Copied from minetest builtin
|
||||
-- https://github.com/minetest/minetest/blob/526a2f7b8c45504088e194a83d54a19045227bbd/builtin/game/item.lua#L137-L139
|
||||
local function user_name(user)
|
||||
return user and user:get_player_name() or ""
|
||||
end
|
||||
|
||||
-- Returns a logging function. For empty names, does not log. Copied from minetest builtin
|
||||
-- https://github.com/minetest/minetest/blob/526a2f7b8c45504088e194a83d54a19045227bbd/builtin/game/item.lua#L142-L144
|
||||
local function make_log(name)
|
||||
return name ~= "" and minetest.log or function() end
|
||||
end
|
||||
|
||||
-- Copied from minetest builtin
|
||||
-- https://github.com/minetest/minetest/blob/526a2f7b8c45504088e194a83d54a19045227bbd/builtin/game/falling.lua#L503-L547
|
||||
local function check_attached_node(p, n, group_rating)
|
||||
local def = core.registered_nodes[n.name]
|
||||
local d = vector.zero()
|
||||
if group_rating == 3 then
|
||||
-- always attach to floor
|
||||
d.y = -1
|
||||
elseif group_rating == 4 then
|
||||
-- always attach to ceiling
|
||||
d.y = 1
|
||||
elseif group_rating == 2 then
|
||||
-- attach to facedir or 4dir direction
|
||||
if (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir") then
|
||||
-- Attach to whatever facedir is "mounted to".
|
||||
-- For facedir, this is where tile no. 5 point at.
|
||||
|
||||
-- The fallback vector here is in case 'facedir to dir' is nil due
|
||||
-- to voxelmanip placing a wallmounted node without resetting a
|
||||
-- pre-existing param2 value that is out-of-range for facedir.
|
||||
-- The fallback vector corresponds to param2 = 0.
|
||||
d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
|
||||
elseif (def.paramtype2 == "4dir" or
|
||||
def.paramtype2 == "color4dir") then
|
||||
-- Similar to facedir handling
|
||||
d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
|
||||
end
|
||||
elseif def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted" then
|
||||
-- Attach to whatever this node is "mounted to".
|
||||
-- This where tile no. 2 points at.
|
||||
|
||||
-- The fallback vector here is used for the same reason as
|
||||
-- for facedir nodes.
|
||||
d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
|
||||
else
|
||||
d.y = -1
|
||||
end
|
||||
local p2 = vector.add(p, d)
|
||||
local nn = core.get_node(p2).name
|
||||
local def2 = core.registered_nodes[nn]
|
||||
|
||||
return not def2 or def2.walkable
|
||||
end
|
||||
|
||||
-- Copied from minetest builtin
|
||||
-- https://github.com/minetest/minetest/blob/e7dd9737bd5deb573c9fef7b3ff2ead29b2cfe31/builtin/game/item.lua#L146-L294
|
||||
return function(itemstack, placer, pointed_thing, param2)
|
||||
local def = itemstack:get_definition()
|
||||
if def.type ~= "node" or pointed_thing.type ~= "node" then
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local under = pointed_thing.under
|
||||
local oldnode_under = minetest.get_node_or_nil(under)
|
||||
local above = pointed_thing.above
|
||||
local oldnode_above = minetest.get_node_or_nil(above)
|
||||
local playername = user_name(placer)
|
||||
local log = make_log(playername)
|
||||
|
||||
if not oldnode_under or not oldnode_above then
|
||||
log("info", playername .. " tried to place"
|
||||
.. " node in unloaded position " .. minetest.pos_to_string(above))
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local olddef_under = minetest.registered_nodes[oldnode_under.name] or minetest.nodedef_default
|
||||
local olddef_above = minetest.registered_nodes[oldnode_above.name] or minetest.nodedef_default
|
||||
|
||||
if not olddef_above.buildable_to and not olddef_under.buildable_to then
|
||||
log("info", playername .. " tried to place"
|
||||
.. " node in invalid position " .. minetest.pos_to_string(above)
|
||||
.. ", replacing " .. oldnode_above.name)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Place above pointed node
|
||||
local place_to = above
|
||||
|
||||
-- If node under is buildable_to, check for callback result and place into it instead
|
||||
-- This line was modified from minetest code to allow overriding builtable_to
|
||||
if olddef_under.buildable_to and not func(oldnode_under.name) then
|
||||
log("info", "node under is buildable to")
|
||||
place_to = under
|
||||
end
|
||||
|
||||
if minetest.is_protected(place_to, playername) then
|
||||
log("action", playername
|
||||
.. " tried to place " .. def.name
|
||||
.. " at protected position "
|
||||
.. minetest.pos_to_string(place_to))
|
||||
minetest.record_protection_violation(place_to, playername)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local oldnode = minetest.get_node(place_to)
|
||||
local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
|
||||
|
||||
-- Calculate direction for wall mounted stuff like torches and signs
|
||||
if def.place_param2 ~= nil then
|
||||
newnode.param2 = def.place_param2
|
||||
elseif (def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted") and not param2 then
|
||||
newnode.param2 = minetest.dir_to_wallmounted(vector.subtract(under, above))
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir" or
|
||||
def.paramtype2 == "4dir" or
|
||||
def.paramtype2 == "color4dir") and not param2 then
|
||||
local placer_pos = placer and placer:get_pos()
|
||||
if placer_pos then
|
||||
newnode.param2 = minetest.dir_to_facedir(vector.subtract(above, placer_pos))
|
||||
log("info", "facedir: " .. newnode.param2)
|
||||
end
|
||||
end
|
||||
|
||||
local metatable = itemstack:get_meta():to_table().fields
|
||||
|
||||
-- Transfer color information
|
||||
if metatable.palette_index and not def.place_param2 then
|
||||
local color_divisor = nil
|
||||
if def.paramtype2 == "color" then
|
||||
color_divisor = 1
|
||||
elseif def.paramtype2 == "colorwallmounted" then
|
||||
color_divisor = 8
|
||||
elseif def.paramtype2 == "colorfacedir" then
|
||||
color_divisor = 32
|
||||
elseif def.paramtype2 == "color4dir" then
|
||||
color_divisor = 4
|
||||
elseif def.paramtype2 == "colordegrotate" then
|
||||
color_divisor = 32
|
||||
end
|
||||
if color_divisor then
|
||||
local color = math.floor(metatable.palette_index / color_divisor)
|
||||
local other = newnode.param2 % color_divisor
|
||||
newnode.param2 = color * color_divisor + other
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if the node is attached and if it can be placed there
|
||||
local an = minetest.get_item_group(def.name, "attached_node")
|
||||
if an ~= 0 and
|
||||
not check_attached_node(place_to, newnode, an) then
|
||||
log("action", "attached node " .. def.name ..
|
||||
" cannot be placed at " .. minetest.pos_to_string(place_to))
|
||||
return itemstack
|
||||
end
|
||||
|
||||
log("action", playername .. " places node "
|
||||
.. def.name .. " at " .. minetest.pos_to_string(place_to))
|
||||
|
||||
-- Add node and update
|
||||
minetest.add_node(place_to, newnode)
|
||||
|
||||
-- Play sound if it was done by a player
|
||||
if playername ~= "" and def.sounds and def.sounds.place then
|
||||
minetest.sound_play(def.sounds.place, {
|
||||
pos = place_to,
|
||||
exclude_player = playername,
|
||||
}, true)
|
||||
end
|
||||
|
||||
local take_item = true
|
||||
|
||||
-- Run callback
|
||||
if def.after_place_node then
|
||||
-- Deepcopy place_to and pointed_thing because callback can modify it
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if def.after_place_node(place_to_copy, placer, itemstack,
|
||||
pointed_thing_copy) then
|
||||
take_item = false
|
||||
end
|
||||
end
|
||||
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(minetest.registered_on_placenodes) do
|
||||
-- Deepcopy pos, node and pointed_thing because callback can modify them
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local newnode_copy = {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2}
|
||||
local oldnode_copy = {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2}
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
|
||||
take_item = false
|
||||
end
|
||||
end
|
||||
|
||||
if take_item then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
local DEFAULT_PALETTE_INDEXES = {grass_palette_index = 0, foliage_palette_index = 0, water_palette_index = 0}
|
||||
function mcl_util.get_palette_indexes_from_pos(pos)
|
||||
local biome_data = minetest.get_biome_data(pos)
|
||||
local biome = biome_data.biome
|
||||
local biome_name = minetest.get_biome_name(biome)
|
||||
local reg_biome = minetest.registered_biomes[biome_name]
|
||||
if reg_biome and reg_biome._mcl_grass_palette_index and reg_biome._mcl_foliage_palette_index and reg_biome._mcl_water_palette_index then
|
||||
return {
|
||||
grass_palette_index = reg_biome._mcl_grass_palette_index,
|
||||
foliage_palette_index = reg_biome._mcl_foliage_palette_index,
|
||||
water_palette_index = reg_biome._mcl_water_palette_index,
|
||||
}
|
||||
else
|
||||
return DEFAULT_PALETTE_INDEXES
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_colorwallmounted_rotation(pos)
|
||||
local colorwallmounted_node = minetest.get_node(pos)
|
||||
for i = 0, 32, 1 do
|
||||
local colorwallmounted_rotation = colorwallmounted_node.param2 - (i * 8)
|
||||
if colorwallmounted_rotation < 6 then
|
||||
return colorwallmounted_rotation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.match_node_to_filter(node_name, filters)
|
||||
for i = 1,#filters do
|
||||
local filter = filters[i]
|
||||
if node_name == filter then return true end
|
||||
|
||||
if string.sub(filter,1,6) == "group:" and minetest.get_item_group(node_name, string.sub(filter,7)) ~= 0 then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
|
@ -1236,13 +1236,14 @@ function mob_class:do_states_attack (dtime)
|
|||
minetest.after(1, function()
|
||||
self.firing = false
|
||||
end)
|
||||
arrow = minetest.add_entity(p, self.arrow)
|
||||
|
||||
arrow = vl_projectile.create(self.arrow, {
|
||||
pos = p,
|
||||
owner = self,
|
||||
})
|
||||
ent = arrow:get_luaentity()
|
||||
if ent.velocity then
|
||||
v = ent.velocity
|
||||
end
|
||||
v = ent.velocity or v
|
||||
ent.switch = 1
|
||||
ent.owner_id = tostring(self.object) -- add unique owner id to arrow
|
||||
|
||||
-- important for mcl_shields
|
||||
ent._shooter = self.object
|
||||
|
@ -1252,12 +1253,11 @@ function mob_class:do_states_attack (dtime)
|
|||
end
|
||||
end
|
||||
|
||||
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
|
||||
-- offset makes shoot aim accurate
|
||||
local amount = vector.length(vec)
|
||||
vec.y = vec.y + self.shoot_offset
|
||||
vec.x = vec.x * (v / amount)
|
||||
vec.y = vec.y * (v / amount)
|
||||
vec.z = vec.z * (v / amount)
|
||||
vec = vector.multiply(vec, v / amount)
|
||||
|
||||
if self.shoot_arrow then
|
||||
vec = vector.normalize(vec)
|
||||
self:shoot_arrow(p, vec)
|
||||
|
|
|
@ -383,11 +383,17 @@ end
|
|||
|
||||
-- register arrow for shoot attack
|
||||
function mcl_mobs.register_arrow(name, def)
|
||||
|
||||
if not name or not def then return end -- errorcheck
|
||||
|
||||
minetest.register_entity(name, {
|
||||
local behaviors = {}
|
||||
if def.hit_node then
|
||||
table.insert(behaviors, vl_projectile.collides_with_solids)
|
||||
end
|
||||
if def.hit_player or def.hit_mob or def.hit_object then
|
||||
table.insert(behaviors, vl_projectile.collides_with_entities)
|
||||
end
|
||||
|
||||
vl_projectile.register(name, {
|
||||
physical = false,
|
||||
visual = def.visual,
|
||||
visual_size = def.visual_size,
|
||||
|
@ -399,43 +405,87 @@ function mcl_mobs.register_arrow(name, def)
|
|||
hit_object = def.hit_object,
|
||||
homing = def.homing,
|
||||
drop = def.drop or false, -- drops arrow as registered item when true
|
||||
collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
|
||||
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0}, -- remove box around arrows
|
||||
timer = 0,
|
||||
switch = 0,
|
||||
_lifetime = def._lifetime or 7,
|
||||
owner_id = def.owner_id,
|
||||
rotate = def.rotate,
|
||||
_vl_projectile = {
|
||||
behaviors = behaviors,
|
||||
ignore_gravity = true,
|
||||
damages_players = true,
|
||||
allow_punching = function(self, entity_def, projectile_def, object)
|
||||
if def.allow_punching and not def.allow_punching(self, entity_def, projectile_def, object) then return false end
|
||||
if self.timer > 2 then return true end
|
||||
if self._owner and mcl_util.get_entity_id(object) == self._owner then return false end
|
||||
|
||||
return true
|
||||
end,
|
||||
on_collide_with_solid = function(self, pos, node, nodedef)
|
||||
if nodedef or not nodedef.walkable then return end
|
||||
|
||||
self.hit_node(self, pos, node)
|
||||
if self.drop == true then
|
||||
pos.y = pos.y + 1
|
||||
self.lastpos = self.lastpos or pos
|
||||
|
||||
minetest.add_item(self.lastpos, self.object:get_luaentity().name)
|
||||
end
|
||||
|
||||
self._removed = true
|
||||
self.object:remove();
|
||||
end,
|
||||
on_collide_with_entity = function(self, pos, object)
|
||||
if self.hit_player and object:is_player() then
|
||||
self.hit_player(self, object)
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
local entity = object:get_luaentity()
|
||||
if not entity or entity.name == self.object:get_luaentity().name then return end
|
||||
if self.timer <= 2 then return end
|
||||
|
||||
if self.hit_mob and entity.is_mob == true then
|
||||
self.hit_mob(self, object)
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
elseif self.hit_object then
|
||||
self.hit_object(self, object)
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
},
|
||||
on_punch = def.on_punch or function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
local vel = self.object:get_velocity():length()
|
||||
self.object:set_velocity(dir * vel)
|
||||
self._puncher = puncher
|
||||
self._owner = mcl_util.get_entity_id(puncher)
|
||||
end,
|
||||
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
|
||||
automatic_face_movement_dir = def.rotate
|
||||
and (def.rotate - (math.pi / 180)) or false,
|
||||
|
||||
on_activate = def.on_activate,
|
||||
|
||||
on_step = def.on_step or function(self, dtime)
|
||||
|
||||
self.timer = self.timer + dtime
|
||||
-- Projectile behavior processing
|
||||
vl_projectile.update_projectile(self, dtime)
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
if self.switch == 0
|
||||
or self.timer > self._lifetime
|
||||
or not within_limits(pos, 0) then
|
||||
if self.switch == 0 or self.timer > self._lifetime or not within_limits(pos, 0) then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self._removed = true
|
||||
self.object:remove();
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- does arrow have a tail (fireball)
|
||||
if def.tail
|
||||
and def.tail == 1
|
||||
and def.tail_texture then
|
||||
|
||||
if def.tail == 1 and def.tail_texture then
|
||||
minetest.add_particle({
|
||||
pos = pos,
|
||||
velocity = {x = 0, y = 0, z = 0},
|
||||
|
@ -448,29 +498,6 @@ function mcl_mobs.register_arrow(name, def)
|
|||
})
|
||||
end
|
||||
|
||||
if self.hit_node then
|
||||
|
||||
local node = node_ok(pos).name
|
||||
|
||||
if minetest.registered_nodes[node].walkable then
|
||||
|
||||
self.hit_node(self, pos, node)
|
||||
|
||||
if self.drop == true then
|
||||
|
||||
pos.y = pos.y + 1
|
||||
|
||||
self.lastpos = (self.lastpos or pos)
|
||||
|
||||
minetest.add_item(self.lastpos, self.object:get_luaentity().name)
|
||||
end
|
||||
|
||||
self.object:remove();
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if self.homing and self._target then
|
||||
local p = self._target:get_pos()
|
||||
if p then
|
||||
|
@ -482,42 +509,6 @@ function mcl_mobs.register_arrow(name, def)
|
|||
end
|
||||
end
|
||||
|
||||
if self.hit_player or self.hit_mob or self.hit_object then
|
||||
|
||||
for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
|
||||
|
||||
if self.hit_player
|
||||
and object:is_player() then
|
||||
|
||||
self.hit_player(self, object)
|
||||
self.object:remove();
|
||||
return
|
||||
end
|
||||
|
||||
local entity = object:get_luaentity()
|
||||
|
||||
if entity
|
||||
and self.hit_mob
|
||||
and entity.is_mob == true
|
||||
and (tostring(object) ~= self.owner_id or self.timer > 2)
|
||||
and entity.name ~= self.object:get_luaentity().name then
|
||||
self.hit_mob(self, object)
|
||||
self.object:remove();
|
||||
return
|
||||
end
|
||||
|
||||
if entity
|
||||
and self.hit_object
|
||||
and (not entity.is_mob)
|
||||
and (tostring(object) ~= self.owner_id or self.timer > 2)
|
||||
and entity.name ~= self.object:get_luaentity().name then
|
||||
self.hit_object(self, object)
|
||||
self.object:remove();
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.lastpos = pos
|
||||
end
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name = mcl_mobs
|
||||
author = PilzAdam
|
||||
description = Adds a mob API for mods to add animals or monsters, etc.
|
||||
depends = mcl_particles, mcl_luck
|
||||
depends = mcl_particles, mcl_luck, vl_projectile
|
||||
optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience, mcl_sculk
|
||||
|
|
|
@ -133,8 +133,8 @@ mcl_mobs.register_arrow("mobs_mc:fireball", {
|
|||
}, nil)
|
||||
mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1, true)
|
||||
local ent = mob:get_luaentity()
|
||||
if (not ent or ent.health <= 0) and self._puncher and name == "mobs_mc:ghast" then
|
||||
awards.unlock(self._puncher:get_player_name(), "mcl:fireball_redir_serv")
|
||||
if (not ent or ent.health <= 0) and self._owner and minetest.get_player_by_name(self._owner) and name == "mobs_mc:ghast" then
|
||||
awards.unlock(self._owner, "mcl:fireball_redir_serv")
|
||||
end
|
||||
end,
|
||||
|
||||
|
|
|
@ -201,6 +201,7 @@ mcl_mobs.register_mob("mobs_mc:wither", {
|
|||
self._death_timer = self._death_timer + self.health - self._health_old
|
||||
if self.health == self._health_old then self._death_timer = self._death_timer + dtime end
|
||||
if self._death_timer > 100 then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return false
|
||||
end
|
||||
|
@ -456,15 +457,18 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull", {
|
|||
textures = {
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:0", -- top
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:1", -- bottom
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:2", -- left
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:3", -- right
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:4", -- back
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:5", -- front
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:3", -- right
|
||||
"mobs_mc_wither_projectile.png^[verticalframe:6:2", -- left
|
||||
},
|
||||
velocity = 7,
|
||||
rotate = 90,
|
||||
_lifetime = 15,
|
||||
on_punch = function(self) end,
|
||||
allow_punching = function(self, _, _, object)
|
||||
local le = object and object:get_luaentity()
|
||||
return not le or le.name ~= "mobs_mc:wither"
|
||||
end,
|
||||
|
||||
-- direct hit
|
||||
hit_player = function(self, player)
|
||||
|
@ -509,15 +513,18 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull_strong", {
|
|||
textures = {
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:0", -- top
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:1", -- bottom
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:2", -- left
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:3", -- right
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:4", -- back
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:5", -- front
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:3", -- right
|
||||
"mobs_mc_wither_projectile_strong.png^[verticalframe:6:2", -- left
|
||||
},
|
||||
velocity = 4,
|
||||
rotate = 90,
|
||||
_lifetime = 25,
|
||||
on_punch = function(self) end,
|
||||
allow_punching = function(self, _, _, object)
|
||||
local le = object and object:get_luaentity()
|
||||
return not le or le.name ~= "mobs_mc:wither"
|
||||
end,
|
||||
|
||||
-- direct hit
|
||||
hit_player = function(self, player)
|
||||
|
|
|
@ -27,6 +27,11 @@ minetest.register_node("mcl_target:target_off", {
|
|||
rules = mesecon.rules.alldirs,
|
||||
},
|
||||
},
|
||||
_vl_projectile = {
|
||||
on_collide = function(projectile, pos, node, node_def)
|
||||
mcl_target.hit(pos, 1) --10 redstone ticks
|
||||
end
|
||||
},
|
||||
_mcl_blast_resistance = 0.5,
|
||||
_mcl_hardness = 0.5,
|
||||
})
|
||||
|
|
|
@ -151,6 +151,16 @@ function mesecon.register_button(basename, description, texture, recipeitem, sou
|
|||
}},
|
||||
_mcl_button_basename = basename,
|
||||
_mcl_button_timer = button_timer,
|
||||
_vl_projectile = {
|
||||
on_collide = function(projectile, pos, node, node_def)
|
||||
pos = vector.round(pos)
|
||||
|
||||
-- Push the button! Push, push, push the button!
|
||||
if node_def.groups.button_push_by_arrow == 1 then
|
||||
mesecon.push_button(pos, node)
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
_mcl_blast_resistance = 0.5,
|
||||
_mcl_hardness = 0.5,
|
||||
|
|
|
@ -7,35 +7,8 @@ local enable_pvp = minetest.settings:get_bool("enable_pvp")
|
|||
local math = math
|
||||
local vector = vector
|
||||
|
||||
-- Time in seconds after which a stuck arrow is deleted
|
||||
local ARROW_TIMEOUT = 60
|
||||
-- Time after which stuck arrow is rechecked for being stuck
|
||||
local STUCK_RECHECK_TIME = 5
|
||||
|
||||
--local GRAVITY = 9.81
|
||||
|
||||
local YAW_OFFSET = -math.pi/2
|
||||
|
||||
local function dir_to_pitch(dir)
|
||||
--local dir2 = vector.normalize(dir)
|
||||
local xz = math.abs(dir.x) + math.abs(dir.z)
|
||||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
local function random_arrow_positions(positions, placement)
|
||||
if positions == "x" then
|
||||
return math.random(-4, 4)
|
||||
elseif positions == "y" then
|
||||
return math.random(0, 10)
|
||||
end
|
||||
if placement == "front" and positions == "z" then
|
||||
return 3
|
||||
elseif placement == "back" and positions == "z" then
|
||||
return -3
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local mod_awards = minetest.get_modpath("awards") and minetest.get_modpath("mcl_achievements")
|
||||
local mod_button = minetest.get_modpath("mesecons_button")
|
||||
|
||||
|
@ -56,7 +29,8 @@ S("Arrows might get stuck on solid blocks and can be retrieved again. They are a
|
|||
end,
|
||||
})
|
||||
|
||||
local ARROW_ENTITY={
|
||||
-- Destroy arrow entity self at pos and drops it as an item
|
||||
local arrow_entity = {
|
||||
physical = true,
|
||||
pointable = false,
|
||||
visual = "mesh",
|
||||
|
@ -65,9 +39,13 @@ local ARROW_ENTITY={
|
|||
textures = {"mcl_bows_arrow.png"},
|
||||
collisionbox = {-0.19, -0.125, -0.19, 0.19, 0.125, 0.19},
|
||||
collide_with_objects = false,
|
||||
liquid_drag = true,
|
||||
_fire_damage_resistant = true,
|
||||
|
||||
_lastpos={},
|
||||
_save_fields = {
|
||||
"last_pos", "startpos", "damage", "is_critical", "stuck", "stuckin", "stuckin_player", "time_in_air", "vl_projectile",
|
||||
},
|
||||
|
||||
_startpos=nil,
|
||||
_damage=1, -- Damage on impact
|
||||
_is_critical=false, -- Whether this arrow would deal critical damage
|
||||
|
@ -81,430 +59,130 @@ local ARROW_ENTITY={
|
|||
_blocked = false,
|
||||
_viscosity=0, -- Viscosity of node the arrow is currently in
|
||||
_deflection_cooloff=0, -- Cooloff timer after an arrow deflection, to prevent many deflections in quick succession
|
||||
}
|
||||
|
||||
-- Destroy arrow entity self at pos and drops it as an item
|
||||
local function spawn_item(self, pos)
|
||||
if not minetest.is_creative_enabled("") then
|
||||
local item = minetest.add_item(pos, "mcl_bows:arrow")
|
||||
item:set_velocity(vector.new(0, 0, 0))
|
||||
item:set_yaw(self.object:get_yaw())
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
end
|
||||
_vl_projectile = {
|
||||
survive_collision = true,
|
||||
sticks_in_players = true,
|
||||
damages_players = true,
|
||||
maximum_time = 60,
|
||||
damage_groups = function(self)
|
||||
return { fleshy = self._damage }
|
||||
end,
|
||||
hide_tracer = function(self)
|
||||
return self._stuck or self._damage < 9 or self._in_player
|
||||
end,
|
||||
tracer_texture = "mobs_mc_arrow_particle.png",
|
||||
behaviors = {
|
||||
vl_projectile.sticks,
|
||||
vl_projectile.burns,
|
||||
vl_projectile.has_tracer,
|
||||
|
||||
local function damage_particles(pos, is_critical)
|
||||
if is_critical then
|
||||
minetest.add_particlespawner({
|
||||
amount = 15,
|
||||
time = 0.1,
|
||||
minpos = vector.offset(pos, -0.5, -0.5, -0.5),
|
||||
maxpos = vector.offset(pos, 0.5, 0.5, 0.5),
|
||||
minvel = vector.new(-0.1, -0.1, -0.1),
|
||||
maxvel = vector.new(0.1, 0.1, 0.1),
|
||||
minexptime = 1,
|
||||
maxexptime = 2,
|
||||
minsize = 1.5,
|
||||
maxsize = 1.5,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_crit.png^[colorize:#bc7a57:127",
|
||||
})
|
||||
end
|
||||
end
|
||||
-- Custom arrow behaviors
|
||||
function(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
self._allow_punch = self._allow_punch or not self._owner or not self._startpos or pos and vector.distance(self._startpos, pos) > 1.5
|
||||
|
||||
function ARROW_ENTITY.on_step(self, dtime)
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
-- mcl_burning.tick may remove object immediately
|
||||
if not self.object:get_pos() then return end
|
||||
|
||||
self._time_in_air = self._time_in_air + .001
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
local dpos = vector.round(vector.new(pos)) -- digital pos
|
||||
local node = minetest.get_node(dpos)
|
||||
|
||||
if self._stuck then
|
||||
self._stucktimer = self._stucktimer + dtime
|
||||
self._stuckrechecktimer = self._stuckrechecktimer + dtime
|
||||
if self._stucktimer > ARROW_TIMEOUT then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
-- Drop arrow as item when it is no longer stuck
|
||||
-- FIXME: Arrows are a bit slow to react and continue to float in mid air for a few seconds.
|
||||
if self._stuckrechecktimer > STUCK_RECHECK_TIME then
|
||||
local stuckin_def
|
||||
if self._stuckin then
|
||||
stuckin_def = minetest.registered_nodes[minetest.get_node(self._stuckin).name]
|
||||
end
|
||||
-- TODO: In MC, arrow just falls down without turning into an item
|
||||
if stuckin_def and stuckin_def.walkable == false then
|
||||
spawn_item(self, pos)
|
||||
return
|
||||
end
|
||||
self._stuckrechecktimer = 0
|
||||
end
|
||||
-- Pickup arrow if player is nearby (not in Creative Mode)
|
||||
local objects = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _,obj in ipairs(objects) do
|
||||
if obj:is_player() then
|
||||
if self._collectable and not minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
if obj:get_inventory():room_for_item("main", "mcl_bows:arrow") then
|
||||
obj:get_inventory():add_item("main", "mcl_bows:arrow")
|
||||
minetest.sound_play("item_drop_pickup", {
|
||||
pos = pos,
|
||||
max_hear_distance = 16,
|
||||
gain = 1.0,
|
||||
}, true)
|
||||
end
|
||||
if self._deflection_cooloff > 0 then
|
||||
self._deflection_cooloff = self._deflection_cooloff - dtime
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
-- Check for object "collision". Done every tick (hopefully this is not too stressing)
|
||||
else
|
||||
vl_projectile.collides_with_solids,
|
||||
vl_projectile.raycast_collides_with_entities,
|
||||
},
|
||||
allow_punching = function(self, entity_def, projectile_def, object)
|
||||
if not self._allow_punch then return false end
|
||||
|
||||
if self._damage >= 9 and self._in_player == false then
|
||||
minetest.add_particlespawner({
|
||||
amount = 20,
|
||||
time = .2,
|
||||
minpos = vector.new(0,0,0),
|
||||
maxpos = vector.new(0,0,0),
|
||||
minvel = vector.new(-0.1,-0.1,-0.1),
|
||||
maxvel = vector.new(0.1,0.1,0.1),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 0.5,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
attached = self.object,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mobs_mc_arrow_particle.png",
|
||||
glow = 1,
|
||||
})
|
||||
end
|
||||
local lua = object:get_luaentity()
|
||||
if lua and lua.name == "mobs_mc:rover" then return false end
|
||||
|
||||
local closest_object
|
||||
local closest_distance
|
||||
|
||||
if self._deflection_cooloff > 0 then
|
||||
self._deflection_cooloff = self._deflection_cooloff - dtime
|
||||
end
|
||||
|
||||
local arrow_dir = self.object:get_velocity()
|
||||
--create a raycast from the arrow based on the velocity of the arrow to deal with lag
|
||||
local raycast = minetest.raycast(pos, vector.add(pos, vector.multiply(arrow_dir, 0.1)), true, false)
|
||||
for hitpoint in raycast do
|
||||
if hitpoint.type == "object" then
|
||||
-- find the closest object that is in the way of the arrow
|
||||
local ok = false
|
||||
if hitpoint.ref:is_player() and enable_pvp then
|
||||
ok = true
|
||||
elseif not hitpoint.ref:is_player() and hitpoint.ref:get_luaentity() then
|
||||
if (hitpoint.ref:get_luaentity().is_mob or hitpoint.ref:get_luaentity()._hittable_by_projectile) then
|
||||
ok = true
|
||||
end
|
||||
return true
|
||||
end,
|
||||
sounds = {
|
||||
on_entity_collision = function(self, _, _, _, obj)
|
||||
if obj:is_player() then
|
||||
return {{name="mcl_bows_hit_player", gain=0.1}, {to_player=obj:get_player_name()}, true}
|
||||
end
|
||||
if ok then
|
||||
local dist = vector.distance(hitpoint.ref:get_pos(), pos)
|
||||
if not closest_object or not closest_distance then
|
||||
closest_object = hitpoint.ref
|
||||
closest_distance = dist
|
||||
elseif dist < closest_distance then
|
||||
closest_object = hitpoint.ref
|
||||
closest_distance = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if closest_object then
|
||||
local obj = closest_object
|
||||
return {{name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true}
|
||||
end
|
||||
},
|
||||
on_collide_with_entity = function(self, pos, obj)
|
||||
local is_player = obj:is_player()
|
||||
local lua = obj:get_luaentity()
|
||||
if obj == self._shooter and self._time_in_air > 1.02 or obj ~= self._shooter and (is_player or (lua and (lua.is_mob or lua._hittable_by_projectile))) then
|
||||
if obj:get_hp() > 0 then
|
||||
-- Check if there is no solid node between arrow and object
|
||||
local ray = minetest.raycast(self.object:get_pos(), obj:get_pos(), true)
|
||||
for pointed_thing in ray do
|
||||
if pointed_thing.type == "object" and pointed_thing.ref == closest_object then
|
||||
-- Target reached! We can proceed now.
|
||||
break
|
||||
elseif pointed_thing.type == "node" then
|
||||
local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name
|
||||
local def = minetest.registered_nodes[nn]
|
||||
if (not def) or def.walkable then
|
||||
-- There's a node in the way. Delete arrow without damage
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Punch target object but avoid hurting enderman.
|
||||
if not lua or lua.name ~= "mobs_mc:rover" then
|
||||
if not self._in_player then
|
||||
damage_particles(vector.add(pos, vector.multiply(self.object:get_velocity(), 0.1)), self._is_critical)
|
||||
end
|
||||
if mcl_burning.is_burning(self.object) then
|
||||
mcl_burning.set_on_fire(obj, 5)
|
||||
end
|
||||
if not self._in_player and not self._blocked then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, self.object:get_velocity())
|
||||
if obj:is_player() then
|
||||
if not mcl_shields.is_blocking(obj) then
|
||||
local placement
|
||||
self._placement = math.random(1, 2)
|
||||
if self._placement == 1 then
|
||||
placement = "front"
|
||||
else
|
||||
placement = "back"
|
||||
end
|
||||
self._in_player = true
|
||||
if self._placement == 2 then
|
||||
self._rotation_station = 90
|
||||
else
|
||||
self._rotation_station = -90
|
||||
end
|
||||
self._y_position = random_arrow_positions("y", placement)
|
||||
self._x_position = random_arrow_positions("x", placement)
|
||||
if self._y_position > 6 and self._x_position < 2 and self._x_position > -2 then
|
||||
self._attach_parent = "Head"
|
||||
self._y_position = self._y_position - 6
|
||||
elseif self._x_position > 2 then
|
||||
self._attach_parent = "Arm_Right"
|
||||
self._y_position = self._y_position - 3
|
||||
self._x_position = self._x_position - 2
|
||||
elseif self._x_position < -2 then
|
||||
self._attach_parent = "Arm_Left"
|
||||
self._y_position = self._y_position - 3
|
||||
self._x_position = self._x_position + 2
|
||||
else
|
||||
self._attach_parent = "Body"
|
||||
end
|
||||
self._z_rotation = math.random(-30, 30)
|
||||
self._y_rotation = math.random( -30, 30)
|
||||
self.object:set_attach(
|
||||
obj, self._attach_parent,
|
||||
vector.new(self._x_position, self._y_position, random_arrow_positions("z", placement)),
|
||||
vector.new(0, self._rotation_station + self._y_rotation, self._z_rotation)
|
||||
)
|
||||
else
|
||||
self._blocked = true
|
||||
self.object:set_velocity(vector.multiply(self.object:get_velocity(), -0.25))
|
||||
end
|
||||
minetest.after(150, function()
|
||||
self.object:remove()
|
||||
end)
|
||||
else
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if is_player then
|
||||
if self._shooter and self._shooter:is_player() and not self._in_player and not self._blocked then
|
||||
-- “Ding” sound for hitting another player
|
||||
minetest.sound_play({name="mcl_bows_hit_player", gain=0.1}, {to_player=self._shooter:get_player_name()}, true)
|
||||
end
|
||||
end
|
||||
|
||||
if lua then
|
||||
local entity_name = lua.name
|
||||
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
|
||||
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
|
||||
-- TODO: This achievement should be given for the kill, not just a hit
|
||||
if self._shooter and self._shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
|
||||
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
|
||||
awards.unlock(self._shooter:get_player_name(), "mcl:snipeSkeleton")
|
||||
end
|
||||
end
|
||||
end
|
||||
if not self._in_player and not self._blocked then
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
end
|
||||
end
|
||||
if not obj:is_player() then
|
||||
mcl_burning.extinguish(self.object)
|
||||
if self._piercing == 0 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
-- Make sure collision is valid
|
||||
if not (is_player or (lua and (lua.is_mob or lua._hittable_by_projectile))) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for node collision
|
||||
if self._lastpos.x~=nil and not self._stuck then
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local vel = self.object:get_velocity()
|
||||
-- Arrow has stopped in one axis, so it probably hit something.
|
||||
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
|
||||
if (math.abs(vel.x) < 0.0001) or (math.abs(vel.z) < 0.0001) or (math.abs(vel.y) < 0.00001) then
|
||||
-- Check for the node to which the arrow is pointing
|
||||
local dir
|
||||
if math.abs(vel.y) < 0.00001 then
|
||||
if self._lastpos.y < pos.y then
|
||||
dir = vector.new(0, 1, 0)
|
||||
else
|
||||
dir = vector.new(0, -1, 0)
|
||||
end
|
||||
else
|
||||
dir = minetest.facedir_to_dir(minetest.dir_to_facedir(minetest.yaw_to_dir(self.object:get_yaw()-YAW_OFFSET)))
|
||||
end
|
||||
self._stuckin = vector.add(dpos, dir)
|
||||
local snode = minetest.get_node(self._stuckin)
|
||||
local sdef = minetest.registered_nodes[snode.name]
|
||||
|
||||
-- If node is non-walkable, unknown or ignore, don't make arrow stuck.
|
||||
-- This causes a deflection in the engine.
|
||||
if not sdef or sdef.walkable == false or snode.name == "ignore" then
|
||||
self._stuckin = nil
|
||||
if self._deflection_cooloff <= 0 then
|
||||
-- Lose 1/3 of velocity on deflection
|
||||
local newvel = vector.multiply(vel, 0.6667)
|
||||
|
||||
self.object:set_velocity(newvel)
|
||||
-- Reset deflection cooloff timer to prevent many deflections happening in quick succession
|
||||
self._deflection_cooloff = 1.0
|
||||
end
|
||||
else
|
||||
|
||||
-- Node was walkable, make arrow stuck
|
||||
self._stuck = true
|
||||
self._stucktimer = 0
|
||||
self._stuckrechecktimer = 0
|
||||
|
||||
self.object:set_velocity(vector.new(0, 0, 0))
|
||||
self.object:set_acceleration(vector.new(0, 0, 0))
|
||||
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
|
||||
if mcl_burning.is_burning(self.object) and snode.name == "mcl_tnt:tnt" then
|
||||
tnt.ignite(self._stuckin)
|
||||
end
|
||||
|
||||
-- Ignite Campfires
|
||||
if mod_campfire and mcl_burning.is_burning(self.object) and minetest.get_item_group(snode.name, "campfire") ~= 0 then
|
||||
mcl_campfires.light_campfire(self._stuckin)
|
||||
end
|
||||
|
||||
-- Activate target
|
||||
if mod_target and snode.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(self._stuckin, 1) --10 redstone ticks
|
||||
end
|
||||
|
||||
-- Push the button! Push, push, push the button!
|
||||
if mod_button and minetest.get_item_group(node.name, "button") > 0 and minetest.get_item_group(node.name, "button_push_by_arrow") == 1 then
|
||||
local bdir = minetest.wallmounted_to_dir(node.param2)
|
||||
-- Check the button orientation
|
||||
if vector.equals(vector.add(dpos, bdir), self._stuckin) then
|
||||
mesecon.push_button(dpos, node)
|
||||
if obj:get_hp() > 0 then
|
||||
if lua then
|
||||
local entity_name = lua.name
|
||||
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
|
||||
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
|
||||
-- TODO: This achievement should be given for the kill, not just a hit
|
||||
local shooter = self._vl_projectile.owner
|
||||
if shooter and shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
|
||||
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
|
||||
awards.unlock(shooter:get_player_name(), "mcl:snipeSkeleton")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif (def and def.liquidtype ~= "none") then
|
||||
-- Slow down arrow in liquids
|
||||
local v = def.liquid_viscosity
|
||||
if not v then
|
||||
v = 0
|
||||
|
||||
-- Item definition entity collision hook
|
||||
local item_def = minetest.registered_items[self._arrow_item]
|
||||
local hook = item_def and item_def._on_collide_with_entity
|
||||
if hook then hook(self, pos, obj) end
|
||||
|
||||
if (self._piercing or 0) > 0 then
|
||||
self._piercing = self._piercing - 1
|
||||
return
|
||||
end
|
||||
--local old_v = self._viscosity
|
||||
self._viscosity = v
|
||||
local vpenalty = math.max(0.1, 0.98 - 0.1 * v)
|
||||
if math.abs(vel.x) > 0.001 then
|
||||
vel.x = vel.x * vpenalty
|
||||
end
|
||||
if math.abs(vel.z) > 0.001 then
|
||||
vel.z = vel.z * vpenalty
|
||||
end
|
||||
self.object:set_velocity(vel)
|
||||
|
||||
-- Because arrows are flagged to survive collisions to allow sticking into blocks, manually remove it now that it
|
||||
-- has collided with an entity
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
-- Update yaw
|
||||
if not self._stuck then
|
||||
local vel = self.object:get_velocity()
|
||||
local yaw = minetest.dir_to_yaw(vel)+YAW_OFFSET
|
||||
local pitch = dir_to_pitch(vel)
|
||||
self.object:set_rotation({ x = 0, y = yaw, z = pitch })
|
||||
end
|
||||
|
||||
-- Update internal variable
|
||||
self._lastpos = pos
|
||||
end
|
||||
|
||||
-- Force recheck of stuck arrows when punched.
|
||||
-- Otherwise, punching has no effect.
|
||||
function ARROW_ENTITY.on_punch(self)
|
||||
if self._stuck then
|
||||
self._stuckrechecktimer = STUCK_RECHECK_TIME
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.get_staticdata(self)
|
||||
local out = {
|
||||
lastpos = self._lastpos,
|
||||
startpos = self._startpos,
|
||||
damage = self._damage,
|
||||
is_critical = self._is_critical,
|
||||
stuck = self._stuck,
|
||||
stuckin = self._stuckin,
|
||||
stuckin_player = self._in_player,
|
||||
}
|
||||
if self._stuck then
|
||||
-- If _stucktimer is missing for some reason, assume the maximum
|
||||
if not self._stucktimer then
|
||||
self._stucktimer = ARROW_TIMEOUT
|
||||
-- Force recheck of stuck arrows when punched.
|
||||
-- Otherwise, punching has no effect.
|
||||
on_punch = function(self)
|
||||
if self._stuck then
|
||||
self._stuckrechecktimer = 5
|
||||
end
|
||||
out.stuckstarttime = minetest.get_gametime() - self._stucktimer
|
||||
end
|
||||
if self._shooter and self._shooter:is_player() then
|
||||
out.shootername = self._shooter:get_player_name()
|
||||
end
|
||||
return minetest.serialize(out)
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
|
||||
self._time_in_air = 1.0
|
||||
local data = minetest.deserialize(staticdata)
|
||||
if data then
|
||||
self._stuck = data.stuck
|
||||
if data.stuck then
|
||||
if data.stuckstarttime then
|
||||
-- First, check if the stuck arrow is aleady past its life timer.
|
||||
-- If yes, delete it.
|
||||
self._stucktimer = minetest.get_gametime() - data.stuckstarttime
|
||||
if self._stucktimer > ARROW_TIMEOUT then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Perform a stuck recheck on the next step.
|
||||
self._stuckrechecktimer = STUCK_RECHECK_TIME
|
||||
|
||||
self._stuckin = data.stuckin
|
||||
end,
|
||||
get_staticdata = function(self)
|
||||
local out = {}
|
||||
local save_fields = self._save_fields
|
||||
for i = 1,#save_fields do
|
||||
local field = save_fields[i]
|
||||
out[field] = self["_"..field]
|
||||
end
|
||||
|
||||
return minetest.serialize(out)
|
||||
end,
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
self.object:set_armor_groups({ immortal = 1 })
|
||||
|
||||
self._time_in_air = 1.0
|
||||
local data = minetest.deserialize(staticdata)
|
||||
if not data then return end
|
||||
|
||||
-- Restore arrow state
|
||||
local save_fields = self._save_fields
|
||||
for i = 1,#save_fields do
|
||||
local field = save_fields[i]
|
||||
self["_"..field] = data[field]
|
||||
end
|
||||
|
||||
if not self._vl_projectile then
|
||||
self._vl_projetile = {}
|
||||
end
|
||||
|
||||
-- Get the remaining arrow state
|
||||
self._lastpos = data.lastpos
|
||||
self._startpos = data.startpos
|
||||
self._damage = data.damage
|
||||
self._is_critical = data.is_critical
|
||||
if data.shootername then
|
||||
local shooter = minetest.get_player_by_name(data.shootername)
|
||||
if shooter and shooter:is_player() then
|
||||
|
@ -513,11 +191,16 @@ function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
|
|||
end
|
||||
|
||||
if data.stuckin_player then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
self.object:set_armor_groups({ immortal = 1 })
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
-- Make the arrow entity available to other mods as a template
|
||||
mcl_bows.arrow_entity = table.copy(arrow_entity)
|
||||
|
||||
vl_projectile.register("mcl_bows:arrow_entity", arrow_entity)
|
||||
|
||||
minetest.register_on_respawnplayer(function(player)
|
||||
for _, obj in pairs(player:get_children()) do
|
||||
|
@ -528,8 +211,6 @@ minetest.register_on_respawnplayer(function(player)
|
|||
end
|
||||
end)
|
||||
|
||||
minetest.register_entity("mcl_bows:arrow_entity", ARROW_ENTITY)
|
||||
|
||||
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
|
||||
minetest.register_craft({
|
||||
output = "mcl_bows:arrow 4",
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
mcl_bows = {}
|
||||
|
||||
-- local arrows = {
|
||||
-- ["mcl_bows:arrow"] = "mcl_bows:arrow_entity",
|
||||
-- }
|
||||
|
||||
local GRAVITY = 9.81
|
||||
local BOW_DURABILITY = 385
|
||||
|
||||
-- Charging time in microseconds
|
||||
|
@ -43,13 +36,16 @@ mcl_fovapi.register_modifier({
|
|||
})
|
||||
|
||||
function mcl_bows.shoot_arrow(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, bow_stack, collectable)
|
||||
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity")
|
||||
if power == nil then
|
||||
power = BOW_MAX_SPEED --19
|
||||
end
|
||||
if damage == nil then
|
||||
damage = 3
|
||||
end
|
||||
power = power or BOW_MAX_SPEED
|
||||
damage = damage or 3
|
||||
|
||||
local obj = vl_projectile.create(arrow_item.."_entity", {
|
||||
pos = pos,
|
||||
dir = dir,
|
||||
velocity = power,
|
||||
owner = shooter,
|
||||
})
|
||||
|
||||
local knockback
|
||||
if bow_stack then
|
||||
local enchantments = mcl_enchanting.get_enchantments(bow_stack)
|
||||
|
@ -65,23 +61,24 @@ function mcl_bows.shoot_arrow(arrow_item, pos, dir, yaw, shooter, power, damage,
|
|||
mcl_burning.set_on_fire(obj, math.huge)
|
||||
end
|
||||
end
|
||||
obj:set_velocity({x=dir.x*power, y=dir.y*power, z=dir.z*power})
|
||||
obj:set_acceleration({x=0, y=-GRAVITY, z=0})
|
||||
obj:set_yaw(yaw-math.pi/2)
|
||||
local le = obj:get_luaentity()
|
||||
le._shooter = shooter
|
||||
le._source_object = shooter
|
||||
le._damage = damage
|
||||
le._is_critical = is_critical
|
||||
le._startpos = pos
|
||||
le._knockback = knockback
|
||||
le._collectable = collectable
|
||||
le._arrow_item = arrow_item
|
||||
local item_def = minetest.registered_items[le._arrow_item]
|
||||
if item_def and item_def._arrow_image then
|
||||
obj:set_properties({ textures = item_def._arrow_image })
|
||||
end
|
||||
minetest.sound_play("mcl_bows_bow_shoot", {pos=pos, max_hear_distance=16}, true)
|
||||
if shooter and shooter:is_player() then
|
||||
if obj:get_luaentity().player == "" then
|
||||
obj:get_luaentity().player = shooter
|
||||
if le.player == "" then
|
||||
le.player = shooter
|
||||
end
|
||||
obj:get_luaentity().node = shooter:get_inventory():get_stack("main", 1):get_name()
|
||||
le.node = shooter:get_inventory():get_stack("main", 1):get_name()
|
||||
end
|
||||
return obj
|
||||
end
|
||||
|
|
|
@ -6,7 +6,6 @@ mcl_bows_s = {}
|
|||
-- ["mcl_bows:arrow"] = "mcl_bows:arrow_entity",
|
||||
-- }
|
||||
|
||||
local GRAVITY = 9.81
|
||||
local BOW_DURABILITY = 385
|
||||
|
||||
-- Charging time in microseconds
|
||||
|
@ -41,13 +40,15 @@ local bow_load = {}
|
|||
local bow_index = {}
|
||||
|
||||
function mcl_bows_s.shoot_arrow_crossbow(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, crossbow_stack, collectable)
|
||||
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity")
|
||||
if power == nil then
|
||||
power = BOW_MAX_SPEED --19
|
||||
end
|
||||
if damage == nil then
|
||||
damage = 3
|
||||
end
|
||||
power = power or BOW_MAX_SPEED
|
||||
damage = damage or 3
|
||||
|
||||
local obj = vl_projectile.create(arrow_item.."_entity", {
|
||||
pos = pos,
|
||||
dir = dir,
|
||||
velocity = power,
|
||||
owner = shooter,
|
||||
})
|
||||
local knockback = 4.875
|
||||
if crossbow_stack then
|
||||
local enchantments = mcl_enchanting.get_enchantments(crossbow_stack)
|
||||
|
@ -57,17 +58,14 @@ function mcl_bows_s.shoot_arrow_crossbow(arrow_item, pos, dir, yaw, shooter, pow
|
|||
obj:get_luaentity()._piercing = 0
|
||||
end
|
||||
end
|
||||
obj:set_velocity({x=dir.x*power, y=dir.y*power, z=dir.z*power})
|
||||
obj:set_acceleration({x=0, y=-GRAVITY, z=0})
|
||||
obj:set_yaw(yaw-math.pi/2)
|
||||
local le = obj:get_luaentity()
|
||||
le._shooter = shooter
|
||||
le._source_object = shooter
|
||||
le._damage = damage
|
||||
le._is_critical = is_critical
|
||||
le._startpos = pos
|
||||
le._knockback = knockback
|
||||
le._collectable = collectable
|
||||
le._arrow_item = arrow_item
|
||||
minetest.sound_play("mcl_bows_crossbow_shoot", {pos=pos, max_hear_distance=16}, true)
|
||||
if shooter and shooter:is_player() then
|
||||
if obj:get_luaentity().player == "" then
|
||||
|
@ -302,21 +300,6 @@ controls.register_on_press(function(player, key, time)
|
|||
if wielditem:get_name()=="mcl_bows:crossbow_loaded" or wielditem:get_name()=="mcl_bows:crossbow_loaded_enchanted" then
|
||||
local enchanted = mcl_enchanting.is_enchanted(wielditem:get_name())
|
||||
local speed, damage
|
||||
local p_load = bow_load[player:get_player_name()]
|
||||
local charge
|
||||
-- Type sanity check
|
||||
if type(p_load) == "number" then
|
||||
charge = minetest.get_us_time() - p_load
|
||||
else
|
||||
-- In case something goes wrong ...
|
||||
-- Just assume minimum charge.
|
||||
charge = 0
|
||||
minetest.log("warning", "[mcl_bows] Player "..player:get_player_name().." fires arrow with non-numeric bow_load!")
|
||||
end
|
||||
charge = math.max(math.min(charge, BOW_CHARGE_TIME_FULL), 0)
|
||||
|
||||
local charge_ratio = charge / BOW_CHARGE_TIME_FULL
|
||||
charge_ratio = math.max(math.min(charge_ratio, 1), 0)
|
||||
|
||||
-- Calculate damage and speed
|
||||
-- Fully charged
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
mcl_bows = {}
|
||||
|
||||
--Bow
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/arrow.lua")
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/bow.lua")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name = mcl_bows
|
||||
author = Arcelmi
|
||||
description = This mod adds bows and arrows for MineClone 2.
|
||||
depends = controls, mcl_particles, mcl_enchanting, mcl_init, mcl_util, mcl_shields, mcl_fovapi, mcl_luck
|
||||
depends = controls, mcl_particles, mcl_enchanting, mcl_init, mcl_util, mcl_shields, mcl_fovapi, mcl_luck, vl_projectile, mcl_explosions
|
||||
optional_depends = awards, mcl_achievements, mcl_core, mcl_mobitems, playerphysics, doc, doc_identifier, mesecons_button
|
||||
|
||||
|
|
|
@ -4,23 +4,14 @@ local math = math
|
|||
local vector = vector
|
||||
|
||||
-- Time in seconds after which a stuck arrow is deleted
|
||||
local ARROW_TIMEOUT = 1
|
||||
-- Time after which stuck arrow is rechecked for being stuck
|
||||
local STUCK_RECHECK_TIME = 0.1
|
||||
|
||||
--local GRAVITY = 9.81
|
||||
local ROCKET_TIMEOUT = 1
|
||||
|
||||
local YAW_OFFSET = -math.pi/2
|
||||
|
||||
local function dir_to_pitch(dir)
|
||||
--local dir2 = vector.normalize(dir)
|
||||
local xz = math.abs(dir.x) + math.abs(dir.z)
|
||||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
local function damage_explosion(self, damagemulitplier)
|
||||
local function damage_explosion(self, damagemulitplier, pos)
|
||||
if self._harmless then return end
|
||||
local p = self.object:get_pos()
|
||||
|
||||
local p = pos or self.object:get_pos()
|
||||
if not p then return end
|
||||
mcl_explosions.explode(p, 3, {})
|
||||
local objects = minetest.get_objects_inside_radius(p, 8)
|
||||
|
@ -36,10 +27,10 @@ local function damage_explosion(self, damagemulitplier)
|
|||
end
|
||||
end
|
||||
|
||||
local function particle_explosion(self)
|
||||
local function particle_explosion(pos)
|
||||
if pos.object then pos = pos.object:get_pos() end
|
||||
local particle_pattern = math.random(1, 3)
|
||||
local fpitch
|
||||
--local true_type
|
||||
local type = math.random(1, 2)
|
||||
local size = math.random(1, 3)
|
||||
local colors = {"red", "yellow", "blue", "green", "white"}
|
||||
|
@ -53,22 +44,16 @@ local function particle_explosion(self)
|
|||
fpitch = math.random(60, 70)
|
||||
end
|
||||
|
||||
--[[if type == 1 then
|
||||
true_type = "Popper"
|
||||
else
|
||||
true_type = "Floof"
|
||||
end]]
|
||||
|
||||
if type == 1 then
|
||||
minetest.sound_play("mcl_bows_firework", {
|
||||
pos = self.object:get_pos(),
|
||||
pos = pos,
|
||||
max_hear_distance = 100,
|
||||
gain = 3.0,
|
||||
pitch = fpitch/100
|
||||
}, true)
|
||||
else
|
||||
minetest.sound_play("mcl_bows_firework_soft", {
|
||||
pos = self.object:get_pos(),
|
||||
pos = pos,
|
||||
max_hear_distance = 100,
|
||||
gain = 4.0,
|
||||
pitch = fpitch/100
|
||||
|
@ -79,8 +64,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-7 * size,-7 * size,-7 * size),
|
||||
maxvel = vector.new(7 * size,7 * size,7 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
|
@ -95,8 +80,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-2 * size,-2 * size,-2 * size),
|
||||
maxvel = vector.new(2 * size,2 * size,2 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
|
@ -111,8 +96,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 100 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-14 * size,-14 * size,-14 * size),
|
||||
maxvel = vector.new(14 * size,14 * size,14 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
|
@ -129,8 +114,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 240 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-5 * size,-5 * size,-5 * size),
|
||||
maxvel = vector.new(5 * size,5 * size,5 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
|
@ -145,8 +130,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 500 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-2 * size,-2 * size,-2 * size),
|
||||
maxvel = vector.new(2 * size,2 * size,2 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
|
@ -161,8 +146,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 350 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-3 * size,-3 * size,-3 * size),
|
||||
maxvel = vector.new(3 * size,3 * size,3 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
|
@ -179,8 +164,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-6 * size,-4 * size,-6 * size),
|
||||
maxvel = vector.new(6 * size,4 * size,6 * size),
|
||||
minexptime = .6 * size,
|
||||
|
@ -195,8 +180,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 120 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-8 * size,6 * size,-8 * size),
|
||||
maxvel = vector.new(8 * size,6 * size,8 * size),
|
||||
minexptime = .6 * size,
|
||||
|
@ -211,8 +196,8 @@ local function particle_explosion(self)
|
|||
minetest.add_particlespawner({
|
||||
amount = 130 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-3 * size,3 * size,-3 * size),
|
||||
maxvel = vector.new(3 * size,3 * size,3 * size),
|
||||
minexptime = .6 * size,
|
||||
|
@ -230,17 +215,12 @@ local function particle_explosion(self)
|
|||
|
||||
end
|
||||
|
||||
local mod_awards = minetest.get_modpath("awards") and minetest.get_modpath("mcl_achievements")
|
||||
local mod_button = minetest.get_modpath("mesecons_button")
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
local enable_pvp = minetest.settings:get_bool("enable_pvp")
|
||||
|
||||
minetest.register_craftitem("mcl_bows:rocket", {
|
||||
description = S("Arrow"),
|
||||
_tt_help = S("Ammunition").."\n"..S("Damage from bow: 1-10").."\n"..S("Damage from dispenser: 3"),
|
||||
_doc_items_longdesc = S("Arrows are ammunition for bows and dispensers.").."\n"..
|
||||
S("An arrow fired from a bow has a regular damage of 1-9. At full charge, there's a 20% chance of a critical hit dealing 10 damage instead. An arrow fired from a dispenser always deals 3 damage.").."\n"..
|
||||
S("Arrows might get stuck on solid blocks and can be retrieved again. They are also capable of pushing wooden buttons."),
|
||||
S("An arrow fired from a bow has a regular damage of 1-9. At full charge, there's a 20% chance of a critical hit dealing 10 damage instead. An arrow fired from a dispenser always deals 3 damage.").."\n"..
|
||||
S("Arrows might get stuck on solid blocks and can be retrieved again. They are also capable of pushing wooden buttons."),
|
||||
_doc_items_usagehelp = S("To use arrows as ammunition for a bow, just put them anywhere in your inventory, they will be used up automatically. To use arrows as ammunition for a dispenser, place them in the dispenser's inventory. To retrieve an arrow that sticks in a block, simply walk close to it."),
|
||||
inventory_image = "mcl_bows_rocket.png",
|
||||
groups = { ammo=1, ammo_crossbow=1, ammo_bow_regular=1 },
|
||||
|
@ -250,448 +230,53 @@ S("Arrows might get stuck on solid blocks and can be retrieved again. They are a
|
|||
local yaw = math.atan2(dropdir.z, dropdir.x) + YAW_OFFSET
|
||||
mcl_bows.shoot_arrow(itemstack:get_name(), shootpos, dropdir, yaw, nil, 19, 3)
|
||||
end,
|
||||
})
|
||||
_on_collide_with_entity = function(self, _, obj)
|
||||
if self._in_player == false then
|
||||
pos = self.object:get_pos()
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, self.object:get_velocity())
|
||||
|
||||
|
||||
|
||||
local ARROW_ENTITY={
|
||||
physical = true,
|
||||
pointable = false,
|
||||
visual = "mesh",
|
||||
mesh = "mcl_bows_rocket.obj",
|
||||
visual_size = {x=2.5, y=2.5},
|
||||
textures = {"mcl_bows_rocket.png"},
|
||||
collisionbox = {-0.19, -0.125, -0.19, 0.19, 0.125, 0.19},
|
||||
collide_with_objects = false,
|
||||
_fire_damage_resistant = true,
|
||||
|
||||
_lastpos={},
|
||||
_startpos=nil,
|
||||
_damage=1, -- Damage on impact
|
||||
_is_critical=false, -- Whether this arrow would deal critical damage
|
||||
_stuck=false, -- Whether arrow is stuck
|
||||
_fuse=nil,-- Amount of time (in seconds) the arrow has been stuck so far
|
||||
_fuserechecktimer=nil,-- An additional timer for periodically re-checking the stuck status of an arrow
|
||||
_stuckin=nil, --Position of node in which arow is stuck.
|
||||
_shooter=nil, -- ObjectRef of player or mob who shot it
|
||||
_is_arrow = true,
|
||||
|
||||
_viscosity=0, -- Viscosity of node the arrow is currently in
|
||||
_deflection_cooloff=0, -- Cooloff timer after an arrow deflection, to prevent many deflections in quick succession
|
||||
}
|
||||
|
||||
-- Destroy arrow entity self at pos and drops it as an item
|
||||
local function spawn_item(self, pos)
|
||||
if not minetest.is_creative_enabled("") then
|
||||
local item = minetest.add_item(pos, "mcl_bows:rocket")
|
||||
item:set_velocity({x=0, y=0, z=0})
|
||||
item:set_yaw(self.object:get_yaw())
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
local function damage_particles(pos, is_critical)
|
||||
if is_critical then
|
||||
minetest.add_particlespawner({
|
||||
amount = 15,
|
||||
time = 0.1,
|
||||
minpos = {x=pos.x-0.5, y=pos.y-0.5, z=pos.z-0.5},
|
||||
maxpos = {x=pos.x+0.5, y=pos.y+0.5, z=pos.z+0.5},
|
||||
minvel = {x=-0.1, y=-0.1, z=-0.1},
|
||||
maxvel = {x=0.1, y=0.1, z=0.1},
|
||||
minacc = {x=0, y=0, z=0},
|
||||
maxacc = {x=0, y=0, z=0},
|
||||
minexptime = 1,
|
||||
maxexptime = 2,
|
||||
minsize = 1.5,
|
||||
maxsize = 1.5,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_crit.png^[colorize:#bc7a57:127",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_step(self, dtime)
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
-- mcl_burning.tick may remove object immediately
|
||||
if not self.object:get_pos() then return end
|
||||
|
||||
self._time_in_air = self._time_in_air + .001
|
||||
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
local dpos = table.copy(pos) -- digital pos
|
||||
dpos = vector.round(dpos)
|
||||
local node = minetest.get_node(dpos)
|
||||
|
||||
if not self._fuse then
|
||||
self._fuse = 0
|
||||
end
|
||||
if not self._fuserechecktimer then
|
||||
self._fuserechecktimer = 0
|
||||
end
|
||||
|
||||
self._fuse = self._fuse + dtime
|
||||
self._fuserechecktimer = self._fuserechecktimer + dtime
|
||||
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
self._stuck = true
|
||||
end
|
||||
if self._stuck then
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
local eploded_particle = particle_explosion(pos)
|
||||
damage_explosion(self, eploded_particle * 17, pos)
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
-- Drop arrow as item when it is no longer stuck
|
||||
-- FIXME: Arrows are a bit slow to react and continue to float in mid air for a few seconds.
|
||||
if self._fuserechecktimer > STUCK_RECHECK_TIME then
|
||||
local stuckin_def
|
||||
if self._stuckin then
|
||||
stuckin_def = minetest.registered_nodes[minetest.get_node(self._stuckin).name]
|
||||
end
|
||||
-- TODO: In MC, arrow just falls down without turning into an item
|
||||
if stuckin_def and stuckin_def.walkable == false then
|
||||
spawn_item(self, pos)
|
||||
return
|
||||
end
|
||||
self._fuserechecktimer = 0
|
||||
end
|
||||
-- Pickup arrow if player is nearby (not in Creative Mode)
|
||||
local objects = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _,obj in ipairs(objects) do
|
||||
if obj:is_player() then
|
||||
if self._collectable and not minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
if obj:get_inventory():room_for_item("main", "mcl_bows:rocket") then
|
||||
obj:get_inventory():add_item("main", "mcl_bows:rocket")
|
||||
minetest.sound_play("item_drop_pickup", {
|
||||
pos = pos,
|
||||
max_hear_distance = 16,
|
||||
gain = 1.0,
|
||||
}, true)
|
||||
end
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- Check for object "collision". Done every tick (hopefully this is not too stressing)
|
||||
else
|
||||
local arrow_entity = mcl_bows.arrow_entity
|
||||
local rocket_entity = table.copy(arrow_entity)
|
||||
table.update(rocket_entity,{
|
||||
mesh = "mcl_bows_rocket.obj",
|
||||
textures = {"mcl_bows_rocket.png"},
|
||||
visual_size = {x=2.5, y=2.5},
|
||||
save_fields = {
|
||||
"stuck", "fuse", "stuckin", "lastpos", "startpos", "damage", "is_critical", "shootername",
|
||||
},
|
||||
_fuse=nil,-- Amount of time (in seconds) the arrow has been stuck so far
|
||||
_fuserechecktimer=nil,-- An additional timer for periodically re-checking the stuck status of an arrow
|
||||
})
|
||||
rocket_entity.on_step = function(self, dtime)
|
||||
self._fuse = (self._fuse or 0) + dtime
|
||||
|
||||
if self._in_player == false then
|
||||
minetest.add_particlespawner({
|
||||
amount = 1,
|
||||
time = .0001,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-0.1,-0.1,-0.1),
|
||||
maxvel = vector.new(0.1,0.1,0.1),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 0.5,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_rocket_particle.png",
|
||||
glow = 1,
|
||||
})
|
||||
end
|
||||
-- We just check for any hurtable objects nearby.
|
||||
-- The radius of 3 is fairly liberal, but anything lower than than will cause
|
||||
-- arrow to hilariously go through mobs often.
|
||||
-- TODO: Implement an ACTUAL collision detection (engine support needed).
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1.5)
|
||||
local closest_object
|
||||
local closest_distance
|
||||
|
||||
if self._deflection_cooloff > 0 then
|
||||
self._deflection_cooloff = self._deflection_cooloff - dtime
|
||||
end
|
||||
|
||||
-- Iterate through all objects and remember the closest attackable object
|
||||
local arrow_dir = self.object:get_velocity()
|
||||
--create a raycast from the arrow based on the velocity of the arrow to deal with lag
|
||||
local raycast = minetest.raycast(pos, vector.add(pos, vector.multiply(arrow_dir, 0.1)), true, false)
|
||||
for hitpoint in raycast do
|
||||
if hitpoint.type == "object" then
|
||||
-- find the closest object that is in the way of the arrow
|
||||
local ok = false
|
||||
if hitpoint.ref:is_player() and enable_pvp then
|
||||
ok = true
|
||||
elseif not hitpoint.ref:is_player() and hitpoint.ref:get_luaentity() then
|
||||
if (hitpoint.ref:get_luaentity().is_mob or hitpoint.ref:get_luaentity()._hittable_by_projectile) then
|
||||
ok = true
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
local dist = vector.distance(hitpoint.ref:get_pos(), pos)
|
||||
if not closest_object or not closest_distance then
|
||||
closest_object = hitpoint.ref
|
||||
closest_distance = dist
|
||||
elseif dist < closest_distance then
|
||||
closest_object = hitpoint.ref
|
||||
closest_distance = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- If an attackable object was found, we will damage the closest one only
|
||||
|
||||
if closest_object then
|
||||
local obj = closest_object
|
||||
local is_player = obj:is_player()
|
||||
local lua = obj:get_luaentity()
|
||||
if obj == self._shooter and self._time_in_air > 1.02 or obj ~= self._shooter and (is_player or (lua and (lua.is_mob or lua._hittable_by_projectile))) then
|
||||
if obj:get_hp() > 0 then
|
||||
-- Check if there is no solid node between arrow and object
|
||||
local ray = minetest.raycast(self.object:get_pos(), obj:get_pos(), true)
|
||||
for pointed_thing in ray do
|
||||
if pointed_thing.type == "object" and pointed_thing.ref == closest_object then
|
||||
-- Target reached! We can proceed now.
|
||||
break
|
||||
elseif pointed_thing.type == "node" then
|
||||
local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name
|
||||
local def = minetest.registered_nodes[nn]
|
||||
if (not def) or def.walkable then
|
||||
-- There's a node in the way. Delete arrow without damage
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Punch target object but avoid hurting enderman.
|
||||
if not lua or lua.name ~= "mobs_mc:rover" then
|
||||
if self._in_player == false then
|
||||
damage_particles(self.object:get_pos(), self._is_critical)
|
||||
end
|
||||
if mcl_burning.is_burning(self.object) then
|
||||
mcl_burning.set_on_fire(obj, 5)
|
||||
end
|
||||
if self._in_player == false then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, self.object:get_velocity())
|
||||
if obj:is_player() then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if is_player then
|
||||
if self._shooter and self._shooter:is_player() and self._in_player == false then
|
||||
-- “Ding” sound for hitting another player
|
||||
minetest.sound_play({name="mcl_bows_hit_player", gain=0.1}, {to_player=self._shooter:get_player_name()}, true)
|
||||
end
|
||||
end
|
||||
|
||||
if lua then
|
||||
local entity_name = lua.name
|
||||
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
|
||||
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
|
||||
-- TODO: This achievement should be given for the kill, not just a hit
|
||||
if self._shooter and self._shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
|
||||
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
|
||||
awards.unlock(self._shooter:get_player_name(), "mcl:snipeSkeleton")
|
||||
end
|
||||
end
|
||||
end
|
||||
if self._in_player == false then
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
end
|
||||
end
|
||||
if not obj:is_player() then
|
||||
mcl_burning.extinguish(self.object)
|
||||
if self._piercing == 0 then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
if self._fuse > ROCKET_TIMEOUT then
|
||||
self._stuck = true
|
||||
end
|
||||
if self._stuck and self._fuse > ROCKET_TIMEOUT then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
-- Check for node collision
|
||||
if self._lastpos.x~=nil and not self._stuck then
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local vel = self.object:get_velocity()
|
||||
-- Arrow has stopped in one axis, so it probably hit something.
|
||||
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
|
||||
if (math.abs(vel.x) < 0.0001) or (math.abs(vel.z) < 0.0001) or (math.abs(vel.y) < 0.00001) then
|
||||
-- Check for the node to which the arrow is pointing
|
||||
local dir
|
||||
if math.abs(vel.y) < 0.00001 then
|
||||
if self._lastpos.y < pos.y then
|
||||
dir = {x=0, y=1, z=0}
|
||||
else
|
||||
dir = {x=0, y=-1, z=0}
|
||||
end
|
||||
else
|
||||
dir = minetest.facedir_to_dir(minetest.dir_to_facedir(minetest.yaw_to_dir(self.object:get_yaw()-YAW_OFFSET)))
|
||||
end
|
||||
self._stuckin = vector.add(dpos, dir)
|
||||
local snode = minetest.get_node(self._stuckin)
|
||||
local sdef = minetest.registered_nodes[snode.name]
|
||||
|
||||
-- If node is non-walkable, unknown or ignore, don't make arrow stuck.
|
||||
-- This causes a deflection in the engine.
|
||||
if not sdef or sdef.walkable == false or snode.name == "ignore" then
|
||||
self._stuckin = nil
|
||||
if self._deflection_cooloff <= 0 then
|
||||
-- Lose 1/3 of velocity on deflection
|
||||
local newvel = vector.multiply(vel, 0.6667)
|
||||
|
||||
self.object:set_velocity(newvel)
|
||||
-- Reset deflection cooloff timer to prevent many deflections happening in quick succession
|
||||
self._deflection_cooloff = 1.0
|
||||
end
|
||||
else
|
||||
|
||||
-- Node was walkable, make arrow stuck
|
||||
self._stuck = true
|
||||
self._fuserechecktimer = 0
|
||||
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
|
||||
if mcl_burning.is_burning(self.object) and snode.name == "mcl_tnt:tnt" then
|
||||
tnt.ignite(self._stuckin)
|
||||
end
|
||||
|
||||
-- Activate target
|
||||
if mod_target and snode.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(self._stuckin, 1) --10 redstone ticks
|
||||
end
|
||||
|
||||
-- Push the button! Push, push, push the button!
|
||||
if mod_button and minetest.get_item_group(node.name, "button") > 0 and minetest.get_item_group(node.name, "button_push_by_arrow") == 1 then
|
||||
local bdir = minetest.wallmounted_to_dir(node.param2)
|
||||
-- Check the button orientation
|
||||
if vector.equals(vector.add(dpos, bdir), self._stuckin) then
|
||||
mesecon.push_button(dpos, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif (def and def.liquidtype ~= "none") then
|
||||
-- Slow down arrow in liquids
|
||||
local v = def.liquid_viscosity
|
||||
if not v then
|
||||
v = 0
|
||||
end
|
||||
--local old_v = self._viscosity
|
||||
self._viscosity = v
|
||||
local vpenalty = math.max(0.1, 0.98 - 0.1 * v)
|
||||
if math.abs(vel.x) > 0.001 then
|
||||
vel.x = vel.x * vpenalty
|
||||
end
|
||||
if math.abs(vel.z) > 0.001 then
|
||||
vel.z = vel.z * vpenalty
|
||||
end
|
||||
self.object:set_velocity(vel)
|
||||
end
|
||||
end
|
||||
|
||||
-- Update yaw
|
||||
if not self._stuck then
|
||||
local vel = self.object:get_velocity()
|
||||
local yaw = minetest.dir_to_yaw(vel)+YAW_OFFSET
|
||||
local pitch = dir_to_pitch(vel)
|
||||
self.object:set_rotation({ x = 0, y = yaw, z = pitch })
|
||||
end
|
||||
|
||||
-- Update internal variable
|
||||
self._lastpos={x=pos.x, y=pos.y, z=pos.z}
|
||||
-- Perform normal arrow behaviors
|
||||
arrow_entity.on_step(self, dtime)
|
||||
end
|
||||
|
||||
-- Force recheck of stuck arrows when punched.
|
||||
-- Otherwise, punching has no effect.
|
||||
function ARROW_ENTITY.on_punch(self)
|
||||
if self._stuck then
|
||||
self._fuserechecktimer = STUCK_RECHECK_TIME
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.get_staticdata(self)
|
||||
local out = {
|
||||
lastpos = self._lastpos,
|
||||
startpos = self._startpos,
|
||||
damage = self._damage,
|
||||
is_critical = self._is_critical,
|
||||
stuck = self._stuck,
|
||||
stuckin = self._stuckin,
|
||||
}
|
||||
if self._stuck then
|
||||
-- If _fuse is missing for some reason, assume the maximum
|
||||
if not self._fuse then
|
||||
self._fuse = ARROW_TIMEOUT
|
||||
end
|
||||
out.stuckstarttime = minetest.get_gametime() - self._fuse
|
||||
end
|
||||
if self._shooter and self._shooter:is_player() then
|
||||
out.shootername = self._shooter:get_player_name()
|
||||
end
|
||||
return minetest.serialize(out)
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
|
||||
self._time_in_air = 1.0
|
||||
self._in_player = false
|
||||
local data = minetest.deserialize(staticdata)
|
||||
if data then
|
||||
self._stuck = data.stuck
|
||||
if data.stuck then
|
||||
if data.stuckstarttime then
|
||||
-- First, check if the stuck arrow is aleady past its life timer.
|
||||
-- If yes, delete it.
|
||||
self._fuse = minetest.get_gametime() - data.stuckstarttime
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self._fuse = 2
|
||||
-- Perform a stuck recheck on the next step.
|
||||
self._fuserechecktimer = STUCK_RECHECK_TIME
|
||||
|
||||
self._stuckin = data.stuckin
|
||||
end
|
||||
|
||||
-- Get the remaining arrow state
|
||||
self._lastpos = data.lastpos
|
||||
self._startpos = data.startpos
|
||||
self._damage = data.damage
|
||||
self._is_critical = data.is_critical
|
||||
if data.shootername then
|
||||
local shooter = minetest.get_player_by_name(data.shootername)
|
||||
if shooter and shooter:is_player() then
|
||||
self._shooter = shooter
|
||||
end
|
||||
end
|
||||
end
|
||||
self.object:set_armor_groups({ immortal = 1 })
|
||||
end
|
||||
|
||||
minetest.register_entity("mcl_bows:rocket_entity", ARROW_ENTITY)
|
||||
vl_projectile.register("mcl_bows:rocket_entity", rocket_entity)
|
||||
|
||||
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
|
||||
minetest.register_craft({
|
||||
|
|
|
@ -261,6 +261,14 @@ function mcl_campfires.register_campfire(name, def)
|
|||
},
|
||||
_mcl_blast_resistance = 2,
|
||||
_mcl_hardness = 2,
|
||||
_vl_projectile = {
|
||||
on_collide = function(projectile, pos, node, node_def)
|
||||
-- Ignite Campfires
|
||||
if mcl_burning.is_burning(projectile) then
|
||||
mcl_campfires.light_campfire(pos)
|
||||
end
|
||||
end
|
||||
},
|
||||
after_dig_node = function(pos, node, oldmeta, digger)
|
||||
campfire_drops(pos, digger, def.drops, name.."_lit")
|
||||
end,
|
||||
|
|
|
@ -312,7 +312,7 @@ bobber_ENTITY.on_step = bobber_on_step
|
|||
|
||||
minetest.register_entity("mcl_fishing:bobber_entity", bobber_ENTITY)
|
||||
|
||||
local flying_bobber_ENTITY={
|
||||
vl_projectile.register("mcl_fishing:flying_bobber_entity", {
|
||||
physical = false,
|
||||
timer=0,
|
||||
textures = {"mcl_fishing_bobber.png"}, --FIXME: Replace with correct texture.
|
||||
|
@ -323,35 +323,35 @@ local flying_bobber_ENTITY={
|
|||
get_staticdata = mcl_throwing.get_staticdata,
|
||||
on_activate = mcl_throwing.on_activate,
|
||||
|
||||
_vl_projectile = {
|
||||
behaviors = {
|
||||
vl_projectile.collides_with_solids,
|
||||
},
|
||||
collides_with = { "group:liquid" },
|
||||
on_collide_with_solid = function(self, pos, node)
|
||||
local player = self._owner
|
||||
|
||||
self._remove = true
|
||||
self.object:remove()
|
||||
|
||||
-- Make sure the player field is valid for when we create the floating bobber
|
||||
if not player then return end
|
||||
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
if not def then return end
|
||||
|
||||
if def.walkable or def.liquidtype == "flowing" or def.liquidtype == "source" then
|
||||
local ent = minetest.add_entity(pos, "mcl_fishing:bobber_entity"):get_luaentity()
|
||||
ent.player = player
|
||||
ent.child = true
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
_lastpos={},
|
||||
_thrower = nil,
|
||||
objtype="fishing",
|
||||
}
|
||||
|
||||
-- Movement function of flying bobber
|
||||
local function flying_bobber_on_step(self, dtime)
|
||||
self.timer=self.timer+dtime
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
--local player = minetest.get_player_by_name(self._thrower)
|
||||
|
||||
-- Destroy when hitting a solid node
|
||||
if self._lastpos.x~=nil then
|
||||
if (def and (def.walkable or def.liquidtype == "flowing" or def.liquidtype == "source")) or not def then
|
||||
local ent = minetest.add_entity(self._lastpos, "mcl_fishing:bobber_entity"):get_luaentity()
|
||||
ent.player = self._thrower
|
||||
ent.child = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set lastpos-->Node will be added at last pos outside the node
|
||||
end
|
||||
|
||||
flying_bobber_ENTITY.on_step = flying_bobber_on_step
|
||||
|
||||
minetest.register_entity("mcl_fishing:flying_bobber_entity", flying_bobber_ENTITY)
|
||||
})
|
||||
|
||||
mcl_throwing.register_throwable_object("mcl_fishing:flying_bobber", "mcl_fishing:flying_bobber_entity", 5)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
local PARTICLE_DENSITY = 4
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
|
||||
local function lingering_image(colorstring, opacity)
|
||||
|
@ -53,7 +54,7 @@ minetest.register_globalstep(function(dtime)
|
|||
else
|
||||
texture = "mcl_particles_effect.png"
|
||||
end
|
||||
linger_particles(pos, d, texture, vals.color)
|
||||
linger_particles(pos, PARTICLE_DENSITY, texture, vals.color)
|
||||
|
||||
-- -- Extinguish fire if water bottle
|
||||
-- if vals.is_water then
|
||||
|
@ -181,40 +182,46 @@ function mcl_potions.register_lingering(name, descr, color, def)
|
|||
|
||||
local w = 0.7
|
||||
|
||||
minetest.register_entity(id.."_flying",{
|
||||
local particle_texture
|
||||
if name == "water" then
|
||||
particle_texture = "mcl_particles_droplet_bottle.png"
|
||||
else
|
||||
if def.instant then
|
||||
particle_texture = "mcl_particles_instant_effect.png"
|
||||
else
|
||||
particle_texture = "mcl_particles_effect.png"
|
||||
end
|
||||
end
|
||||
|
||||
local function on_collide(self, pos)
|
||||
local potency = self._potency or 0
|
||||
local plus = self._plus or 0
|
||||
add_lingering_effect(pos, color, def, name == "water", potency, plus)
|
||||
linger_particles(pos, PARTICLE_DENSITY, particle_texture, color)
|
||||
if def.on_splash then def.on_splash(pos, potency+1) end
|
||||
end
|
||||
vl_projectile.register(id.."_flying",{
|
||||
textures = {lingering_image(color)},
|
||||
hp_max = 1,
|
||||
visual_size = {x=w/2,y=w/2},
|
||||
collisionbox = {-0.1,-0.1,-0.1,0.1,0.1,0.1},
|
||||
pointable = false,
|
||||
on_step = function(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(pos)
|
||||
local n = node.name
|
||||
local g = minetest.get_item_group(n, "liquid")
|
||||
local d = 4
|
||||
if mod_target and n == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
if n ~= "air" and n ~= "mcl_portals:portal" and n ~= "mcl_portals:portal_end" and g == 0 or mcl_potions.is_obj_hit(self, pos) then
|
||||
minetest.sound_play("mcl_potions_breaking_glass", {pos = pos, max_hear_distance = 16, gain = 1})
|
||||
local potency = self._potency or 0
|
||||
local plus = self._plus or 0
|
||||
add_lingering_effect(pos, color, def, name == "water", potency, plus)
|
||||
local texture
|
||||
if name == "water" then
|
||||
texture = "mcl_particles_droplet_bottle.png"
|
||||
else
|
||||
if def.instant then
|
||||
texture = "mcl_particles_instant_effect.png"
|
||||
else
|
||||
texture = "mcl_particles_effect.png"
|
||||
end
|
||||
_vl_projectile = {
|
||||
behaviors = {
|
||||
vl_projectile.collides_with_entities,
|
||||
vl_projectile.collides_with_solids,
|
||||
},
|
||||
on_collide_with_entity = on_collide,
|
||||
on_collide_with_solid = function(self, pos, node)
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
linger_particles(pos, d, texture, color)
|
||||
if def.on_splash then def.on_splash(pos, potency+1) end
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
|
||||
on_collide(self, pos)
|
||||
end,
|
||||
sounds = {
|
||||
on_collision = {"mcl_potions_breaking_glass", {max_hear_distance = 16, gain = 1}},
|
||||
},
|
||||
},
|
||||
})
|
||||
end
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
name = mcl_potions
|
||||
depends = mcl_core, mcl_farming, mcl_flowers, mcl_mobitems, mcl_mobs, mcl_fishing, mcl_bows, mcl_end, mcl_weather, playerphysics, mcl_wip
|
||||
depends = mcl_core, mcl_farming, mcl_flowers, mcl_mobitems, mcl_mobs, mcl_fishing, mcl_bows, mcl_end, mcl_weather, playerphysics, mcl_wip, vl_projectile
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
local GRAVITY = tonumber(minetest.settings:get("movement_gravity"))
|
||||
local REDUX_MAP = {7/8,0.5,0.25}
|
||||
local PARTICLE_DIAMETER = 0.1
|
||||
local PARTICLE_MIN_VELOCITY = vector.new(-2, 0, -2)
|
||||
local PARTICLE_MAX_VELOCITY = vector.new( 2, 2, 2)
|
||||
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
|
||||
|
@ -10,7 +14,6 @@ local function splash_image(colorstring, opacity)
|
|||
return "mcl_potions_splash_overlay.png^[colorize:"..colorstring..":"..tostring(opacity).."^mcl_potions_splash_bottle.png"
|
||||
end
|
||||
|
||||
|
||||
function mcl_potions.register_splash(name, descr, color, def)
|
||||
local id = "mcl_potions:"..name.."_splash"
|
||||
local longdesc = def._longdesc
|
||||
|
@ -22,6 +25,7 @@ function mcl_potions.register_splash(name, descr, color, def)
|
|||
end
|
||||
local groups = {brewitem=1, bottle=1, splash_potion=1, _mcl_potion=1}
|
||||
if def.nocreative then groups.not_in_creative_inventory = 1 end
|
||||
|
||||
minetest.register_craftitem(id, {
|
||||
description = descr,
|
||||
_tt_help = def._tt,
|
||||
|
@ -73,112 +77,123 @@ function mcl_potions.register_splash(name, descr, color, def)
|
|||
|
||||
local w = 0.7
|
||||
|
||||
minetest.register_entity(id.."_flying",{
|
||||
textures = {splash_image(color)},
|
||||
hp_max = 1,
|
||||
visual_size = {x=w/2,y=w/2},
|
||||
collisionbox = {-0.1,-0.1,-0.1,0.1,0.1,0.1},
|
||||
pointable = false,
|
||||
on_step = function(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(pos)
|
||||
local n = node.name
|
||||
local g = minetest.get_item_group(n, "liquid")
|
||||
local d = 0.1
|
||||
local redux_map = {7/8,0.5,0.25}
|
||||
if mod_target and n == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
if n ~= "air" and n ~= "mcl_portals:portal" and n ~= "mcl_portals:portal_end" and g == 0 or mcl_potions.is_obj_hit(self, pos) then
|
||||
minetest.sound_play("mcl_potions_breaking_glass", {pos = pos, max_hear_distance = 16, gain = 1})
|
||||
local texture, acc
|
||||
if name == "water" then
|
||||
texture = "mcl_particles_droplet_bottle.png"
|
||||
acc = {x=0, y=-GRAVITY, z=0}
|
||||
else
|
||||
if def.instant then
|
||||
texture = "mcl_particles_instant_effect.png"
|
||||
else
|
||||
texture = "mcl_particles_effect.png"
|
||||
end
|
||||
acc = {x=0, y=0, z=0}
|
||||
end
|
||||
minetest.add_particlespawner({
|
||||
amount = 50,
|
||||
time = 0.1,
|
||||
minpos = {x=pos.x-d, y=pos.y+0.5, z=pos.z-d},
|
||||
maxpos = {x=pos.x+d, y=pos.y+0.5+d, z=pos.z+d},
|
||||
minvel = {x=-2, y=0, z=-2},
|
||||
maxvel = {x=2, y=2, z=2},
|
||||
minacc = acc,
|
||||
maxacc = acc,
|
||||
minexptime = 0.5,
|
||||
maxexptime = 1.25,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = true,
|
||||
vertical = false,
|
||||
texture = texture.."^[colorize:"..color..":127"
|
||||
})
|
||||
-- Precompute particle texture and acceleration
|
||||
local particle_texture, particle_acc
|
||||
if name == "water" then
|
||||
particle_texture = "mcl_particles_droplet_bottle.png"
|
||||
particle_acc = {x=0, y=-GRAVITY, z=0}
|
||||
else
|
||||
if def.instant then
|
||||
particle_texture = "mcl_particles_instant_effect.png"
|
||||
else
|
||||
particle_texture = "mcl_particles_effect.png"
|
||||
end
|
||||
particle_acc = {x=0, y=0, z=0}
|
||||
end
|
||||
particle_texture = particle_texture.."^[colorize:"..color..":127"
|
||||
|
||||
local function splash_effects(self, pos, def, range)
|
||||
minetest.add_particlespawner({
|
||||
amount = 50,
|
||||
time = 0.1,
|
||||
minpos = vector.offset(pos, -PARTICLE_DIAMETER, 0.5, -PARTICLE_DIAMETER),
|
||||
maxpos = vector.offset(pos, PARTICLE_DIAMETER, 0.5, PARTICLE_DIAMETER),
|
||||
minvel = PARTICLE_MIN_VELOCITY,
|
||||
maxvel = PARTICLE_MAX_VELOCITY,
|
||||
minacc = particle_acc,
|
||||
maxacc = particle_acc,
|
||||
minexptime = 0.5,
|
||||
maxexptime = 1.25,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = true,
|
||||
vertical = false,
|
||||
texture = particle_texture,
|
||||
})
|
||||
|
||||
for _,obj in pairs(minetest.get_objects_inside_radius(pos, range)) do
|
||||
-- Make sure the potion can interact with this object
|
||||
local entity = obj:get_luaentity()
|
||||
if obj:is_player() or entity and entity.is_mob then
|
||||
local potency = self._potency or 0
|
||||
local plus = self._plus or 0
|
||||
|
||||
if def.on_splash then def.on_splash(pos, potency+1) end
|
||||
for _,obj in pairs(minetest.get_objects_inside_radius(pos, 4)) do
|
||||
|
||||
local entity = obj:get_luaentity()
|
||||
if obj:is_player() or entity and entity.is_mob then
|
||||
local pos2 = obj:get_pos()
|
||||
local rad = math.floor(math.sqrt((pos2.x-pos.x)^2 + (pos2.y-pos.y)^2 + (pos2.z-pos.z)^2))
|
||||
|
||||
local pos2 = obj:get_pos()
|
||||
local rad = math.floor(math.sqrt((pos2.x-pos.x)^2 + (pos2.y-pos.y)^2 + (pos2.z-pos.z)^2))
|
||||
-- Apply effect list
|
||||
if def._effect_list then
|
||||
for name, details in pairs(def._effect_list) do
|
||||
local ef_level
|
||||
local dur
|
||||
|
||||
if def._effect_list then
|
||||
local ef_level
|
||||
local dur
|
||||
for name, details in pairs(def._effect_list) do
|
||||
if details.uses_level then
|
||||
ef_level = details.level + details.level_scaling * (potency)
|
||||
else
|
||||
ef_level = details.level
|
||||
end
|
||||
if details.dur_variable then
|
||||
dur = details.dur * math.pow(mcl_potions.PLUS_FACTOR, plus)
|
||||
if potency>0 and details.uses_level then
|
||||
dur = dur / math.pow(mcl_potions.POTENT_FACTOR, potency)
|
||||
end
|
||||
dur = dur * mcl_potions.SPLASH_FACTOR
|
||||
else
|
||||
dur = details.dur
|
||||
end
|
||||
if details.effect_stacks then
|
||||
ef_level = ef_level + mcl_potions.get_effect_level(obj, name)
|
||||
end
|
||||
if rad > 0 then
|
||||
mcl_potions.give_effect_by_level(name, obj, ef_level, redux_map[rad]*dur)
|
||||
else
|
||||
mcl_potions.give_effect_by_level(name, obj, ef_level, dur)
|
||||
end
|
||||
end
|
||||
if details.uses_level then
|
||||
ef_level = details.level + details.level_scaling * potency
|
||||
else
|
||||
ef_level = details.level
|
||||
end
|
||||
|
||||
if def.custom_effect then
|
||||
local power = (potency+1) * mcl_potions.SPLASH_FACTOR
|
||||
if rad > 0 then
|
||||
def.custom_effect(obj, redux_map[rad] * power, plus)
|
||||
else
|
||||
def.custom_effect(obj, power, plus)
|
||||
if details.dur_variable then
|
||||
dur = details.dur * math.pow(mcl_potions.PLUS_FACTOR, plus)
|
||||
if potency>0 and details.uses_level then
|
||||
dur = dur / math.pow(mcl_potions.POTENT_FACTOR, potency)
|
||||
end
|
||||
dur = dur * mcl_potions.SPLASH_FACTOR
|
||||
else
|
||||
dur = details.dur
|
||||
end
|
||||
|
||||
if details.effect_stacks then
|
||||
ef_level = ef_level + mcl_potions.get_effect_level(obj, name)
|
||||
end
|
||||
|
||||
if rad > 0 then
|
||||
mcl_potions.give_effect_by_level(name, obj, ef_level, REDUX_MAP[rad]*dur)
|
||||
else
|
||||
mcl_potions.give_effect_by_level(name, obj, ef_level, dur)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.object:remove()
|
||||
|
||||
if def.custom_effect then
|
||||
local power = (potency+1) * mcl_potions.SPLASH_FACTOR
|
||||
if rad > 0 then
|
||||
def.custom_effect(obj, REDUX_MAP[rad] * power, plus)
|
||||
else
|
||||
def.custom_effect(obj, power, plus)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
end
|
||||
end
|
||||
|
||||
vl_projectile.register(id.."_flying",{
|
||||
textures = {splash_image(color)},
|
||||
hp_max = 1,
|
||||
visual_size = {x=w/2,y=w/2},
|
||||
collisionbox = {-0.1,-0.1,-0.1,0.1,0.1,0.1},
|
||||
_vl_projectile = {
|
||||
behaviors = {
|
||||
vl_projectile.collides_with_entities,
|
||||
vl_projectile.collides_with_solids,
|
||||
},
|
||||
on_collide_with_solid = function(self, pos, node)
|
||||
splash_effects(self, pos, def, 4)
|
||||
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(pos, 0.4) -- 4 redstone ticks
|
||||
end
|
||||
end,
|
||||
on_collide_with_entity = function(self, pos, obj)
|
||||
splash_effects(self, pos, def, 4)
|
||||
end,
|
||||
sounds = {
|
||||
on_collision = {"mcl_potions_breaking_glass", {max_hear_distance = 16, gain = 1}, true},
|
||||
},
|
||||
},
|
||||
pointable = false,
|
||||
})
|
||||
end
|
||||
|
||||
--[[local function time_string(dur)
|
||||
return math.floor(dur/60)..string.format(":%02d",math.floor(dur % 60))
|
||||
end]]
|
||||
|
|
|
@ -1,24 +1,9 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
|
||||
local math = math
|
||||
|
||||
-- Time in seconds after which a stuck arrow is deleted
|
||||
local ARROW_TIMEOUT = 60
|
||||
-- Time after which stuck arrow is rechecked for being stuck
|
||||
local STUCK_RECHECK_TIME = 5
|
||||
|
||||
--local GRAVITY = 9.81
|
||||
|
||||
local YAW_OFFSET = -math.pi/2
|
||||
|
||||
local function dir_to_pitch(dir)
|
||||
--local dir2 = vector.normalize(dir)
|
||||
local xz = math.abs(dir.x) + math.abs(dir.z)
|
||||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
local function arrow_image(colorstring, opacity)
|
||||
if not opacity then
|
||||
opacity = 127
|
||||
|
@ -28,20 +13,16 @@ end
|
|||
|
||||
local how_to_shoot = minetest.registered_items["mcl_bows:arrow"]._doc_items_usagehelp
|
||||
|
||||
local mod_awards = minetest.get_modpath("awards") and minetest.get_modpath("mcl_achievements")
|
||||
local mod_button = minetest.get_modpath("mesecons_button")
|
||||
local enable_pvp = minetest.settings:get_bool("enable_pvp")
|
||||
|
||||
local arrow_longdesc = minetest.registered_items["mcl_bows:arrow"]._doc_items_longdesc or ""
|
||||
local arrow_tt = minetest.registered_items["mcl_bows:arrow"]._tt_help or ""
|
||||
|
||||
function mcl_potions.register_arrow(name, desc, color, def)
|
||||
|
||||
local longdesc = def._longdesc or ""
|
||||
local tt = def._tt or ""
|
||||
local groups = {ammo=1, ammo_bow=1, brewitem=1, _mcl_potion=1}
|
||||
if def.nocreative then groups.not_in_creative_inventory = 1 end
|
||||
minetest.register_craftitem("mcl_potions:"..name.."_arrow", {
|
||||
local arrow_item = "mcl_potions:"..name.."_arrow"
|
||||
minetest.register_craftitem(arrow_item, {
|
||||
description = desc,
|
||||
_tt_help = arrow_tt .. "\n" .. tt,
|
||||
_dynamic_tt = def._dynamic_tt,
|
||||
|
@ -63,464 +44,44 @@ function mcl_potions.register_arrow(name, desc, color, def)
|
|||
local yaw = math.atan2(dropdir.z, dropdir.x) + YAW_OFFSET
|
||||
mcl_bows.shoot_arrow(itemstack:get_name(), shootpos, dropdir, yaw, nil, 19, 3)
|
||||
end,
|
||||
})
|
||||
_arrow_image = arrow_image(color, 100),
|
||||
_on_collide_with_entity = function(self, pos, obj)
|
||||
local potency = self._potency or 0
|
||||
local plus = self._plus or 0
|
||||
|
||||
if def._effect_list then
|
||||
for name, details in pairs(def._effect_list) do
|
||||
local ef_level = details.level
|
||||
if details.uses_level then
|
||||
ef_level = details.level + details.level_scaling * potency
|
||||
end
|
||||
|
||||
-- This is a fake node, used as model for the arrow entity.
|
||||
-- It's not supposed to be usable as item or real node.
|
||||
-- TODO: Use a proper mesh for the arrow entity
|
||||
minetest.register_node("mcl_potions:"..name.."_arrow_box", {
|
||||
drawtype = "nodebox",
|
||||
is_ground_content = false,
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
-- Shaft
|
||||
{-6.5/17, -1.5/17, -1.5/17, -4.5/17, 1.5/17, 1.5/17},
|
||||
{-4.5/17, -0.5/17, -0.5/17, 5.5/17, 0.5/17, 0.5/17},
|
||||
{5.5/17, -1.5/17, -1.5/17, 6.5/17, 1.5/17, 1.5/17},
|
||||
-- Tip
|
||||
{-4.5/17, 2.5/17, 2.5/17, -3.5/17, -2.5/17, -2.5/17},
|
||||
{-8.5/17, 0.5/17, 0.5/17, -6.5/17, -0.5/17, -0.5/17},
|
||||
-- Fletching
|
||||
{6.5/17, 1.5/17, 1.5/17, 7.5/17, 2.5/17, 2.5/17},
|
||||
{7.5/17, -2.5/17, 2.5/17, 6.5/17, -1.5/17, 1.5/17},
|
||||
{7.5/17, 2.5/17, -2.5/17, 6.5/17, 1.5/17, -1.5/17},
|
||||
{6.5/17, -1.5/17, -1.5/17, 7.5/17, -2.5/17, -2.5/17},
|
||||
local dur = details.dur
|
||||
if details.dur_variable then
|
||||
dur = details.dur * math.pow(mcl_potions.PLUS_FACTOR, plus)
|
||||
if potency>0 and details.uses_level then
|
||||
dur = dur / math.pow(mcl_potions.POTENT_FACTOR, potency)
|
||||
end
|
||||
end
|
||||
dur = dur * mcl_potions.SPLASH_FACTOR
|
||||
|
||||
{7.5/17, 2.5/17, 2.5/17, 8.5/17, 3.5/17, 3.5/17},
|
||||
{8.5/17, -3.5/17, 3.5/17, 7.5/17, -2.5/17, 2.5/17},
|
||||
{8.5/17, 3.5/17, -3.5/17, 7.5/17, 2.5/17, -2.5/17},
|
||||
{7.5/17, -2.5/17, -2.5/17, 8.5/17, -3.5/17, -3.5/17},
|
||||
}
|
||||
},
|
||||
tiles = arrow_image(color, 100),
|
||||
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "opaque" or false,
|
||||
paramtype = "light",
|
||||
paramtype2 = "facedir",
|
||||
sunlight_propagates = true,
|
||||
groups = {not_in_creative_inventory=1, dig_immediate=3},
|
||||
drop = "",
|
||||
node_placement_prediction = "",
|
||||
on_construct = function(pos)
|
||||
minetest.log("error", "[mcl_potions] Trying to construct mcl_potions:"..name.."arrow_box at "..minetest.pos_to_string(pos))
|
||||
minetest.remove_node(pos)
|
||||
if details.effect_stacks then
|
||||
ef_level = ef_level + mcl_potions.get_effect_level(obj, name)
|
||||
end
|
||||
mcl_potions.give_effect_by_level(name, obj, ef_level, dur)
|
||||
end
|
||||
end
|
||||
if def.custom_effect then def.custom_effect(obj, potency+1, plus) end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
local ARROW_ENTITY={
|
||||
physical = true,
|
||||
visual = "mesh",
|
||||
mesh = "mcl_bows_arrow.obj",
|
||||
visual_size = {x=-1, y=1},
|
||||
textures = arrow_image(color, 100),
|
||||
collisionbox = {-0.19, -0.125, -0.19, 0.19, 0.125, 0.19},
|
||||
collide_with_objects = false,
|
||||
|
||||
_lastpos={},
|
||||
_startpos=nil,
|
||||
_damage=1, -- Damage on impact
|
||||
_stuck=false, -- Whether arrow is stuck
|
||||
_stucktimer=nil,-- Amount of time (in seconds) the arrow has been stuck so far
|
||||
_stuckrechecktimer=nil,-- An additional timer for periodically re-checking the stuck status of an arrow
|
||||
_stuckin=nil, --Position of node in which arow is stuck.
|
||||
_shooter=nil, -- ObjectRef of player or mob who shot it
|
||||
|
||||
_viscosity=0, -- Viscosity of node the arrow is currently in
|
||||
_deflection_cooloff=0, -- Cooloff timer after an arrow deflection, to prevent many deflections in quick succession
|
||||
}
|
||||
|
||||
-- Destroy arrow entity self at pos and drops it as an item
|
||||
local function spawn_item(self, pos)
|
||||
if not minetest.is_creative_enabled("") then
|
||||
local item = minetest.add_item(pos, "mcl_potions:"..name.."_arrow")
|
||||
item:set_velocity({x=0, y=0, z=0})
|
||||
item:set_yaw(self.object:get_yaw())
|
||||
end
|
||||
self.object:remove()
|
||||
-- Entity for older-style arrows
|
||||
local arrow_entity = table.copy(mcl_bows.arrow_entity)
|
||||
arrow_entity.on_activate = function(self, staticdata, dtime_s)
|
||||
mcl_bows.arrow_entity.on_activate(self, staticdata, dtime_s)
|
||||
self._arrow_item = arrow_item
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_step(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
local dpos = table.copy(pos) -- digital pos
|
||||
dpos = vector.round(dpos)
|
||||
local node = minetest.get_node(dpos)
|
||||
|
||||
if self._stuck then
|
||||
self._stucktimer = self._stucktimer + dtime
|
||||
self._stuckrechecktimer = self._stuckrechecktimer + dtime
|
||||
if self._stucktimer > ARROW_TIMEOUT then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
-- Drop arrow as item when it is no longer stuck
|
||||
-- FIXME: Arrows are a bit slow to react and continue to float in mid air for a few seconds.
|
||||
if self._stuckrechecktimer > STUCK_RECHECK_TIME then
|
||||
local stuckin_def
|
||||
if self._stuckin then
|
||||
stuckin_def = minetest.registered_nodes[minetest.get_node(self._stuckin).name]
|
||||
end
|
||||
-- TODO: In MC, arrow just falls down without turning into an item
|
||||
if stuckin_def and stuckin_def.walkable == false then
|
||||
spawn_item(self, pos)
|
||||
return
|
||||
end
|
||||
self._stuckrechecktimer = 0
|
||||
end
|
||||
-- Pickup arrow if player is nearby (not in Creative Mode)
|
||||
local objects = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _,obj in ipairs(objects) do
|
||||
if obj:is_player() then
|
||||
if not minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
if obj:get_inventory():room_for_item("main", "mcl_potions:"..name.."_arrow") then
|
||||
obj:get_inventory():add_item("main", "mcl_potions:"..name.."_arrow")
|
||||
minetest.sound_play("item_drop_pickup", {
|
||||
pos = pos,
|
||||
max_hear_distance = 16,
|
||||
gain = 1.0,
|
||||
}, true)
|
||||
end
|
||||
end
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for object "collision". Done every tick (hopefully this is not too stressing)
|
||||
else
|
||||
|
||||
if self._damage == 10 or self._damage == 9 then
|
||||
minetest.add_particlespawner({
|
||||
amount = 1,
|
||||
time = .001,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-0.1,-0.1,-0.1),
|
||||
maxvel = vector.new(0.1,0.1,0.1),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 0.5,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mobs_mc_arrow_particle.png",
|
||||
glow = 1,
|
||||
})
|
||||
end
|
||||
-- We just check for any hurtable objects nearby.
|
||||
-- The radius of 3 is fairly liberal, but anything lower than than will cause
|
||||
-- arrow to hilariously go through mobs often.
|
||||
-- TODO: Implement an ACTUAL collision detection (engine support needed).
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1.5)
|
||||
local closest_object
|
||||
local closest_distance
|
||||
|
||||
if self._deflection_cooloff > 0 then
|
||||
self._deflection_cooloff = self._deflection_cooloff - dtime
|
||||
end
|
||||
|
||||
-- Iterate through all objects and remember the closest attackable object
|
||||
local arrow_dir = self.object:get_velocity()
|
||||
--create a raycast from the arrow based on the velocity of the arrow to deal with lag
|
||||
local raycast = minetest.raycast(pos, vector.add(pos, vector.multiply(arrow_dir, 0.1)), true, false)
|
||||
for hitpoint in raycast do
|
||||
if hitpoint.type == "object" then
|
||||
-- find the closest object that is in the way of the arrow
|
||||
local ok = false
|
||||
if hitpoint.ref:is_player() and enable_pvp then
|
||||
ok = true
|
||||
elseif not hitpoint.ref:is_player() and hitpoint.ref:get_luaentity() then
|
||||
if (hitpoint.ref:get_luaentity().is_mob or hitpoint.ref:get_luaentity()._hittable_by_projectile) then
|
||||
ok = true
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
local dist = vector.distance(hitpoint.ref:get_pos(), pos)
|
||||
if not closest_object or not closest_distance then
|
||||
closest_object = hitpoint.ref
|
||||
closest_distance = dist
|
||||
elseif dist < closest_distance then
|
||||
closest_object = hitpoint.ref
|
||||
closest_distance = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- If an attackable object was found, we will damage the closest one only
|
||||
if closest_object then
|
||||
local obj = closest_object
|
||||
local is_player = obj:is_player()
|
||||
local lua = obj:get_luaentity()
|
||||
if obj ~= self._shooter and (is_player or (lua and lua.is_mob)) then
|
||||
|
||||
if obj:get_hp() > 0 then
|
||||
-- Check if there is no solid node between arrow and object
|
||||
local ray = minetest.raycast(self.object:get_pos(), obj:get_pos(), true)
|
||||
for pointed_thing in ray do
|
||||
if pointed_thing.type == "object" and pointed_thing.ref == closest_object then
|
||||
-- Target reached! We can proceed now.
|
||||
break
|
||||
elseif pointed_thing.type == "node" then
|
||||
local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name
|
||||
local nodedef = minetest.registered_nodes[nn]
|
||||
if (not nodedef) or nodedef.walkable then
|
||||
-- There's a node in the way. Delete arrow without damage
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local potency = self._potency or 0
|
||||
local plus = self._plus or 0
|
||||
|
||||
-- Punch target object but avoid hurting enderman.
|
||||
if lua then
|
||||
if lua.name ~= "mobs_mc:rover" then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, nil)
|
||||
if def._effect_list then
|
||||
local ef_level
|
||||
local dur
|
||||
for name, details in pairs(def._effect_list) do
|
||||
if details.uses_level then
|
||||
ef_level = details.level + details.level_scaling * (potency)
|
||||
else
|
||||
ef_level = details.level
|
||||
end
|
||||
if details.dur_variable then
|
||||
dur = details.dur * math.pow(mcl_potions.PLUS_FACTOR, plus)
|
||||
if potency>0 and details.uses_level then
|
||||
dur = dur / math.pow(mcl_potions.POTENT_FACTOR, potency)
|
||||
end
|
||||
else
|
||||
dur = details.dur
|
||||
end
|
||||
dur = dur * mcl_potions.SPLASH_FACTOR
|
||||
if details.effect_stacks then
|
||||
ef_level = ef_level + mcl_potions.get_effect_level(obj, name)
|
||||
end
|
||||
mcl_potions.give_effect_by_level(name, obj, ef_level, dur)
|
||||
end
|
||||
end
|
||||
if def.custom_effect then def.custom_effect(obj, potency+1, plus) end
|
||||
end
|
||||
else
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, nil)
|
||||
if def._effect_list then
|
||||
local ef_level
|
||||
local dur
|
||||
for name, details in pairs(def._effect_list) do
|
||||
if details.uses_level then
|
||||
ef_level = details.level + details.level_scaling * (potency)
|
||||
else
|
||||
ef_level = details.level
|
||||
end
|
||||
if details.dur_variable then
|
||||
dur = details.dur * math.pow(mcl_potions.PLUS_FACTOR, plus)
|
||||
if potency>0 and details.uses_level then
|
||||
dur = dur / math.pow(mcl_potions.POTENT_FACTOR, potency)
|
||||
end
|
||||
else
|
||||
dur = details.dur
|
||||
end
|
||||
dur = dur * mcl_potions.SPLASH_FACTOR
|
||||
if details.effect_stacks then
|
||||
ef_level = ef_level + mcl_potions.get_effect_level(obj, name)
|
||||
end
|
||||
mcl_potions.give_effect_by_level(name, obj, ef_level, dur)
|
||||
end
|
||||
end
|
||||
if def.custom_effect then def.custom_effect(obj, potency+1, plus) end
|
||||
end
|
||||
|
||||
if is_player then
|
||||
if self._shooter and self._shooter:is_player() then
|
||||
-- “Ding” sound for hitting another player
|
||||
minetest.sound_play({name="mcl_bows_hit_player", gain=0.1}, {to_player=self._shooter:get_player_name()}, true)
|
||||
end
|
||||
end
|
||||
|
||||
if lua then
|
||||
local entity_name = lua.name
|
||||
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
|
||||
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
|
||||
-- TODO: This achievement should be given for the kill, not just a hit
|
||||
if self._shooter and self._shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
|
||||
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
|
||||
awards.unlock(self._shooter:get_player_name(), "mcl:snipeSkeleton")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for node collision
|
||||
if self._lastpos.x~=nil and not self._stuck then
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
local vel = self.object:get_velocity()
|
||||
-- Arrow has stopped in one axis, so it probably hit something.
|
||||
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
|
||||
if (math.abs(vel.x) < 0.0001) or (math.abs(vel.z) < 0.0001) or (math.abs(vel.y) < 0.00001) then
|
||||
-- Check for the node to which the arrow is pointing
|
||||
local dir
|
||||
if math.abs(vel.y) < 0.00001 then
|
||||
if self._lastpos.y < pos.y then
|
||||
dir = {x=0, y=1, z=0}
|
||||
else
|
||||
dir = {x=0, y=-1, z=0}
|
||||
end
|
||||
else
|
||||
dir = minetest.facedir_to_dir(minetest.dir_to_facedir(minetest.yaw_to_dir(self.object:get_yaw()-YAW_OFFSET)))
|
||||
end
|
||||
self._stuckin = vector.add(dpos, dir)
|
||||
local snode = minetest.get_node(self._stuckin)
|
||||
local sdef = minetest.registered_nodes[snode.name]
|
||||
|
||||
-- If node is non-walkable, unknown or ignore, don't make arrow stuck.
|
||||
-- This causes a deflection in the engine.
|
||||
if not sdef or sdef.walkable == false or snode.name == "ignore" then
|
||||
self._stuckin = nil
|
||||
if self._deflection_cooloff <= 0 then
|
||||
-- Lose 1/3 of velocity on deflection
|
||||
local newvel = vector.multiply(vel, 0.6667)
|
||||
|
||||
self.object:set_velocity(newvel)
|
||||
-- Reset deflection cooloff timer to prevent many deflections happening in quick succession
|
||||
self._deflection_cooloff = 1.0
|
||||
end
|
||||
else
|
||||
|
||||
-- Node was walkable, make arrow stuck
|
||||
self._stuck = true
|
||||
self._stucktimer = 0
|
||||
self._stuckrechecktimer = 0
|
||||
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
|
||||
-- Activate target
|
||||
if mod_target and snode.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(self._stuckin, 1) --10 redstone ticks
|
||||
end
|
||||
|
||||
-- Push the button! Push, push, push the button!
|
||||
if mod_button and minetest.get_item_group(node.name, "button") > 0 and minetest.get_item_group(node.name, "button_push_by_arrow") == 1 then
|
||||
local bdir = minetest.wallmounted_to_dir(node.param2)
|
||||
-- Check the button orientation
|
||||
if vector.equals(vector.add(dpos, bdir), self._stuckin) then
|
||||
mesecon.push_button(dpos, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif (nodedef and nodedef.liquidtype ~= "none") then
|
||||
-- Slow down arrow in liquids
|
||||
local v = nodedef.liquid_viscosity
|
||||
if not v then
|
||||
v = 0
|
||||
end
|
||||
--local old_v = self._viscosity
|
||||
self._viscosity = v
|
||||
local vpenalty = math.max(0.1, 0.98 - 0.1 * v)
|
||||
if math.abs(vel.x) > 0.001 then
|
||||
vel.x = vel.x * vpenalty
|
||||
end
|
||||
if math.abs(vel.z) > 0.001 then
|
||||
vel.z = vel.z * vpenalty
|
||||
end
|
||||
self.object:set_velocity(vel)
|
||||
end
|
||||
end
|
||||
|
||||
-- Update yaw
|
||||
if not self._stuck then
|
||||
local vel = self.object:get_velocity()
|
||||
local yaw = minetest.dir_to_yaw(vel)+YAW_OFFSET
|
||||
local pitch = dir_to_pitch(vel)
|
||||
self.object:set_rotation({ x = 0, y = yaw, z = pitch })
|
||||
end
|
||||
|
||||
-- Update internal variable
|
||||
self._lastpos={x=pos.x, y=pos.y, z=pos.z}
|
||||
end
|
||||
|
||||
-- Force recheck of stuck arrows when punched.
|
||||
-- Otherwise, punching has no effect.
|
||||
function ARROW_ENTITY.on_punch(self)
|
||||
if self._stuck then
|
||||
self._stuckrechecktimer = STUCK_RECHECK_TIME
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.get_staticdata(self)
|
||||
local out = {
|
||||
lastpos = self._lastpos,
|
||||
startpos = self._startpos,
|
||||
damage = self._damage,
|
||||
stuck = self._stuck,
|
||||
stuckin = self._stuckin,
|
||||
}
|
||||
if self._stuck then
|
||||
-- If _stucktimer is missing for some reason, assume the maximum
|
||||
if not self._stucktimer then
|
||||
self._stucktimer = ARROW_TIMEOUT
|
||||
end
|
||||
out.stuckstarttime = minetest.get_gametime() - self._stucktimer
|
||||
end
|
||||
if self._shooter and self._shooter:is_player() then
|
||||
out.shootername = self._shooter:get_player_name()
|
||||
end
|
||||
return minetest.serialize(out)
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
|
||||
local data = minetest.deserialize(staticdata)
|
||||
if data then
|
||||
self._stuck = data.stuck
|
||||
if data.stuck then
|
||||
if data.stuckstarttime then
|
||||
-- First, check if the stuck arrow is aleady past its life timer.
|
||||
-- If yes, delete it.
|
||||
self._stucktimer = minetest.get_gametime() - data.stuckstarttime
|
||||
if self._stucktimer > ARROW_TIMEOUT then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Perform a stuck recheck on the next step.
|
||||
self._stuckrechecktimer = STUCK_RECHECK_TIME
|
||||
|
||||
self._stuckin = data.stuckin
|
||||
end
|
||||
|
||||
-- Get the remaining arrow state
|
||||
self._lastpos = data.lastpos
|
||||
self._startpos = data.startpos
|
||||
self._damage = data.damage
|
||||
if data.shootername then
|
||||
local shooter = minetest.get_player_by_name(data.shootername)
|
||||
if shooter and shooter:is_player() then
|
||||
self._shooter = shooter
|
||||
end
|
||||
end
|
||||
end
|
||||
self.object:set_armor_groups({ immortal = 1 })
|
||||
end
|
||||
|
||||
minetest.register_entity("mcl_potions:"..name.."_arrow_entity", ARROW_ENTITY)
|
||||
minetest.register_entity("mcl_potions:"..name.."_arrow_entity", arrow_entity)
|
||||
|
||||
if minetest.get_modpath("mcl_bows") then
|
||||
minetest.register_craft({
|
||||
|
@ -531,7 +92,6 @@ function mcl_potions.register_arrow(name, desc, color, def)
|
|||
{"mcl_bows:arrow","mcl_bows:arrow","mcl_bows:arrow"}
|
||||
}
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
local how_to_throw = S("Use the punch key to throw.")
|
||||
|
||||
-- Egg
|
||||
minetest.register_craftitem("mcl_throwing:egg", {
|
||||
description = S("Egg"),
|
||||
_tt_help = S("Throwable").."\n"..S("Chance to hatch chicks when broken"),
|
||||
_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."),
|
||||
_doc_items_usagehelp = how_to_throw,
|
||||
inventory_image = "mcl_throwing_egg.png",
|
||||
stack_max = 64,
|
||||
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:egg_entity"),
|
||||
_on_dispense = mcl_throwing.dispense_function,
|
||||
groups = { craftitem = 1 },
|
||||
})
|
||||
|
||||
local function egg_spawn_chicks(pos)
|
||||
-- 1/8 chance to spawn a chick
|
||||
if math.random(1,8) ~= 1 then return end
|
||||
|
||||
mcl_mobs.spawn_child(pos, "mobs_mc:chicken")
|
||||
|
||||
-- BONUS ROUND: 1/32 chance to spawn 3 additional chicks
|
||||
if math.random(1,32) ~= 1 then return end
|
||||
|
||||
mcl_mobs.spawn_child(vector.offset(pos, 0.7, 0, 0 ), "mobs_mc:chicken")
|
||||
mcl_mobs.spawn_child(vector.offset(pos, -0.7, 0, -0.7), "mobs_mc:chicken")
|
||||
mcl_mobs.spawn_child(vector.offset(pos, -0.7, 0, 0.7), "mobs_mc:chicken")
|
||||
end
|
||||
|
||||
vl_projectile.register("mcl_throwing:egg_entity",{
|
||||
physical = false,
|
||||
timer=0,
|
||||
textures = {"mcl_throwing_egg.png"},
|
||||
visual_size = {x=0.45, y=0.45},
|
||||
collisionbox = {0,0,0,0,0,0},
|
||||
pointable = false,
|
||||
|
||||
get_staticdata = mcl_throwing.get_staticdata,
|
||||
on_activate = mcl_throwing.on_activate,
|
||||
|
||||
on_step = vl_projectile.update_projectile,
|
||||
_lastpos={},
|
||||
_thrower = nil,
|
||||
_vl_projectile = {
|
||||
behaviors = {
|
||||
vl_projectile.collides_with_solids,
|
||||
vl_projectile.collides_with_entities,
|
||||
},
|
||||
allow_punching = function(self, _, _, object)
|
||||
if self._owner == mcl_util.get_entity_id(object) then
|
||||
return self.timer > 1
|
||||
end
|
||||
|
||||
local le = object:get_luaentity()
|
||||
return le and (le.is_mob or le._hittable_by_projectile) or object:is_player()
|
||||
end,
|
||||
on_collide_with_solid = function(self, pos, node)
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
|
||||
local vel = self.object:get_velocity()
|
||||
pos = vector.round(pos + vector.normalize(vel) * -0.35)
|
||||
|
||||
egg_spawn_chicks(pos)
|
||||
end,
|
||||
on_collide_with_entity = function(self, pos, obj)
|
||||
local vel = self.object:get_velocity()
|
||||
pos = vector.round(pos + vector.normalize(vel) * -0.35)
|
||||
|
||||
egg_spawn_chicks(pos)
|
||||
end,
|
||||
sounds = {
|
||||
on_collision = {"mcl_throwing_egg_impact", {max_hear_distance=10, gain=0.5}, true}
|
||||
},
|
||||
},
|
||||
})
|
||||
mcl_throwing.register_throwable_object("mcl_throwing:egg", "mcl_throwing:egg_entity", 22)
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local math = math
|
||||
local vector = vector
|
||||
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
local how_to_throw = S("Use the punch key to throw.")
|
||||
|
||||
-- Ender Pearl
|
||||
minetest.register_craftitem("mcl_throwing:ender_pearl", {
|
||||
description = S("Ender Pearl"),
|
||||
_tt_help = S("Throwable").."\n"..minetest.colorize(mcl_colors.YELLOW, S("Teleports you on impact for cost of 5 HP")),
|
||||
_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."),
|
||||
_doc_items_usagehelp = how_to_throw,
|
||||
wield_image = "mcl_throwing_ender_pearl.png",
|
||||
inventory_image = "mcl_throwing_ender_pearl.png",
|
||||
stack_max = 16,
|
||||
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:ender_pearl_entity"),
|
||||
groups = { transport = 1 },
|
||||
})
|
||||
|
||||
function on_collide(self, pos, node)
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
|
||||
if node.name == "ignore" then
|
||||
-- FIXME: This also means the player loses an ender pearl for throwing into unloaded areas
|
||||
return
|
||||
end
|
||||
|
||||
-- Make sure we have a reference to the player
|
||||
local player = self._thrower and minetest.get_player_by_name(self._thrower)
|
||||
if not player then return end
|
||||
|
||||
-- Teleport and hurt player
|
||||
|
||||
-- First determine good teleport position
|
||||
local dir = vector.zero()
|
||||
|
||||
local v = self.object:get_velocity()
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def and node_def.walkable then
|
||||
local vc = vector.normalize(v) -- vector for calculating
|
||||
-- Node is walkable, we have to find a place somewhere outside of that node
|
||||
|
||||
-- Zero-out the two axes with a lower absolute value than the axis with the strongest force
|
||||
local lv, ld = math.abs(vc.y), "y"
|
||||
if math.abs(vc.x) > lv then
|
||||
lv, ld = math.abs(vc.x), "x"
|
||||
end
|
||||
if math.abs(vc.z) > lv then
|
||||
ld = "z" --math.abs(vc.z)
|
||||
end
|
||||
if ld ~= "x" then vc.x = 0 end
|
||||
if ld ~= "y" then vc.y = 0 end
|
||||
if ld ~= "z" then vc.z = 0 end
|
||||
|
||||
-- Final tweaks to the teleporting pos, based on direction
|
||||
-- Impact from the side
|
||||
dir.x = vc.x * -1
|
||||
dir.z = vc.z * -1
|
||||
|
||||
-- Special case: top or bottom of node
|
||||
if vc.y > 0 then
|
||||
-- We need more space when impact is from below
|
||||
dir.y = -2.3
|
||||
elseif vc.y < 0 then
|
||||
-- Standing on top
|
||||
dir.y = 0.5
|
||||
end
|
||||
end
|
||||
-- If node was not walkable, no modification to pos is made.
|
||||
|
||||
-- Final teleportation position
|
||||
local telepos = vector.add(pos, dir)
|
||||
local telenode = minetest.get_node(telepos)
|
||||
|
||||
--[[ It may be possible that telepos is walkable due to the algorithm.
|
||||
Especially when the ender pearl is faster horizontally than vertical.
|
||||
This applies final fixing, just to be sure we're not in a walkable node ]]
|
||||
if not minetest.registered_nodes[telenode.name] or minetest.registered_nodes[telenode.name].walkable then
|
||||
if v.y < 0 then
|
||||
telepos.y = telepos.y + 0.5
|
||||
else
|
||||
telepos.y = telepos.y - 2.3
|
||||
end
|
||||
end
|
||||
|
||||
local oldpos = player:get_pos()
|
||||
-- Teleport and hurt player
|
||||
player:set_pos(telepos)
|
||||
player:set_hp(player:get_hp() - 5, { type = "fall", from = "mod" })
|
||||
|
||||
-- 5% chance to spawn endermite at the player's origin
|
||||
if math.random(1,20) == 1 then
|
||||
minetest.add_entity(oldpos, "mobs_mc:endermite")
|
||||
end
|
||||
end
|
||||
|
||||
-- Ender pearl entity
|
||||
vl_projectile.register("mcl_throwing:ender_pearl_entity",{
|
||||
physical = false,
|
||||
timer=0,
|
||||
textures = {"mcl_throwing_ender_pearl.png"},
|
||||
visual_size = {x=0.9, y=0.9},
|
||||
collisionbox = {0,0,0,0,0,0},
|
||||
pointable = false,
|
||||
|
||||
get_staticdata = mcl_throwing.get_staticdata,
|
||||
on_activate = mcl_throwing.on_activate,
|
||||
|
||||
on_step = vl_projectile.update_projectile,
|
||||
_lastpos={},
|
||||
_thrower = nil, -- Player ObjectRef of the player who threw the ender pearl
|
||||
_vl_projectile = {
|
||||
behaviors = {
|
||||
vl_projectile.collides_with_solids,
|
||||
vl_projectile.collides_with_entities,
|
||||
},
|
||||
collides_with = {
|
||||
"mcl_core:vine", "mcl_core:deadbush",
|
||||
"group:flower", "group:sapling",
|
||||
"group:plant", "group:mushroom",
|
||||
},
|
||||
allow_punching = function(self, _, _, object)
|
||||
if self._owner == mcl_util.get_entity_id(object) then
|
||||
return self.timer > 1
|
||||
end
|
||||
|
||||
local le = object:get_luaentity()
|
||||
return le and (le.is_mob or le._hittable_by_projectile) or object:is_player()
|
||||
end,
|
||||
on_collide_with_entity = function(self, pos, entity)
|
||||
on_collide(self, pos, minetest.get_node(pos))
|
||||
end,
|
||||
on_collide_with_solid = on_collide,
|
||||
},
|
||||
})
|
||||
mcl_throwing.register_throwable_object("mcl_throwing:ender_pearl", "mcl_throwing:ender_pearl_entity", 22)
|
||||
|
|
@ -6,32 +6,29 @@ local modpath = minetest.get_modpath(minetest.get_current_modname())
|
|||
-- Snowballs and other throwable items
|
||||
--
|
||||
|
||||
local GRAVITY = tonumber(minetest.settings:get("movement_gravity"))
|
||||
|
||||
local entity_mapping = {}
|
||||
local velocities = {}
|
||||
|
||||
function mcl_throwing.register_throwable_object(name, entity, velocity)
|
||||
entity_mapping[name] = entity
|
||||
velocities[name] = velocity
|
||||
assert(minetest.registered_entities[entity], entity.." not registered")
|
||||
assert(minetest.registered_entities[entity]._vl_projectile)
|
||||
end
|
||||
|
||||
function mcl_throwing.throw(throw_item, pos, dir, velocity, thrower)
|
||||
if velocity == nil then
|
||||
velocity = velocities[throw_item]
|
||||
end
|
||||
if velocity == nil then
|
||||
velocity = 22
|
||||
end
|
||||
velocity = velocity or velocities[throw_item] or 22
|
||||
minetest.sound_play("mcl_throwing_throw", {pos=pos, gain=0.4, max_hear_distance=16}, true)
|
||||
|
||||
local itemstring = ItemStack(throw_item):get_name()
|
||||
local obj = minetest.add_entity(pos, entity_mapping[itemstring])
|
||||
obj:set_velocity({x=dir.x*velocity, y=dir.y*velocity, z=dir.z*velocity})
|
||||
obj:set_acceleration({x=dir.x*-3, y=-GRAVITY, z=dir.z*-3})
|
||||
if thrower then
|
||||
obj:get_luaentity()._thrower = thrower
|
||||
end
|
||||
local obj = vl_projectile.create(entity_mapping[itemstring], {
|
||||
pos = pos,
|
||||
owner_id = thrower,
|
||||
dir = dir,
|
||||
velocity = velocity,
|
||||
drag = 3,
|
||||
})
|
||||
obj:get_luaentity()._thrower = thrower
|
||||
return obj
|
||||
end
|
||||
|
||||
|
@ -71,6 +68,7 @@ end
|
|||
|
||||
function mcl_throwing.on_activate(self, staticdata, dtime_s)
|
||||
local data = minetest.deserialize(staticdata)
|
||||
self._staticdata = data
|
||||
if data then
|
||||
self._lastpos = data._lastpos
|
||||
self._thrower = data._thrower
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mcl_throwing
|
||||
depends = mcl_colors
|
||||
depends = mcl_colors, vl_projectile
|
||||
optional_depends = mcl_core, mcl_mobitems, doc, mcl_target
|
||||
|
|
|
@ -1,322 +1,7 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
|
||||
local math = math
|
||||
local vector = vector
|
||||
dofile(modpath.."/snowball.lua")
|
||||
dofile(modpath.."/egg.lua")
|
||||
dofile(modpath.."/ender_pearl.lua")
|
||||
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
|
||||
-- The snowball entity
|
||||
local snowball_ENTITY={
|
||||
physical = false,
|
||||
timer=0,
|
||||
textures = {"mcl_throwing_snowball.png"},
|
||||
visual_size = {x=0.5, y=0.5},
|
||||
collisionbox = {0,0,0,0,0,0},
|
||||
pointable = false,
|
||||
|
||||
get_staticdata = mcl_throwing.get_staticdata,
|
||||
on_activate = mcl_throwing.on_activate,
|
||||
_thrower = nil,
|
||||
|
||||
_lastpos={},
|
||||
}
|
||||
|
||||
local egg_ENTITY={
|
||||
physical = false,
|
||||
timer=0,
|
||||
textures = {"mcl_throwing_egg.png"},
|
||||
visual_size = {x=0.45, y=0.45},
|
||||
collisionbox = {0,0,0,0,0,0},
|
||||
pointable = false,
|
||||
|
||||
get_staticdata = mcl_throwing.get_staticdata,
|
||||
on_activate = mcl_throwing.on_activate,
|
||||
_thrower = nil,
|
||||
|
||||
_lastpos={},
|
||||
}
|
||||
|
||||
-- Ender pearl entity
|
||||
local pearl_ENTITY={
|
||||
physical = false,
|
||||
timer=0,
|
||||
textures = {"mcl_throwing_ender_pearl.png"},
|
||||
visual_size = {x=0.9, y=0.9},
|
||||
collisionbox = {0,0,0,0,0,0},
|
||||
pointable = false,
|
||||
|
||||
get_staticdata = mcl_throwing.get_staticdata,
|
||||
on_activate = mcl_throwing.on_activate,
|
||||
|
||||
_lastpos={},
|
||||
_thrower = nil, -- Player ObjectRef of the player who threw the ender pearl
|
||||
}
|
||||
|
||||
local function check_object_hit(self, pos, dmg)
|
||||
for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
|
||||
|
||||
local entity = object:get_luaentity()
|
||||
|
||||
if entity
|
||||
and entity.name ~= self.object:get_luaentity().name then
|
||||
|
||||
if object:is_player() and self._thrower ~= object:get_player_name() then
|
||||
self.object:remove()
|
||||
return true
|
||||
elseif (entity.is_mob == true or entity._hittable_by_projectile) and (self._thrower ~= object) then
|
||||
object:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = dmg,
|
||||
}, nil)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function snowball_particles(pos, vel)
|
||||
local vel = vector.normalize(vector.multiply(vel, -1))
|
||||
minetest.add_particlespawner({
|
||||
amount = 20,
|
||||
time = 0.001,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.add({x=-2, y=3, z=-2}, vel),
|
||||
maxvel = vector.add({x=2, y=5, z=2}, vel),
|
||||
minacc = {x=0, y=-9.81, z=0},
|
||||
maxacc = {x=0, y=-9.81, z=0},
|
||||
minexptime = 1,
|
||||
maxexptime = 3,
|
||||
minsize = 0.7,
|
||||
maxsize = 0.7,
|
||||
collisiondetection = true,
|
||||
collision_removal = true,
|
||||
object_collision = false,
|
||||
texture = "weather_pack_snow_snowflake"..math.random(1,2)..".png",
|
||||
})
|
||||
end
|
||||
|
||||
-- Snowball on_step()--> called when snowball is moving.
|
||||
local function snowball_on_step(self, dtime)
|
||||
self.timer = self.timer + dtime
|
||||
local pos = self.object:get_pos()
|
||||
local vel = self.object:get_velocity()
|
||||
local node = minetest.get_node(pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Destroy when hitting a solid node
|
||||
if self._lastpos.x~=nil then
|
||||
if (def and def.walkable) or not def then
|
||||
minetest.sound_play("mcl_throwing_snowball_impact_hard", { pos = pos, max_hear_distance=16, gain=0.7 }, true)
|
||||
snowball_particles(self._lastpos, vel)
|
||||
self.object:remove()
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
if check_object_hit(self, pos, {snowball_vulnerable = 3}) then
|
||||
minetest.sound_play("mcl_throwing_snowball_impact_soft", { pos = pos, max_hear_distance=16, gain=0.7 }, true)
|
||||
snowball_particles(pos, vel)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
self._lastpos = pos -- Set _lastpos-->Node will be added at last pos outside the node
|
||||
end
|
||||
|
||||
-- Movement function of egg
|
||||
local function egg_on_step(self, dtime)
|
||||
self.timer = self.timer + dtime
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Destroy when hitting a solid node or entity, with chance to spawn chicks
|
||||
if (def and def.walkable) or not def or check_object_hit(self, pos, 0) then
|
||||
-- If egg has just been thrown, use current position
|
||||
if not self._lastpos.x then
|
||||
self._lastpos = pos
|
||||
end
|
||||
-- 1/8 chance to spawn a chick
|
||||
-- FIXME: Chicks have a quite good chance to spawn in walls
|
||||
if math.random(1,8) == 1 then
|
||||
mcl_mobs.spawn_child(self._lastpos, "mobs_mc:chicken")
|
||||
|
||||
-- BONUS ROUND: 1/32 chance to spawn 3 additional chicks
|
||||
if math.random(1,32) == 1 then
|
||||
local offsets = {
|
||||
{ x=0.7, y=0, z=0 },
|
||||
{ x=-0.7, y=0, z=-0.7 },
|
||||
{ x=-0.7, y=0, z=0.7 },
|
||||
}
|
||||
for o=1, 3 do
|
||||
local pos = vector.add(self._lastpos, offsets[o])
|
||||
mcl_mobs.spawn_child(pos, "mobs_mc:chicken")
|
||||
end
|
||||
end
|
||||
end
|
||||
minetest.sound_play("mcl_throwing_egg_impact", { pos = self.object:get_pos(), max_hear_distance=10, gain=0.5 }, true)
|
||||
self.object:remove()
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
self._lastpos = pos -- Set lastpos-->Node will be added at last pos outside the node
|
||||
end
|
||||
|
||||
-- Movement function of ender pearl
|
||||
local function pearl_on_step(self, dtime)
|
||||
self.timer = self.timer + dtime
|
||||
local pos = self.object:get_pos()
|
||||
pos.y = math.floor(pos.y)
|
||||
local node = minetest.get_node(pos)
|
||||
local nn = node.name
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Destroy when hitting a solid node
|
||||
if self._lastpos.x~=nil then
|
||||
local walkable = (def and def.walkable)
|
||||
|
||||
-- No teleport for hitting ignore for now. Otherwise the player could get stuck.
|
||||
-- FIXME: This also means the player loses an ender pearl for throwing into unloaded areas
|
||||
if node.name == "ignore" then
|
||||
self.object:remove()
|
||||
-- Activate when hitting a solid node or a plant
|
||||
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
|
||||
local player = self._thrower and minetest.get_player_by_name(self._thrower)
|
||||
if player then
|
||||
-- Teleport and hurt player
|
||||
|
||||
-- First determine good teleport position
|
||||
local dir = {x=0, y=0, z=0}
|
||||
|
||||
local v = self.object:get_velocity()
|
||||
if walkable then
|
||||
local vc = table.copy(v) -- vector for calculating
|
||||
-- Node is walkable, we have to find a place somewhere outside of that node
|
||||
vc = vector.normalize(vc)
|
||||
|
||||
-- Zero-out the two axes with a lower absolute value than
|
||||
-- the axis with the strongest force
|
||||
local lv, ld
|
||||
lv, ld = math.abs(vc.y), "y"
|
||||
if math.abs(vc.x) > lv then
|
||||
lv, ld = math.abs(vc.x), "x"
|
||||
end
|
||||
if math.abs(vc.z) > lv then
|
||||
ld = "z" --math.abs(vc.z)
|
||||
end
|
||||
if ld ~= "x" then vc.x = 0 end
|
||||
if ld ~= "y" then vc.y = 0 end
|
||||
if ld ~= "z" then vc.z = 0 end
|
||||
|
||||
-- Final tweaks to the teleporting pos, based on direction
|
||||
-- Impact from the side
|
||||
dir.x = vc.x * -1
|
||||
dir.z = vc.z * -1
|
||||
|
||||
-- Special case: top or bottom of node
|
||||
if vc.y > 0 then
|
||||
-- We need more space when impact is from below
|
||||
dir.y = -2.3
|
||||
elseif vc.y < 0 then
|
||||
-- Standing on top
|
||||
dir.y = 0.5
|
||||
end
|
||||
end
|
||||
-- If node was not walkable, no modification to pos is made.
|
||||
|
||||
-- Final teleportation position
|
||||
local telepos = vector.add(pos, dir)
|
||||
local telenode = minetest.get_node(telepos)
|
||||
|
||||
--[[ It may be possible that telepos is walkable due to the algorithm.
|
||||
Especially when the ender pearl is faster horizontally than vertical.
|
||||
This applies final fixing, just to be sure we're not in a walkable node ]]
|
||||
if not minetest.registered_nodes[telenode.name] or minetest.registered_nodes[telenode.name].walkable then
|
||||
if v.y < 0 then
|
||||
telepos.y = telepos.y + 0.5
|
||||
else
|
||||
telepos.y = telepos.y - 2.3
|
||||
end
|
||||
end
|
||||
|
||||
local oldpos = player:get_pos()
|
||||
-- Teleport and hurt player
|
||||
player:set_pos(telepos)
|
||||
player:set_hp(player:get_hp() - 5, { type = "fall", from = "mod" })
|
||||
|
||||
-- 5% chance to spawn endermite at the player's origin
|
||||
local r = math.random(1,20)
|
||||
if r == 1 then
|
||||
minetest.add_entity(oldpos, "mobs_mc:endermite")
|
||||
end
|
||||
|
||||
end
|
||||
self.object:remove()
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
self._lastpos = pos -- Set lastpos-->Node will be added at last pos outside the node
|
||||
end
|
||||
|
||||
snowball_ENTITY.on_step = snowball_on_step
|
||||
egg_ENTITY.on_step = egg_on_step
|
||||
pearl_ENTITY.on_step = pearl_on_step
|
||||
|
||||
minetest.register_entity("mcl_throwing:snowball_entity", snowball_ENTITY)
|
||||
minetest.register_entity("mcl_throwing:egg_entity", egg_ENTITY)
|
||||
minetest.register_entity("mcl_throwing:ender_pearl_entity", pearl_ENTITY)
|
||||
|
||||
|
||||
local how_to_throw = S("Use the punch key to throw.")
|
||||
|
||||
-- Snowball
|
||||
minetest.register_craftitem("mcl_throwing:snowball", {
|
||||
description = S("Snowball"),
|
||||
_tt_help = S("Throwable"),
|
||||
_doc_items_longdesc = S("Snowballs can be thrown or launched from a dispenser for fun. Hitting something with a snowball does nothing."),
|
||||
_doc_items_usagehelp = how_to_throw,
|
||||
inventory_image = "mcl_throwing_snowball.png",
|
||||
stack_max = 64,
|
||||
groups = { weapon_ranged = 1 },
|
||||
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:snowball_entity"),
|
||||
_on_dispense = mcl_throwing.dispense_function,
|
||||
})
|
||||
|
||||
-- Egg
|
||||
minetest.register_craftitem("mcl_throwing:egg", {
|
||||
description = S("Egg"),
|
||||
_tt_help = S("Throwable").."\n"..S("Chance to hatch chicks when broken"),
|
||||
_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."),
|
||||
_doc_items_usagehelp = how_to_throw,
|
||||
inventory_image = "mcl_throwing_egg.png",
|
||||
stack_max = 64,
|
||||
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:egg_entity"),
|
||||
_on_dispense = mcl_throwing.dispense_function,
|
||||
groups = { craftitem = 1 },
|
||||
})
|
||||
|
||||
-- Ender Pearl
|
||||
minetest.register_craftitem("mcl_throwing:ender_pearl", {
|
||||
description = S("Ender Pearl"),
|
||||
_tt_help = S("Throwable").."\n"..minetest.colorize(mcl_colors.YELLOW, S("Teleports you on impact for cost of 5 HP")),
|
||||
_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."),
|
||||
_doc_items_usagehelp = how_to_throw,
|
||||
wield_image = "mcl_throwing_ender_pearl.png",
|
||||
inventory_image = "mcl_throwing_ender_pearl.png",
|
||||
stack_max = 16,
|
||||
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:ender_pearl_entity"),
|
||||
groups = { transport = 1 },
|
||||
})
|
||||
|
||||
mcl_throwing.register_throwable_object("mcl_throwing:snowball", "mcl_throwing:snowball_entity", 22)
|
||||
mcl_throwing.register_throwable_object("mcl_throwing:egg", "mcl_throwing:egg_entity", 22)
|
||||
mcl_throwing.register_throwable_object("mcl_throwing:ender_pearl", "mcl_throwing:ender_pearl_entity", 22)
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local mod_target = minetest.get_modpath("mcl_target")
|
||||
local how_to_throw = S("Use the punch key to throw.")
|
||||
|
||||
-- Snowball
|
||||
minetest.register_craftitem("mcl_throwing:snowball", {
|
||||
description = S("Snowball"),
|
||||
_tt_help = S("Throwable"),
|
||||
_doc_items_longdesc = S("Snowballs can be thrown or launched from a dispenser for fun. Hitting something with a snowball does nothing."),
|
||||
_doc_items_usagehelp = how_to_throw,
|
||||
inventory_image = "mcl_throwing_snowball.png",
|
||||
stack_max = 64,
|
||||
groups = { weapon_ranged = 1 },
|
||||
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:snowball_entity"),
|
||||
_on_dispense = mcl_throwing.dispense_function,
|
||||
})
|
||||
|
||||
-- The snowball entity
|
||||
local function snowball_particles(pos, vel)
|
||||
local vel = vector.normalize(vector.multiply(vel, -1))
|
||||
minetest.add_particlespawner({
|
||||
amount = 20,
|
||||
time = 0.001,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.offset(vel, -2, 3, -2),
|
||||
maxvel = vector.offset(vel, 2, 5, 2),
|
||||
minacc = {x=0, y=-9.81, z=0},
|
||||
maxacc = {x=0, y=-9.81, z=0},
|
||||
minexptime = 1,
|
||||
maxexptime = 3,
|
||||
minsize = 0.7,
|
||||
maxsize = 0.7,
|
||||
collisiondetection = true,
|
||||
collision_removal = true,
|
||||
object_collision = false,
|
||||
texture = "weather_pack_snow_snowflake"..math.random(1,2)..".png",
|
||||
})
|
||||
end
|
||||
vl_projectile.register("mcl_throwing:snowball_entity", {
|
||||
physical = false,
|
||||
timer=0,
|
||||
textures = {"mcl_throwing_snowball.png"},
|
||||
visual_size = {x=0.5, y=0.5},
|
||||
collisionbox = {0,0,0,0,0,0},
|
||||
pointable = false,
|
||||
|
||||
get_staticdata = mcl_throwing.get_staticdata,
|
||||
on_activate = mcl_throwing.on_activate,
|
||||
_vl_projectile = {
|
||||
behaviors = {
|
||||
vl_projectile.collides_with_solids,
|
||||
vl_projectile.collides_with_entities,
|
||||
},
|
||||
allow_punching = function(self, _, _, object)
|
||||
if self._owner == mcl_util.get_entity_id(object) then
|
||||
return self.timer > 1
|
||||
end
|
||||
|
||||
local le = object:get_luaentity()
|
||||
return le and (le.is_mob or le._hittable_by_projectile) or object:is_player()
|
||||
end,
|
||||
on_collide_with_solid = function(self, pos, node)
|
||||
if mod_target and node.name == "mcl_target:target_off" then
|
||||
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
|
||||
end
|
||||
|
||||
snowball_particles(self._last_pos or pos, self.object:get_velocity())
|
||||
end,
|
||||
on_collide_with_entity = function(self, pos, entity)
|
||||
snowball_particles(self._last_pos or pos, self.object:get_velocity())
|
||||
end,
|
||||
sounds = {
|
||||
on_solid_collision = {"mcl_throwing_snowball_impact_hard", { max_hear_distance=16, gain=0.7 }, true},
|
||||
on_entity_collision = {"mcl_throwing_snowball_impact_soft", { max_hear_distance=16, gain=0.7 }, true}
|
||||
},
|
||||
damage_groups = { snowball_vulnerable = 3 },
|
||||
},
|
||||
})
|
||||
mcl_throwing.register_throwable_object("mcl_throwing:snowball", "mcl_throwing:snowball_entity", 22)
|
||||
|
|
@ -110,6 +110,13 @@ minetest.register_node("mcl_tnt:tnt", {
|
|||
tnt.ignite(droppos)
|
||||
end
|
||||
end,
|
||||
_vl_projectile = {
|
||||
on_collide = function(projectile, pos, node, node_def)
|
||||
if mcl_burning.is_burning(projectile) then
|
||||
tnt.ignite(pos)
|
||||
end
|
||||
end
|
||||
},
|
||||
sounds = sounds,
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
# Projectiles API
|
||||
|
||||
## `vl_projectile.register(entity_name, def)`
|
||||
|
||||
Registers a projectile entity.
|
||||
|
||||
Arguments:
|
||||
|
||||
* `entity_name`: The name the entity will be refered to by the minetest engine
|
||||
* `def`: Projectile defintion. Supports all fields that standard minetest entities support.
|
||||
Must include the field `_vl_projectile` for projectile-specific behaviors. These are the supported
|
||||
fields:
|
||||
* `ignore_gravity`: if true, the projectile will not be affected by gravity
|
||||
* `liquid_drag`: if true, apply drag from liquid nodes to the projectile
|
||||
* `survive_collision`: if this field is `false` or `nil`, the projectile will be removed after a collision.
|
||||
* `sticks_in_players`: if true, the projectile will stick into players after colliding with them.
|
||||
* `damages_players`: if true, the projectile will deal damage to players.
|
||||
* `damage_groups`: damage group information to use for `punch()`. May be a function of type `function(projectile, entity_def, projectile_def, obj)`
|
||||
that returns dynamic damange group information.
|
||||
* `allow_punching`: will the projectile punch entities it collides with. May be either a boolean or a function of type `function(projectile, entity_def, projectile_def, obj)`.
|
||||
* `survive_collision`: will the projectile surive collisions. May be either a boolean or a fnction of type `function(projectile, entity_def, projectile_def, type, ...)`.
|
||||
* If `type` is "node" then the additional parameters `node, node_def` will be provided.
|
||||
* If `type` is "entity" then the additional parameter `objet` will be provided.
|
||||
* `behaviors`: a list of behavior callbacks that define the projectile's behavior. This mod provides the following
|
||||
behaviors: `vl_projectiles.collides_with_solids`, `vl_projectiles.collides_with_entities` and `vl_projectiles.raycast_collides_with_entities`
|
||||
* `maximum_time`: number of seconds until projectiles are removed.
|
||||
* `sounds`: sounds for this projectile. All fields take a table with three parameters corresponding to the
|
||||
three parameters for `minetest.play_sound()`. Supported sounds are:
|
||||
* `on_collision`: played when no other more specific sound is defined. May be a function of type `function(projectile, entity_def, projectile_def, type, ...)`
|
||||
* `on_solid_collision`: played when the projectile collides with a solid node. May be a function of type
|
||||
`funciton(projectile, entity_def, projectile_def, type, pos, node, node_def)` with `type = "node"`
|
||||
* `on_entity_collision`: played when the projectile collides with another entity. May be a function of type
|
||||
`function(projectile, entity_def, projectile_def, type, entity)` with `type = "entity"`
|
||||
* `on_collide_with_solid`: callback of type `function(projectile, pos, node, node_def)` used when the projectile collides with a solid node. Requires
|
||||
`vl_projectile.collides_with_solids` in `behaviors` list.
|
||||
* `on_collide_with_entity`: callback of type `function(projectile, pos, obj)` used when the projectile collides with an entity. Requires
|
||||
`vl_projectile.collides_with_entities` in `behaviors` list.
|
||||
|
||||
## `vl_projectile.update_projectile(self, dtime)`
|
||||
|
||||
Performs standard projectile update logic and runs projectile behaviors.
|
||||
|
||||
Arguments:
|
||||
* `self`: The lua entity of the projectile to update
|
||||
* `dtime`: The amount of time that has passed since the last update. Nomally the `dtime`
|
||||
parameter of the entity's `on_step(self, dtime)` callback.
|
||||
|
||||
## `vl_projectile.create(entity_id, options)`
|
||||
|
||||
Creates a projectile and performs convenience initialization.
|
||||
|
||||
Arguments:
|
||||
* `entity_id`: The name the entity as passed to `vl_projectile.register()`
|
||||
* `options`: A table with optional parameters. Supported fields are:
|
||||
* `dir`: direction the projectile is moving in
|
||||
* `velocity`: scalar velocity amount
|
||||
* `drag`: scalar resistance to velocity
|
||||
* `owner`: passed thru unmodified
|
||||
* `extra`: passed thru unmodified
|
||||
|
||||
## `vl_projectile.replace_with_item_drop(projectile_lua_entity, pos, projectile_def)`
|
||||
|
||||
Removes the projectile and replaces it with an item entity based on either the entity's `_arrow_item` field or
|
||||
the value `self._vl_projectile.item`.
|
||||
|
||||
Arguments:
|
||||
|
||||
* `projectile_lua_entity`: the lua entity of the projectile to be replaced.
|
||||
* `pos`: the position to create the item entity
|
||||
* `projectile_def`: The projectile's `_vl_projectile` field. If not provided, it will be
|
||||
extracted from the projectile's lua entity.
|
||||
|
||||
## Custom Projectile Behaviors
|
||||
|
||||
The projectile API supports specifying the behaviors that a projectile will exhibit. There are several
|
||||
standard behaviors provided with the API:
|
||||
|
||||
* `vl_projectile.burns`: projectile can be set on fire
|
||||
* `vl_projectile.collides_with_solids`: handles collisions between projectiles and solid nodes
|
||||
* `vl_projectile.collides_with_entities`: handles collisions between projectiles and entities by checking nearby entities
|
||||
* `vl_projectile.has_tracer`: projectile will have a tracer trail when thrown/shot. Projectile can define
|
||||
`_vl_projectile.hide_tracer = function(self)` to conditionally hide the tracer.
|
||||
* `vl_projectile.sticks`: projectile will stick into nodes. Forces `_vl_projectile.sticks_in_nodes = true`
|
||||
and `_vl_projectile.survive_collision = true`.
|
||||
* `vl_projectile.raycast_collides_with_entities`: handles collisions between projectils and entities by performing a raycast
|
||||
check along the path of movement.
|
||||
|
||||
Custom behaviors can be provided by adding a function with the signature `function(self, dtime, entity_def, projectile_def)`
|
||||
to the list of behaviors a projectile supports.
|
||||
|
||||
Arguments:
|
||||
|
||||
* `self`: The lua entity of the projectile
|
||||
* `dtime`: The amount of time that has passed since the last update. Nomally the `dtime`
|
||||
parameter of the entity's `on_step(self, dtime)` callback.
|
||||
* `entity_def`: The definition from `minetest.registered_entities` for the projectile.
|
||||
* `projectile_def`: Same as `entity_def._vl_projectile`
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,626 @@
|
|||
vl_projectile = {}
|
||||
local mod = vl_projectile
|
||||
|
||||
local vl_physics_path = minetest.get_modpath("vl_physics")
|
||||
|
||||
local DEBUG = false
|
||||
local YAW_OFFSET = -math.pi/2
|
||||
local GRAVITY = tonumber(minetest.settings:get("movement_gravity"))
|
||||
local STUCK_TIMEOUT = 60
|
||||
local STUCK_RECHECK_TIME = 0.25
|
||||
local enable_pvp = minetest.settings:get_bool("enable_pvp")
|
||||
|
||||
function mod.projectile_physics(obj, entity_def, v, a)
|
||||
local le = obj:get_luaentity()
|
||||
if not le then return end
|
||||
|
||||
local entity_def = minetest.registered_entities[le.name]
|
||||
local pos = obj:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
if vl_physics_path then
|
||||
v,a = vl_physics.apply_entity_environmental_physics(obj)
|
||||
else
|
||||
-- Simple physics
|
||||
v = v or obj:get_velocity()
|
||||
a = a or vector.zero()
|
||||
|
||||
if not entity_def._vl_projectile.ignore_gravity then
|
||||
a = a + vector.new(0,-GRAVITY,0)
|
||||
end
|
||||
|
||||
if entity_def.liquid_drag then
|
||||
local def = minetest.registered_nodes[minetest.get_node(pos).name]
|
||||
if def and def.liquidtype ~= "none" then
|
||||
-- Slow down arrow in liquids
|
||||
local visc = def.liquid_viscosity or 0
|
||||
le._viscosity = visc
|
||||
|
||||
local vpenalty = math.max(0.1, 0.98 - 0.1 * visc)
|
||||
if math.abs(v.x) > 0.001 then
|
||||
v.x = v.x * vpenalty
|
||||
end
|
||||
if math.abs(v.z) > 0.001 then
|
||||
v.z = v.z * vpenalty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Pass to entity
|
||||
if v then obj:set_velocity(v) end
|
||||
if a then obj:set_acceleration(a) end
|
||||
|
||||
-- Update projectile yaw to match velocity direction
|
||||
if v and le and not le._stuck then
|
||||
local yaw = minetest.dir_to_yaw(v) + YAW_OFFSET
|
||||
local pitch = math.asin(vector.normalize(v).y)
|
||||
obj:set_rotation(vector.new(0,yaw,pitch))
|
||||
end
|
||||
end
|
||||
|
||||
function mod.update_projectile(self, dtime)
|
||||
if self._removed then return end
|
||||
|
||||
-- Workaround for randomly occurring velocity change between projectile creation
|
||||
-- and the first time step
|
||||
if self._starting_velocity then
|
||||
local curr_velocity = self.object:get_velocity()
|
||||
local distance = vector.distance(curr_velocity, self._starting_velocity)
|
||||
local length = vector.length(self._starting_velocity)
|
||||
if length / distance > 1 then
|
||||
self.object:set_velocity(self._starting_velocity)
|
||||
end
|
||||
self._starting_velocity = nil
|
||||
end
|
||||
|
||||
local entity_name = self.name
|
||||
local entity_def = minetest.registered_entities[entity_name] or {}
|
||||
local entity_vl_projectile = entity_def._vl_projectile or {}
|
||||
|
||||
-- Update entity timer and remove expired projectiles
|
||||
self.timer = (self.timer or 0) + dtime
|
||||
local maximum_flight_time = entity_vl_projectile.maximum_time or 300
|
||||
if (self.timer or 0) > maximum_flight_time then
|
||||
self.removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
-- Run behaviors
|
||||
local behaviors = entity_vl_projectile.behaviors or {}
|
||||
for i=1,#behaviors do
|
||||
if behaviors[i](self, dtime, entity_def, entity_vl_projectile) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not self._stuck then
|
||||
mod.projectile_physics(self.object, entity_def)
|
||||
end
|
||||
end
|
||||
|
||||
local function damage_particles(pos, is_critical)
|
||||
if is_critical then
|
||||
minetest.add_particlespawner({
|
||||
amount = 15,
|
||||
time = 0.1,
|
||||
minpos = vector.offset(pos, -0.5, -0.5, -0.5),
|
||||
maxpos = vector.offset(pos, 0.5, 0.5, 0.5),
|
||||
minvel = vector.new(-0.1, -0.1, -0.1),
|
||||
maxvel = vector.new(0.1, 0.1, 0.1),
|
||||
minexptime = 1,
|
||||
maxexptime = 2,
|
||||
minsize = 1.5,
|
||||
maxsize = 1.5,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_crit.png^[colorize:#bc7a57:127",
|
||||
})
|
||||
end
|
||||
end
|
||||
local function random_hit_positions(positions, placement)
|
||||
if positions == "x" then
|
||||
return math.random(-4, 4)
|
||||
elseif positions == "y" then
|
||||
return math.random(0, 10)
|
||||
elseif positions == "z" then
|
||||
if placement == "front" then
|
||||
return 3
|
||||
elseif placement == "back" then
|
||||
return -3
|
||||
end
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
local function check_hitpoint(hitpoint)
|
||||
if hitpoint.type ~= "object" then return false end
|
||||
|
||||
-- find the closest object that is in the way of the arrow
|
||||
-- TODO: change this check when adding mob projectiles
|
||||
if hitpoint.ref:is_player() and enable_pvp then
|
||||
return true
|
||||
end
|
||||
|
||||
if not hitpoint.ref:is_player() and hitpoint.ref:get_luaentity() then
|
||||
if (hitpoint.ref:get_luaentity().is_mob or hitpoint.ref:get_luaentity()._hittable_by_projectile) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
local function handle_player_sticking(self, entity_def, projectile_def, entity)
|
||||
if self._in_player or self._blocked then return end
|
||||
if not projectile_def.sticks_in_players then return end
|
||||
|
||||
minetest.after(150, function()
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
end)
|
||||
|
||||
-- Handle blocking projectiles
|
||||
if mcl_shields.is_blocking(entity) then
|
||||
self._blocked = true
|
||||
self.object:set_velocity(vector.multiply(self.object:get_velocity(), -0.25))
|
||||
return
|
||||
end
|
||||
|
||||
-- Handle when the projectile hits the player
|
||||
self._placement = math.random(1, 2)
|
||||
|
||||
local placement = self._placement == 1 and "front" or "back"
|
||||
self._rotation_station = self.placement == 1 and -90 or 90
|
||||
self._in_player = true
|
||||
self._y_position = random_hit_positions("y", placement)
|
||||
self._x_position = random_hit_positions("x", placement)
|
||||
if self._y_position > 6 and self._x_position < 2 and self._x_position > -2 then
|
||||
self._attach_parent = "Head"
|
||||
self._y_position = self._y_position - 6
|
||||
elseif self._x_position > 2 then
|
||||
self._attach_parent = "Arm_Right"
|
||||
self._y_position = self._y_position - 3
|
||||
self._x_position = self._x_position - 2
|
||||
elseif self._x_position < -2 then
|
||||
self._attach_parent = "Arm_Left"
|
||||
self._y_position = self._y_position - 3
|
||||
self._x_position = self._x_position + 2
|
||||
else
|
||||
self._attach_parent = "Body"
|
||||
end
|
||||
self._z_rotation = math.random(-30, 30)
|
||||
self._y_rotation = math.random(-30, 30)
|
||||
self.object:set_attach(
|
||||
entity, self._attach_parent,
|
||||
vector.new(self._x_position, self._y_position, random_hit_positions("z", placement)),
|
||||
vector.new(0, self._rotation_station + self._y_rotation, self._z_rotation)
|
||||
)
|
||||
end
|
||||
|
||||
function mod.burns(self, dtime, entity_def, projectile_def)
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
|
||||
-- mcl_burning.tick may remove object immediately
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return true end
|
||||
|
||||
-- Handle getting set on fire
|
||||
local node = minetest.get_node(vector.round(pos))
|
||||
if not node or node.name == "ignore" then return end
|
||||
|
||||
local set_on_fire = minetest.get_item_group(node.name, "set_on_fire")
|
||||
if set_on_fire ~= 0 then
|
||||
mcl_burning.set_on_fire(self.object, set_on_fire)
|
||||
end
|
||||
end
|
||||
|
||||
function mod.has_tracer(self, dtime, entity_def, projectile_def)
|
||||
local hide_tracer = projectile_def.hide_tracer
|
||||
if hide_tracer and hide_tracer(self) then return end
|
||||
|
||||
-- Add tracer
|
||||
minetest.add_particlespawner({
|
||||
amount = 20,
|
||||
time = .2,
|
||||
minpos = vector.zero(),
|
||||
maxpos = vector.zero(),
|
||||
minvel = vector.new(-0.1,-0.1,-0.1),
|
||||
maxvel = vector.new(0.1,0.1,0.1),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 0.5,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
attached = self.object,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = projectile_def.tracer_texture or "mobs_mc_arrow_particle.png",
|
||||
glow = 1,
|
||||
})
|
||||
end
|
||||
|
||||
function mod.replace_with_item_drop(self, pos, projectile_def)
|
||||
local item = self._arrow_item
|
||||
|
||||
if not item then
|
||||
projectile_def = projectile_def or self._vl_projectile
|
||||
if not projectile_def then return end
|
||||
|
||||
item = projectile_def.item
|
||||
end
|
||||
|
||||
if self._collectable and not minetest.is_creative_enabled("") then
|
||||
local item = minetest.add_item(pos, item)
|
||||
item:set_velocity(vector.zero())
|
||||
item:set_yaw(self.object:get_yaw())
|
||||
end
|
||||
|
||||
mcl_burning.extinguish(self.object)
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
local function stuck_on_step(self, dtime, entity_def, projectile_def)
|
||||
-- Don't process objects that have been removed
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return true end
|
||||
|
||||
self._stucktimer = (self._stucktimer or 0) + dtime
|
||||
if self._stucktimer > STUCK_TIMEOUT then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Drop arrow as item when it is no longer stuck
|
||||
-- TODO: revist after observer rework
|
||||
self._stuckrechecktimer = (self._stuckrechecktimer or 0) + dtime
|
||||
if self._stuckrechecktimer > 1 then
|
||||
self._stuckrechecktimer = 0
|
||||
if self._stuckin then
|
||||
local node = minetest.get_node(self._stuckin)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def and node_def.walkable == false then
|
||||
mod.replace_with_item_drop(self, pos, projectile_def)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Pickup arrow if player is nearby (not in Creative Mode)
|
||||
local objects = minetest.get_objects_inside_radius(pos, 1)
|
||||
for i = 1,#objects do
|
||||
obj = objects[i]
|
||||
if obj:is_player() then
|
||||
if self._collectable and not minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
local arrow_item = self._arrow_item
|
||||
if arrow_item and minetest.registered_items[arrow_item] and obj:get_inventory():room_for_item("main", arrow_item) then
|
||||
obj:get_inventory():add_item("main", arrow_item)
|
||||
|
||||
minetest.sound_play("item_drop_pickup", {
|
||||
pos = pos,
|
||||
max_hear_distance = 16,
|
||||
gain = 1.0,
|
||||
}, true)
|
||||
end
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function mod.sticks(self, dtime, entity_def, projectile_def)
|
||||
-- Force the projectile to survive collisions (Otherwise, the projectile can't stick in nodes)
|
||||
projectile_def.survive_collision = true
|
||||
projectile_def.sticks_in_nodes = true
|
||||
|
||||
-- Stuck handling
|
||||
if self._stuck then
|
||||
return stuck_on_step(self, dtime, entity_def, projectile_def)
|
||||
end
|
||||
end
|
||||
|
||||
function mod.collides_with_solids(self, dtime, entity_def, projectile_def)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
-- Don't try to do anything on first update
|
||||
if not self._last_pos then
|
||||
self._last_pos = pos
|
||||
return
|
||||
end
|
||||
|
||||
-- Check if the object can collide with this node
|
||||
local node = minetest.get_node(pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
local collides_with = projectile_def.collides_with
|
||||
|
||||
if entity_def.physical then
|
||||
-- Projectile has stopped in one axis, so it probably hit something.
|
||||
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
|
||||
local vel = self.object:get_velocity()
|
||||
if not self._last_velocity then
|
||||
self._last_velocity = vel
|
||||
return
|
||||
end
|
||||
|
||||
local delta_v = (vel - self._last_velocity) / vector.length(vel)
|
||||
if math.abs(delta_v.x) <= 0.1 and math.abs(delta_v.z) <= 0.1 and math.abs(delta_v.y) <= 0.2 then
|
||||
return
|
||||
end
|
||||
self._last_velocity = vel
|
||||
else
|
||||
if node_def and not node_def.walkable and (not collides_with or not mcl_util.match_node_to_filter(node.name, collides_with)) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle sticking in nodes
|
||||
if projectile_def.sticks_in_nodes then
|
||||
local vel = self.object:get_velocity()
|
||||
local dpos = vector.round(pos) -- digital pos
|
||||
|
||||
-- Check for the node to which the arrow is pointing
|
||||
local dir
|
||||
if math.abs(vel.y) < 0.00001 then
|
||||
if self._last_pos.y < pos.y then
|
||||
dir = vector.new(0, 1, 0)
|
||||
else
|
||||
dir = vector.new(0, -1, 0)
|
||||
end
|
||||
else
|
||||
dir = minetest.facedir_to_dir(minetest.dir_to_facedir(minetest.yaw_to_dir(self.object:get_yaw()-YAW_OFFSET)))
|
||||
end
|
||||
self._stuckin = vector.add(dpos, dir)
|
||||
|
||||
local snode = minetest.get_node(self._stuckin)
|
||||
local sdef = minetest.registered_nodes[snode.name]
|
||||
|
||||
-- If node is non-walkable, unknown or ignore, don't make arrow stuck.
|
||||
-- This causes a deflection in the engine.
|
||||
if not sdef or sdef.walkable == false or snode.name == "ignore" then
|
||||
self._stuckin = nil
|
||||
if self._deflection_cooloff <= 0 then
|
||||
-- Lose 1/3 of velocity on deflection
|
||||
local newvel = vector.multiply(vel, 0.6667)
|
||||
|
||||
self.object:set_velocity(newvel)
|
||||
-- Reset deflection cooloff timer to prevent many deflections happening in quick succession
|
||||
self._deflection_cooloff = 1.0
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Node was walkable, make arrow stuck
|
||||
self._stuck = true
|
||||
self._stucktimer = 0
|
||||
self._stuckrechecktimer = 0
|
||||
|
||||
self.object:set_velocity(vector.zero())
|
||||
self.object:set_acceleration(vector.zero())
|
||||
|
||||
-- Trigger hits on the node the projectile hit
|
||||
local hook = sdef._vl_projectile and sdef._vl_projectile.on_collide
|
||||
if hook then hook(self, self._stuckin, snode, sdef) end
|
||||
end
|
||||
|
||||
-- Call entity collied hook
|
||||
local hook = projectile_def.on_collide_with_solid
|
||||
if hook then hook(self, pos, node, node_def) end
|
||||
|
||||
-- Call node collided hook
|
||||
local hook = node_def and node_def._vl_projectile and node_def._vl_projectile.on_collide
|
||||
if hook then hook(self, pos, node, node_def) end
|
||||
|
||||
-- Play sounds
|
||||
local sounds = projectile_def.sounds or {}
|
||||
local sound = sounds.on_solid_collision or sounds.on_collision
|
||||
if type(sound) == "function" then sound = sound(self, entity_def, projectile_def, "node", pos, node, node_def) end
|
||||
if sound then
|
||||
local arg2 = table.copy(sound[2])
|
||||
arg2.pos = pos
|
||||
minetest.sound_play(sound[1], arg2, sound[3])
|
||||
end
|
||||
|
||||
-- Normally objects should be removed on collision with solids
|
||||
local survive_collision = projectile_def.survive_collision
|
||||
if type(survive_collision) == "function" then
|
||||
survive_collision = survive_collision(self, entity_def, projectile_def, "node", node, node_def)
|
||||
end
|
||||
if not survive_collision then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
-- Done with behaviors
|
||||
return true
|
||||
end
|
||||
|
||||
local function handle_entity_collision(self, entity_def, projectile_def, object)
|
||||
local pos = self.object:get_pos()
|
||||
local dir = vector.normalize(self.object:get_velocity())
|
||||
|
||||
-- Check if this is allowed
|
||||
local allow_punching = projectile_def.allow_punching or true
|
||||
if type(allow_punching) == "function" then
|
||||
allow_punching = allow_punching(self, entity_def, projectile_def, object)
|
||||
end
|
||||
|
||||
if DEBUG then
|
||||
minetest.log("handle_entity_collision("..dump({
|
||||
self = self,
|
||||
allow_punching = allow_punching,
|
||||
entity_def = entity_def,
|
||||
object = object,
|
||||
luaentity = object:get_luaentity(),
|
||||
})..")")
|
||||
end
|
||||
|
||||
if not allow_punching then return end
|
||||
-- Get damage
|
||||
local dmg = projectile_def.damage_groups or 0
|
||||
if type(dmg) == "function" then
|
||||
dmg = dmg(self, entity_def, projectile_def, object)
|
||||
end
|
||||
|
||||
local object_lua = object:get_luaentity()
|
||||
|
||||
-- Apply damage
|
||||
-- Note: Damage blocking for shields is handled in mcl_shields with an mcl_damage modifier
|
||||
local do_damage = false
|
||||
if object:is_player() and projectile_def.damages_players then
|
||||
do_damage = true
|
||||
|
||||
handle_player_sticking(self, entity_def, projectile_def, object)
|
||||
elseif object_lua and (object_lua.is_mob or object_lua._hittable_by_projectile) then
|
||||
do_damage = true
|
||||
end
|
||||
|
||||
if do_damage then
|
||||
object:punch(self.object, 1.0, projectile_def.tool or { full_punch_interval = 1.0, damage_groups = dmg }, dir )
|
||||
|
||||
-- Guard against crashes when projectiles get destroyed in response to what it punched
|
||||
if not self.object:get_pos() then return true end
|
||||
|
||||
-- Indicate damage
|
||||
damage_particles(vector.add(pos, vector.multiply(self.object:get_velocity(), 0.1)), self._is_critical)
|
||||
|
||||
-- Light things on fire
|
||||
if mcl_burning.is_burning(self.object) then
|
||||
mcl_burning.set_on_fire(object, 5)
|
||||
end
|
||||
end
|
||||
|
||||
-- Call entity collision hook
|
||||
local hook = projectile_def.on_collide_with_entity
|
||||
if hook then hook(self, pos, object) end
|
||||
|
||||
-- Call reverse entity collision hook
|
||||
local other_entity_def = minetest.registered_entities[object.name] or {}
|
||||
local other_entity_vl_projectile = other_entity_def._vl_projectile or {}
|
||||
local hook = other_entity_vl_projectile and other_entity_vl_projectile.on_collide
|
||||
if hook then hook(object, self) end
|
||||
|
||||
-- Play sounds
|
||||
local sounds = projectile_def.sounds or {}
|
||||
local sound = sounds.on_entity_collion or sounds.on_collision
|
||||
if type(sound) == "function" then sound = sound(self, entity_def, projectile_def, "entity", object) end
|
||||
if sound then
|
||||
local arg2 = table.copy(sound[2])
|
||||
arg2.pos = pos
|
||||
minetest.sound_play(sound[1], arg2, sound[3])
|
||||
end
|
||||
|
||||
-- Normally objects should be removed on collision with entities
|
||||
local survive_collision = projectile_def.survive_collision
|
||||
if type(survive_collision) == "function" then
|
||||
survive_collision = survive_collision(self, entity_def, projectile_def, "entity", object)
|
||||
end
|
||||
if not survive_collision then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function mod.collides_with_entities(self, dtime, entity_def, projectile_def)
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
local objects = minetest.get_objects_inside_radius(pos, 1.5)
|
||||
for i = 1,#objects do
|
||||
local object = objects[i]
|
||||
local entity = object:get_luaentity()
|
||||
|
||||
if object ~= self.object and (not entity or entity.name ~= self.name) then
|
||||
if object:is_player() then
|
||||
return handle_entity_collision(self, entity_def, projectile_def, object)
|
||||
elseif (entity.is_mob or entity._hittable_by_projectile) then
|
||||
return handle_entity_collision(self, entity_def, projectile_def, object)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mod.raycast_collides_with_entities(self, dtime, entity_def, projectile_def)
|
||||
local closest_object, closest_distance
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
local arrow_dir = self.object:get_velocity()
|
||||
|
||||
--create a raycast from the arrow based on the velocity of the arrow to deal with lag
|
||||
local raycast = minetest.raycast(pos, vector.add(pos, vector.multiply(arrow_dir, 0.1)), true, false)
|
||||
for hitpoint in raycast do
|
||||
if check_hitpoint(hitpoint) then
|
||||
local hitpoint_ref = hitpoint.ref
|
||||
local dist = vector.distance(hitpoint_ref:get_pos(), pos)
|
||||
if not closest_distance or dist < closest_distance then
|
||||
closest_object = hitpoint_ref
|
||||
closest_distance = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if closest_object then
|
||||
return handle_entity_collision(self, entity_def, projectile_def, closest_object)
|
||||
end
|
||||
end
|
||||
|
||||
function mod.create(entity_id, options)
|
||||
local pos = options.pos
|
||||
local obj = minetest.add_entity(pos, entity_id, options.staticdata)
|
||||
|
||||
-- Set initial velocity and acceleration
|
||||
local a, v
|
||||
if options.dir then
|
||||
v = vector.multiply(options.dir, options.velocity or 0)
|
||||
a = vector.multiply(v, -math.abs(options.drag or 0))
|
||||
else
|
||||
a = vector.zero()
|
||||
v = a
|
||||
end
|
||||
local entity_def = minetest.registered_entities[entity_id]
|
||||
mod.projectile_physics(obj, entity_def, v, a)
|
||||
|
||||
-- Update projectile parameters
|
||||
local luaentity = obj:get_luaentity()
|
||||
if options.owner_id then
|
||||
luaentity._owner = options.owner_id
|
||||
else
|
||||
luaentity._owner = mcl_util.get_entity_id(options.owner)
|
||||
end
|
||||
luaentity._starting_velocity = obj:get_velocity()
|
||||
luaentity._vl_projectile = {
|
||||
extra = options.extra,
|
||||
}
|
||||
|
||||
-- And provide the caller with the created object
|
||||
return obj
|
||||
end
|
||||
|
||||
function mod.register(name, def)
|
||||
assert(def._vl_projectile, "vl_projectile.register() requires definition to define _vl_projectile")
|
||||
assert(def._vl_projectile.behaviors, "vl_projectile.register() requires definition to define _vl_projectile.behaviors")
|
||||
local behaviors = def._vl_projectile.behaviors
|
||||
for i = 1,#behaviors do
|
||||
assert(behaviors[i] and type(behaviors[i]) == "function", "def._vl_projectile.behaviors["..i.." is malformed")
|
||||
end
|
||||
|
||||
if not def.on_step then
|
||||
def.on_step = mod.update_projectile
|
||||
end
|
||||
|
||||
def._thrower = nil
|
||||
def._shooter = nil
|
||||
def._last_pos = nil
|
||||
|
||||
minetest.register_entity(name, def)
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
name = vl_projectile
|
||||
depends = mcl_util
|
||||
optional_depends = vl_physics, mcl_shields, mcl_burning, mcl_util
|
Loading…
Reference in New Issue