598 lines
22 KiB
Lua
598 lines
22 KiB
Lua
---Gets total armor level from 3d armor
|
|
---@param player ObjectRef
|
|
---@return integer
|
|
local function get_3d_armor_armor(player)
|
|
local armor_total = 0
|
|
|
|
if not player:is_player() or not minetest.get_modpath('3d_armor') or not armor.def[player:get_player_name()] then
|
|
return armor_total
|
|
end
|
|
|
|
armor_total = armor.def[player:get_player_name()].level
|
|
|
|
return armor_total
|
|
end
|
|
|
|
---Limits number `x` between `min` and `max` values
|
|
---@param x integer
|
|
---@param min integer
|
|
---@param max integer
|
|
---@return integer
|
|
local function limit(x, min, max)
|
|
return math.min(math.max(x, min), max)
|
|
end
|
|
|
|
---Gets collision box
|
|
---@param obj ObjectRef
|
|
---@return number[]
|
|
local function get_obj_box(obj)
|
|
local box
|
|
|
|
if obj:is_player() then
|
|
box = obj:get_properties().collisionbox or {-0.5, 0.0, -0.5, 0.5, 1.0, 0.5}
|
|
else
|
|
box = obj:get_luaentity().collisionbox or {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
|
|
end
|
|
|
|
return box
|
|
end
|
|
|
|
---Poison Arrow Effects
|
|
---@param tick integer|number
|
|
---@param time integer|number
|
|
---@param time_left integer|number
|
|
---@param arrow_obj ObjectRef
|
|
---@param target_obj ObjectRef
|
|
---@param old_damage_texture_modifier string
|
|
---@param punch_def table
|
|
function x_bows.poison_effect(tick, time, time_left, arrow_obj, target_obj, old_damage_texture_modifier, punch_def)
|
|
if not arrow_obj or target_obj:get_hp() <= 0 then
|
|
return
|
|
end
|
|
|
|
target_obj:set_properties({damage_texture_modifier = '^[colorize:#00FF0050'})
|
|
|
|
time_left = time_left + tick
|
|
|
|
if time_left <= time then
|
|
minetest.after(
|
|
tick,
|
|
x_bows.poison_effect,
|
|
tick,
|
|
time,
|
|
time_left,
|
|
arrow_obj,
|
|
target_obj,
|
|
old_damage_texture_modifier,
|
|
punch_def
|
|
)
|
|
elseif target_obj:is_player() then
|
|
if x_bows.hbhunger then
|
|
-- Reset HUD bar color
|
|
hb.change_hudbar(target_obj, 'health', nil, nil, 'hudbars_icon_health.png', nil, 'hudbars_bar_health.png')
|
|
end
|
|
|
|
if old_damage_texture_modifier then
|
|
target_obj:set_properties({damage_texture_modifier = old_damage_texture_modifier})
|
|
end
|
|
|
|
-- return
|
|
else
|
|
-- local lua_ent = target_obj:get_luaentity()
|
|
|
|
-- if not lua_ent then
|
|
-- return
|
|
-- end
|
|
|
|
-- lua_ent[arrow_obj.arrow .. '_active'] = false
|
|
|
|
|
|
if old_damage_texture_modifier then
|
|
target_obj:set_properties({damage_texture_modifier = old_damage_texture_modifier})
|
|
end
|
|
-- return
|
|
end
|
|
|
|
local _damage = punch_def.tool_capabilities.damage_groups.fleshy
|
|
if target_obj:get_hp() - _damage > 0 then
|
|
target_obj:punch(
|
|
punch_def.puncher,
|
|
punch_def.time_from_last_punch,
|
|
punch_def.tool_capabilities
|
|
)
|
|
|
|
local target_obj_pos = target_obj:get_pos()
|
|
|
|
if target_obj_pos then
|
|
x_bows.particle_effect(target_obj_pos, 'arrow_tipped')
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Main Arrow Entity
|
|
minetest.register_entity('x_bows:arrow_entity', {
|
|
initial_properties = {
|
|
visual = 'wielditem',
|
|
collisionbox = {0, 0, 0, 0, 0, 0},
|
|
selectionbox = {0, 0, 0, 0, 0, 0},
|
|
physical = false,
|
|
textures = {'air'},
|
|
hp_max = 1
|
|
},
|
|
|
|
---@param self table
|
|
---@param staticdata string
|
|
on_activate = function(self, staticdata)
|
|
if not self or not staticdata or staticdata == '' then
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
local _staticdata = minetest.deserialize(staticdata)
|
|
|
|
-- set/reset - do not inherit from previous entity table
|
|
self._velocity = {x = 0, y = 0, z = 0}
|
|
self._old_pos = nil
|
|
self._attached = false
|
|
self._attached_to = {
|
|
type = '',
|
|
pos = nil
|
|
}
|
|
self._has_particles = false
|
|
self._lifetimer = 60
|
|
self._nodechecktimer = 0.5
|
|
self._is_drowning = false
|
|
self._in_liquid = false
|
|
self._poison_arrow = false
|
|
self._shot_from_pos = self.object:get_pos()
|
|
self.arrow = _staticdata.arrow
|
|
self.user = minetest.get_player_by_name(_staticdata.user_name)
|
|
self._tflp = _staticdata._tflp
|
|
self._tool_capabilities = _staticdata._tool_capabilities
|
|
self._is_critical_hit = _staticdata.is_critical_hit
|
|
self._faster_arrows_multiplier = _staticdata.faster_arrows_multiplier
|
|
self._add_damage = _staticdata.add_damage
|
|
|
|
if self.arrow == 'x_bows:arrow_diamond_tipped_poison' then
|
|
self._poison_arrow = true
|
|
end
|
|
|
|
self.object:set_properties({
|
|
textures = {'x_bows:arrow_node'},
|
|
infotext = self.arrow
|
|
})
|
|
end,
|
|
|
|
---@param self table
|
|
---@param killer ObjectRef|nil
|
|
on_death = function(self, killer)
|
|
if not self._old_pos then
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
minetest.item_drop(ItemStack(self.arrow), nil, vector.round(self._old_pos))
|
|
end,
|
|
|
|
---@param self table
|
|
---@param dtime integer|number
|
|
on_step = function(self, dtime)
|
|
local pos = self.object:get_pos()
|
|
self._old_pos = self._old_pos or pos
|
|
local ray = minetest.raycast(self._old_pos, pos, true, true)
|
|
local pointed_thing = ray:next()
|
|
|
|
self._lifetimer = self._lifetimer - dtime
|
|
self._nodechecktimer = self._nodechecktimer - dtime
|
|
|
|
-- adjust pitch when flying
|
|
if not self._attached then
|
|
local velocity = self.object:get_velocity()
|
|
local v_rotation = self.object:get_rotation()
|
|
local pitch = math.atan2(velocity.y, math.sqrt(velocity.x^2 + velocity.z^2))
|
|
|
|
self.object:set_rotation({
|
|
x = pitch,
|
|
y = v_rotation.y,
|
|
z = v_rotation.z
|
|
})
|
|
end
|
|
|
|
-- remove attached arrows after lifetime
|
|
if self._lifetimer <= 0 then
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
-- add particles only when not attached
|
|
if not self._attached and not self._in_liquid then
|
|
self._has_particles = true
|
|
|
|
if self._tflp >= self._tool_capabilities.full_punch_interval then
|
|
if self._is_critical_hit then
|
|
x_bows.particle_effect(self._old_pos, 'arrow_crit')
|
|
elseif self._faster_arrows_multiplier then
|
|
x_bows.particle_effect(self._old_pos, 'arrow_fast')
|
|
else
|
|
x_bows.particle_effect(self._old_pos, 'arrow')
|
|
end
|
|
end
|
|
end
|
|
|
|
-- remove attached arrows after object dies
|
|
if not self.object:get_attach() and self._attached_to.type == 'object' then
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
-- arrow falls down when not attached to node any more
|
|
if self._attached_to.type == 'node' and self._attached and self._nodechecktimer <= 0 then
|
|
local node = minetest.get_node(self._attached_to.pos)
|
|
self._nodechecktimer = 0.5
|
|
|
|
if not node then
|
|
return
|
|
end
|
|
|
|
if node.name == 'air' then
|
|
self.object:set_velocity({x = 0, y = -3, z = 0})
|
|
self.object:set_acceleration({x = 0, y = -3, z = 0})
|
|
-- reset values
|
|
self._attached = false
|
|
self._attached_to.type = ''
|
|
self._attached_to.pos = nil
|
|
self.object:set_properties({collisionbox = {0, 0, 0, 0, 0, 0}})
|
|
|
|
return
|
|
end
|
|
end
|
|
|
|
while pointed_thing do
|
|
local ip_pos = pointed_thing.intersection_point
|
|
local in_pos = pointed_thing.intersection_normal
|
|
self.pointed_thing = pointed_thing
|
|
|
|
if pointed_thing.type == 'object'
|
|
and pointed_thing.ref ~= self.object
|
|
and pointed_thing.ref:get_hp() > 0
|
|
and (
|
|
(pointed_thing.ref:is_player() and pointed_thing.ref:get_player_name() ~= self.user:get_player_name())
|
|
or (
|
|
pointed_thing.ref:get_luaentity()
|
|
and pointed_thing.ref:get_luaentity().physical
|
|
and pointed_thing.ref:get_luaentity().name ~= '__builtin:item'
|
|
)
|
|
)
|
|
and self.object:get_attach() == nil
|
|
then
|
|
if pointed_thing.ref:is_player() then
|
|
minetest.sound_play('x_bows_arrow_successful_hit', {
|
|
to_player = self.user:get_player_name(),
|
|
gain = 0.3
|
|
})
|
|
else
|
|
minetest.sound_play('x_bows_arrow_hit', {
|
|
to_player = self.user:get_player_name(),
|
|
gain = 0.6
|
|
})
|
|
end
|
|
|
|
-- store these here before punching in case pointed_thing.ref dies
|
|
local collisionbox = get_obj_box(pointed_thing.ref)
|
|
local xmin = collisionbox[1] * 100
|
|
local ymin = collisionbox[2] * 100
|
|
local zmin = collisionbox[3] * 100
|
|
local xmax = collisionbox[4] * 100
|
|
local ymax = collisionbox[5] * 100
|
|
local zmax = collisionbox[6] * 100
|
|
|
|
self.object:set_velocity({x = 0, y = 0, z = 0})
|
|
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
|
|
|
-- calculate damage
|
|
local target_armor_groups = pointed_thing.ref:get_armor_groups()
|
|
local _damage = 0
|
|
|
|
if self._add_damage then
|
|
_damage = _damage + self._add_damage
|
|
end
|
|
|
|
for group, base_damage in pairs(self._tool_capabilities.damage_groups) do
|
|
_damage = _damage
|
|
+ base_damage
|
|
* limit(self._tflp / self._tool_capabilities.full_punch_interval, 0.0, 1.0)
|
|
* ((target_armor_groups[group] or 0) + get_3d_armor_armor(pointed_thing.ref)) / 100.0
|
|
end
|
|
|
|
-- crits
|
|
if self._is_critical_hit then
|
|
_damage = _damage * 2
|
|
end
|
|
|
|
-- knockback
|
|
local dir = vector.normalize(vector.subtract(self._shot_from_pos, ip_pos))
|
|
local distance = vector.distance(self._shot_from_pos, ip_pos)
|
|
local knockback = minetest.calculate_knockback(
|
|
pointed_thing.ref,
|
|
self.object,
|
|
self._tflp,
|
|
{
|
|
full_punch_interval = self._tool_capabilities.full_punch_interval,
|
|
damage_groups = {fleshy = _damage},
|
|
},
|
|
dir,
|
|
distance,
|
|
_damage
|
|
)
|
|
|
|
pointed_thing.ref:add_velocity({
|
|
x = dir.x * knockback * -1,
|
|
y = 7,
|
|
z = dir.z * knockback * -1
|
|
})
|
|
|
|
pointed_thing.ref:punch(
|
|
self.object,
|
|
self._tflp,
|
|
{
|
|
full_punch_interval = self._tool_capabilities.full_punch_interval,
|
|
damage_groups = {fleshy = _damage, knockback = knockback}
|
|
},
|
|
{
|
|
x = dir.x * -1,
|
|
y = 7,
|
|
z = dir.z * -1
|
|
}
|
|
)
|
|
|
|
-- already dead (entity)
|
|
if not pointed_thing.ref:get_luaentity() and not pointed_thing.ref:is_player() then
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
-- already dead (player)
|
|
if pointed_thing.ref:get_hp() <= 0 then
|
|
if x_bows.hbhunger then
|
|
-- Reset HUD bar color
|
|
hb.change_hudbar(pointed_thing.ref, 'health', nil, nil, 'hudbars_icon_health.png', nil, 'hudbars_bar_health.png')
|
|
end
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
-- attach arrow prepare
|
|
local rotation = {x = 0, y = 0, z = 0}
|
|
local position = {x = 0, y = 0, z = 0}
|
|
|
|
if in_pos.x == 1 then
|
|
-- x = 0
|
|
-- y = -90
|
|
-- z = 0
|
|
rotation.x = math.random(-10, 10)
|
|
rotation.y = math.random(-100, -80)
|
|
rotation.z = math.random(-10, 10)
|
|
|
|
position.x = xmax / 10
|
|
position.y = math.random(ymin, ymax) / 10
|
|
position.z = math.random(zmin, zmax) / 10
|
|
elseif in_pos.x == -1 then
|
|
-- x = 0
|
|
-- y = 90
|
|
-- z = 0
|
|
rotation.x = math.random(-10, 10)
|
|
rotation.y = math.random(80, 100)
|
|
rotation.z = math.random(-10, 10)
|
|
|
|
position.x = xmin / 10
|
|
position.y = math.random(ymin, ymax) / 10
|
|
position.z = math.random(zmin, zmax) / 10
|
|
elseif in_pos.y == 1 then
|
|
-- x = -90
|
|
-- y = 0
|
|
-- z = -180
|
|
rotation.x = math.random(-100, -80)
|
|
rotation.y = math.random(-10, 10)
|
|
rotation.z = math.random(-190, -170)
|
|
|
|
position.x = math.random(xmin, xmax) / 10
|
|
position.y = ymax / 10
|
|
position.z = math.random(zmin, zmax) / 10
|
|
elseif in_pos.y == -1 then
|
|
-- x = 90
|
|
-- y = 0
|
|
-- z = 180
|
|
rotation.x = math.random(80, 100)
|
|
rotation.y = math.random(-10, 10)
|
|
rotation.z = math.random(170, 190)
|
|
|
|
position.x = math.random(xmin, xmax) / 10
|
|
position.y = ymin / 10
|
|
position.z = math.random(zmin, zmax) / 10
|
|
elseif in_pos.z == 1 then
|
|
-- x = 180
|
|
-- y = 0
|
|
-- z = 180
|
|
rotation.x = math.random(170, 190)
|
|
rotation.y = math.random(-10, 10)
|
|
rotation.z = math.random(170, 190)
|
|
|
|
position.x = math.random(xmin, xmax) / 10
|
|
position.y = math.random(ymin, ymax) / 10
|
|
position.z = zmax / 10
|
|
elseif in_pos.z == -1 then
|
|
-- x = -180
|
|
-- y = 180
|
|
-- z = -180
|
|
rotation.x = math.random(-190, -170)
|
|
rotation.y = math.random(170, 190)
|
|
rotation.z = math.random(-190, -170)
|
|
|
|
position.x = math.random(xmin, xmax) / 10
|
|
position.y = math.random(ymin, ymax) / 10
|
|
position.z = zmin / 10
|
|
end
|
|
|
|
-- poison arrow
|
|
if self._poison_arrow then
|
|
local old_damage_texture_modifier = pointed_thing.ref:get_properties().damage_texture_modifier
|
|
local punch_def = {}
|
|
punch_def.puncher = self.object
|
|
punch_def.time_from_last_punch = self._tflp
|
|
punch_def.tool_capabilities = {
|
|
full_punch_interval = self._tool_capabilities.full_punch_interval,
|
|
damage_groups = {fleshy = _damage, knockback = knockback}
|
|
}
|
|
|
|
if pointed_thing.ref:is_player() then
|
|
-- @TODO missing `active` posion arrow check for player (see lua_ent below)
|
|
if x_bows.hbhunger then
|
|
-- Set poison bar
|
|
hb.change_hudbar(
|
|
pointed_thing.ref,
|
|
'health',
|
|
nil,
|
|
nil,
|
|
'hbhunger_icon_health_poison.png',
|
|
nil,
|
|
'hbhunger_bar_health_poison.png'
|
|
)
|
|
end
|
|
|
|
x_bows.poison_effect(1, 5, 0, self, pointed_thing.ref, old_damage_texture_modifier, punch_def)
|
|
else
|
|
-- local lua_ent = pointed_thing.ref:get_luaentity()
|
|
-- if not lua_ent[self.arrow .. '_active'] or lua_ent[self.arrow .. '_active'] == 'false' then
|
|
-- lua_ent[self.arrow .. '_active'] = true
|
|
x_bows.poison_effect(1, 5, 0, self, pointed_thing.ref, old_damage_texture_modifier, punch_def)
|
|
-- end
|
|
end
|
|
end
|
|
|
|
if not x_bows.settings.x_bows_attach_arrows_to_entities and not pointed_thing.ref:is_player() then
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
-- attach arrow
|
|
self.object:set_attach(
|
|
pointed_thing.ref,
|
|
'',
|
|
position,
|
|
rotation,
|
|
true
|
|
)
|
|
self._attached = true
|
|
self._attached_to.type = pointed_thing.type
|
|
self._attached_to.pos = position
|
|
|
|
-- remove last arrow when too many already attached
|
|
local children = {}
|
|
|
|
for _, object in ipairs(pointed_thing.ref:get_children()) do
|
|
if object:get_luaentity() and object:get_luaentity().name == 'x_bows:arrow_entity' then
|
|
table.insert(children, object)
|
|
end
|
|
end
|
|
|
|
if #children >= 5 then
|
|
children[1]:remove()
|
|
end
|
|
|
|
return
|
|
|
|
elseif pointed_thing.type == 'node' and not self._attached then
|
|
local node = minetest.get_node(pointed_thing.under)
|
|
local node_def = minetest.registered_nodes[node.name]
|
|
|
|
if not node_def then
|
|
return
|
|
end
|
|
|
|
self._velocity = self.object:get_velocity()
|
|
|
|
if node_def.drawtype == 'liquid' and not self._is_drowning then
|
|
self._is_drowning = true
|
|
self._in_liquid = true
|
|
local drag = 1 / (node_def.liquid_viscosity * 6)
|
|
self.object:set_velocity(vector.multiply(self._velocity, drag))
|
|
self.object:set_acceleration({x = 0, y = -1.0, z = 0})
|
|
|
|
x_bows.particle_effect(self._old_pos, 'bubble')
|
|
elseif self._is_drowning then
|
|
self._is_drowning = false
|
|
|
|
if self._velocity then
|
|
self.object:set_velocity(self._velocity)
|
|
end
|
|
|
|
self.object:set_acceleration({x = 0, y = -9.81, z = 0})
|
|
end
|
|
|
|
if x_bows.mesecons and node.name == 'x_bows:target' then
|
|
local distance = vector.distance(pointed_thing.under, ip_pos)
|
|
distance = math.floor(distance * 100) / 100
|
|
|
|
-- only close to the center of the target will trigger signal
|
|
if distance < 0.54 then
|
|
mesecon.receptor_on(pointed_thing.under)
|
|
minetest.get_node_timer(pointed_thing.under):start(2)
|
|
end
|
|
end
|
|
|
|
if node_def.walkable then
|
|
self.object:set_velocity({x=0, y=0, z=0})
|
|
self.object:set_acceleration({x=0, y=0, z=0})
|
|
self.object:set_pos(ip_pos)
|
|
self.object:set_rotation(self.object:get_rotation())
|
|
self._attached = true
|
|
self._attached_to.type = pointed_thing.type
|
|
self._attached_to.pos = pointed_thing.under
|
|
self.object:set_properties({collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2}})
|
|
|
|
-- remove last arrow when too many already attached
|
|
local children = {}
|
|
|
|
for _, object in ipairs(minetest.get_objects_inside_radius(pointed_thing.under, 1)) do
|
|
if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == 'x_bows:arrow_entity' then
|
|
table.insert(children, object)
|
|
end
|
|
end
|
|
|
|
if #children >= 5 then
|
|
children[#children]:remove()
|
|
end
|
|
|
|
minetest.sound_play('x_bows_arrow_hit', {
|
|
pos = pointed_thing.under,
|
|
gain = 0.6,
|
|
max_hear_distance = 16
|
|
})
|
|
|
|
return
|
|
end
|
|
end
|
|
pointed_thing = ray:next()
|
|
end
|
|
|
|
self._old_pos = pos
|
|
end,
|
|
|
|
---@param self table
|
|
---@param puncher ObjectRef|nil
|
|
---@param time_from_last_punch number|integer|nil
|
|
---@param tool_capabilities ToolCapabilitiesDef|nil
|
|
---@param dir Vector
|
|
---@param damage number|integer
|
|
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
|
local wood_sound_def = default.node_sound_wood_defaults()
|
|
|
|
minetest.sound_play(wood_sound_def.dig.name, {
|
|
pos = self.object:get_pos(),
|
|
gain = wood_sound_def.dig.gain
|
|
})
|
|
|
|
return false
|
|
end,
|
|
})
|