v 1.0.0
206
README.md
|
@ -1,198 +1,12 @@
|
|||
# RCBOWS
|
||||
# x_bows
|
||||
|
||||
## function rcbows.register_arrow(name, def)
|
||||
Example:
|
||||
```
|
||||
rcbows.register_arrow("farbows:e_arrow", {
|
||||
damage = 5,
|
||||
inventory_arrow = {
|
||||
name = "farbows:inv_arrow",
|
||||
description = S("Arrow"),
|
||||
inventory_image = "farbows_arrow.png",
|
||||
stack_max = 64, --optional, 99 by default
|
||||
}
|
||||
sounds = {
|
||||
max_hear_distance = 10,
|
||||
gain = 0.4,
|
||||
},
|
||||
})
|
||||
```
|
||||
## function rcbows.register_bow(name, def)
|
||||
Example:
|
||||
```
|
||||
rcbows.register_bow("farbows:bow_wood", {
|
||||
description = S("Wooden Bow"),
|
||||
image = "farbows_bow_wood.png",
|
||||
strength = 30,
|
||||
uses = 150,
|
||||
charge_time = 0.5,
|
||||
recipe = {
|
||||
{"", "group:wood", "farming:string"},
|
||||
{"group:wood", "", "farming:string"},
|
||||
{"", "group:wood", "farming:string"},
|
||||
},
|
||||
base_texture = "farbows_base_bow_wood.png",
|
||||
overlay_empty = "farbows_overlay_empty.png",
|
||||
overlay_charged = "farbows_overlay_charged.png",
|
||||
arrows = "farbows:e_arrow",
|
||||
sounds = {
|
||||
max_hear_distance = 10,
|
||||
gain = 0.4,
|
||||
}
|
||||
})
|
||||
```
|
||||
## TODO
|
||||
|
||||
### Arrows
|
||||
|
||||
You can define "arrows" as a single arrow (string) or a table of arrows.
|
||||
|
||||
In this case the order matters. The first ones have preference over the last ones when charging the bow.
|
||||
|
||||
I.e:
|
||||
```
|
||||
arrows = {"farbows:e_arrow", ""farbows:ice_arrow""},
|
||||
```
|
||||
|
||||
### Viewfinder
|
||||
|
||||
You can define a viewfinder for a bow. This produces a zoom effect.
|
||||
|
||||
```
|
||||
viewfinder = {
|
||||
zoom = 15, --level of zoom; by default 15.
|
||||
texture = "" --optional
|
||||
}
|
||||
```
|
||||
|
||||
- When the bow charged, toogle the viewfinder with the secondary use (right-click).
|
||||
- You can define an optional texture to being showed. If you define texture as empty (""), you get the default rcbows viewfinder texture.
|
||||
|
||||
## Audio
|
||||
|
||||
1. If you define ``sounds={}``, you get the default sounds.
|
||||
|
||||
For no sound at all do not declare 'sounds'.
|
||||
|
||||
Also you can set the sound parameters 'max_hear_distance' and 'gain'.
|
||||
|
||||
In example:
|
||||
```
|
||||
sounds = {
|
||||
max_hear_distance = 10,
|
||||
gain = 0.4,
|
||||
}
|
||||
```
|
||||
|
||||
2. You also can define your own soundfiles.
|
||||
|
||||
You can set "soundfile_draw_bow" and/or "soundfile_fire_arrow" for bows, and "soundfile_hit_arrow" for arrows.
|
||||
|
||||
In example for a Bow:
|
||||
```
|
||||
sounds = {
|
||||
soundfile_draw_bow = "my_draw_bow"
|
||||
soundfile_fire_arrow = "my_fire_arrow"
|
||||
max_hear_distance = 5,
|
||||
--set the gain by default (0.5)
|
||||
}
|
||||
```
|
||||
|
||||
In example for a Arrow:
|
||||
```
|
||||
sounds = {
|
||||
soundfile_hit_arrow = "my_hit_arrow"
|
||||
max_hear_distance = 5,
|
||||
--set the gain by default (0.5)
|
||||
}
|
||||
```
|
||||
|
||||
## Drop
|
||||
|
||||
By default the arrow drops the inventory_arrow when reachs a solid node.
|
||||
|
||||
If you want to define another item to drop, define it with 'drop':
|
||||
```
|
||||
rcbows.register_arrow("farbows:e_arrow", {
|
||||
damage = 5,
|
||||
inventory_arrow = {
|
||||
name = "farbows:inv_arrow",
|
||||
description = S("Arrow"),
|
||||
inventory_image = "farbows_arrow.png",
|
||||
}
|
||||
drop = "farbows_drop_arrow"
|
||||
})
|
||||
```
|
||||
|
||||
If you want not any drop at all, add:
|
||||
```
|
||||
drop = "",
|
||||
```
|
||||
|
||||
## Arrow Effects
|
||||
You can define some arrow effects
|
||||
### replace_node
|
||||
Replace the hit node for this one.
|
||||
### trail_particle
|
||||
Particle texture to create an arrow trail.
|
||||
|
||||
It can be a string with "texture" only, or a table for animated textures: {texture = "texture", animation = "animation"}.
|
||||
### explosion
|
||||
It requires "tnt" or "explosion" mods as an optional dependency.
|
||||
|
||||
It is a table in where to define:
|
||||
- mod = "tnt" or "explosions",
|
||||
- radius
|
||||
- damage = It is "damage_radius" for the "tnt" mod or "strength" for "explosions"
|
||||
|
||||
|
||||
In example:
|
||||
```
|
||||
rcbows.register_arrow("farbows:fire_arrow", {
|
||||
damage = 7,
|
||||
inventory_arrow = {
|
||||
name = "farbows:inv_fire_arrow",
|
||||
description = S("Fire Arrow"),
|
||||
inventory_image = "farbows_arrow_fire.png",
|
||||
},
|
||||
drop = "farbows:inv_arrow",
|
||||
effects = {
|
||||
replace_node = "fire:basic_flame",
|
||||
trail_particle = "farbows_particle_fire.png",
|
||||
explosion = {
|
||||
mod = "tnt",
|
||||
radius= 10,
|
||||
damage = 1,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
### water
|
||||
An effect that extinguishes the flames.
|
||||
|
||||
It requires "fire" mod as an optional dependency.
|
||||
|
||||
It is a table in where to define:
|
||||
- flame_node = The name of the flame node to extinguish
|
||||
- radius
|
||||
- particles = A water particles effect [optional]
|
||||
|
||||
```
|
||||
rcbows.register_arrow("farbows:water_arrow", {
|
||||
projectile_texture = "farbows_water_arrow",
|
||||
damage = 2,
|
||||
inventory_arrow = {
|
||||
name = "farbows:inv_water_arrow",
|
||||
description = S("Water Arrow"),
|
||||
inventory_image = "farbows_arrow_water.png",
|
||||
},
|
||||
drop = "bucket:bucket_empty",
|
||||
effects = {
|
||||
trail_particle = "default_water.png",
|
||||
water = {
|
||||
radius = 5,
|
||||
flame_node = "fire:basic_flame",
|
||||
particles = true,
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
- critical hit one shot kill will not remove the arrow
|
||||
- change poison damage texture overlay
|
||||
- add poison particles to target
|
||||
- add player to poison damage
|
||||
- charged bow should not hit and shoot at the same time when pointing on node/object
|
||||
- caused by non charged bow after switching itemstack form charged to not charged bow
|
||||
- add vertical knockback to poison punch?
|
||||
- arrow too large when attached to entitites with visual_scale
|
|
@ -0,0 +1,511 @@
|
|||
-- Gets total armor level from 3d armor
|
||||
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
|
||||
local function limit(x, min, max)
|
||||
return math.min(math.max(x, min), max)
|
||||
end
|
||||
|
||||
-- Gets `ObjectRef` collision box
|
||||
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
|
||||
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',
|
||||
visual_size = {x = 0.2, y = 0.2, z = 0.3},
|
||||
collisionbox = {0, 0, 0, 0, 0, 0},
|
||||
selectionbox = {0, 0, 0, 0, 0, 0},
|
||||
physical = false,
|
||||
textures = {'air'},
|
||||
hp_max = 0.5
|
||||
},
|
||||
|
||||
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,
|
||||
ref = 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
|
||||
|
||||
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,
|
||||
|
||||
on_death = function(self, killer)
|
||||
minetest.item_drop(ItemStack(self.arrow), nil, vector.round(self._old_pos))
|
||||
end,
|
||||
|
||||
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')
|
||||
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 = self._attached_to.ref
|
||||
|
||||
self._nodechecktimer = 0.5
|
||||
|
||||
if node.name == 'air' then
|
||||
local pos = self._attached_to.pos
|
||||
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._attached_to.ref = 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
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
self._attached_to.ref = pointed_thing.ref
|
||||
|
||||
local children = pointed_thing.ref:get_children()
|
||||
|
||||
-- remove last arrow when too many already attached
|
||||
if #children >= 5 then
|
||||
children[1]:remove()
|
||||
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
|
||||
|
||||
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._attached_to.ref = node_def
|
||||
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 k, 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,
|
||||
})
|
726
init.lua
|
@ -1,449 +1,357 @@
|
|||
rcbows = {}
|
||||
local mod_start_time = minetest.get_us_time()
|
||||
local bow_charged_timer = 0
|
||||
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
x_bows = {
|
||||
pvp = minetest.settings:get_bool('enable_pvp') or false,
|
||||
creative = minetest.settings:get_bool('creative_mode') or false,
|
||||
mesecons = minetest.get_modpath('mesecons'),
|
||||
hbhunger = minetest.get_modpath('hbhunger'),
|
||||
registered_arrows = {},
|
||||
registered_bows = {},
|
||||
player_bow_sneak = {}
|
||||
}
|
||||
|
||||
--CONSTANTS
|
||||
local DEFAULT_MAX_HEAR_DISTANCE = 10
|
||||
local DEFAULT_GAIN = 0.5
|
||||
|
||||
function rcbows.spawn_arrow(user, strength, itemstack)
|
||||
local pos = user:get_pos()
|
||||
pos.y = pos.y + 1.5 -- camera offset
|
||||
local dir = user:get_look_dir()
|
||||
local yaw = user:get_look_horizontal()
|
||||
local meta = itemstack:get_meta()
|
||||
local arrow = meta:get_string("rcbows:charged_arrow")
|
||||
local obj = nil
|
||||
if pos and arrow then
|
||||
obj = minetest.add_entity(pos, arrow)
|
||||
end
|
||||
if not obj then
|
||||
return
|
||||
end
|
||||
local lua_ent = obj:get_luaentity()
|
||||
lua_ent.shooter_name = user:get_player_name()
|
||||
obj:set_yaw(yaw - 0.5 * math.pi)
|
||||
local velocity = vector.multiply(dir, strength)
|
||||
obj:set_velocity(velocity)
|
||||
return true
|
||||
function x_bows.is_creative(name)
|
||||
return x_bows.creative or minetest.check_player_privs(name, {creative = true})
|
||||
end
|
||||
|
||||
function rcbows.launch_arrow(user, name, def, itemstack)
|
||||
if not rcbows.spawn_arrow(user, def.strength, itemstack) then --throw arrow (spawn arrow entity)
|
||||
return -- something failed
|
||||
end
|
||||
if def.sounds then
|
||||
local user_pos = user:get_pos()
|
||||
if not def.sounds.soundfile_fire_arrow then
|
||||
def.sounds.soundfile_fire_arrow = "rcbows_fire_arrow"
|
||||
end
|
||||
rcbows.make_sound("pos", user_pos, def.sounds.soundfile_fire_arrow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE)
|
||||
end
|
||||
itemstack:set_name(name)
|
||||
itemstack:set_wear(itemstack:get_wear() + 0x10000 / def.uses)
|
||||
if def.viewfinder then --reset the viewfinder-zoom
|
||||
if not(user:get_fov() == 0) then
|
||||
user:set_fov(0)
|
||||
remove_viewfinder(user, itemstack:get_meta():get_int( "rcbows:viewfinder_id"))
|
||||
end
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local function show_viewfinder(player, texture)
|
||||
local hud_id = player:hud_add({
|
||||
hud_elem_type = "image",
|
||||
text = texture,
|
||||
position = {x=0, y=0},
|
||||
scale = {x=-100, y=-100},
|
||||
alignment = {x=1, y=1},
|
||||
offset = {x=0, y=0}
|
||||
})
|
||||
return hud_id
|
||||
end
|
||||
|
||||
function remove_viewfinder(player, hud_id)
|
||||
if hud_id then
|
||||
player:hud_remove(hud_id)
|
||||
end
|
||||
end
|
||||
|
||||
function rcbows.register_bow(name, def)
|
||||
assert(type(def.description) == "string")
|
||||
assert(type(def.image) == "string")
|
||||
assert(type(def.strength) == "number")
|
||||
assert(def.uses > 0)
|
||||
|
||||
local function reload_bow(itemstack, user, pointed_thing)
|
||||
local inv = user:get_inventory()
|
||||
local arrow, inventory_arrows, inventory_arrow, inv_arrow_name
|
||||
local inv_list = inv:get_list("main")
|
||||
if type(def.arrows) == 'table' then --more than one arrow?
|
||||
for i = 1, #def.arrows do
|
||||
arrow = def.arrows[i]
|
||||
inv_arrow_name = minetest.registered_entities[arrow].inventory_arrow_name
|
||||
if inv:contains_item("main", inv_arrow_name) then
|
||||
if not inventory_arrows then
|
||||
inventory_arrows = {}
|
||||
end
|
||||
inventory_arrows[#inventory_arrows+1] = {arrow = arrow, inv_arrow_name = inv_arrow_name}
|
||||
end
|
||||
end
|
||||
else
|
||||
arrow = def.arrows
|
||||
inventory_arrow = minetest.registered_entities[def.arrows].inventory_arrow_name
|
||||
end
|
||||
if not inventory_arrow and not inventory_arrows then
|
||||
return
|
||||
end
|
||||
if inventory_arrows then --more than one arrow?
|
||||
for i = 1, #inv_list do
|
||||
if inventory_arrow then
|
||||
break
|
||||
end
|
||||
for j = 1, #inventory_arrows do
|
||||
local inv_arrow = inventory_arrows[j]
|
||||
if inv_list[i]:get_name() == inv_arrow.inv_arrow_name then
|
||||
arrow = inv_arrow.arrow
|
||||
inventory_arrow = inv_arrow.inv_arrow_name
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not inventory_arrow then
|
||||
return
|
||||
end
|
||||
end
|
||||
if not inv:remove_item("main", inventory_arrow):is_empty() then
|
||||
minetest.after(def.charge_time or 0, function(v_user, v_name)
|
||||
local wielded_item = v_user:get_wielded_item()
|
||||
local wielded_item_name = wielded_item:get_name()
|
||||
if wielded_item_name == v_name then
|
||||
local meta = wielded_item:get_meta()
|
||||
meta:set_string("rcbows:charged_arrow", arrow) --save the arrow in the meta
|
||||
wielded_item:set_name(v_name .. "_charged")
|
||||
v_user:set_wielded_item(wielded_item)
|
||||
end
|
||||
end, user, name)
|
||||
if def.sounds then
|
||||
local user_pos = user:get_pos()
|
||||
if not def.sounds.soundfile_draw_bow then
|
||||
def.sounds.soundfile_draw_bow = "rcbows_draw_bow"
|
||||
end
|
||||
rcbows.make_sound("pos", user_pos, def.sounds.soundfile_draw_bow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE)
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
function x_bows.register_bow(name, def)
|
||||
if name == nil or name == '' then
|
||||
return false
|
||||
end
|
||||
|
||||
minetest.register_tool(name, {
|
||||
description = def.description .. " ".. S("(place to reload)"),
|
||||
inventory_image = def.image .. "^" .. def.overlay_empty,
|
||||
def.name = 'x_bows:' .. name
|
||||
def.name_charged = 'x_bows:' .. name .. '_charged'
|
||||
def.description = def.description or name
|
||||
def.uses = def.uses or 150
|
||||
|
||||
on_use = function() end,
|
||||
on_place = reload_bow,
|
||||
on_secondary_use = reload_bow,
|
||||
x_bows.registered_bows[def.name_charged] = def
|
||||
|
||||
-- not charged bow
|
||||
minetest.register_tool(def.name, {
|
||||
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. (1 / def.crit_chance) * 100 .. '%'),
|
||||
inventory_image = def.inventory_image or 'x_bows_bow_wood.png',
|
||||
-- on_use = function(itemstack, user, pointed_thing)
|
||||
-- end,
|
||||
on_place = x_bows.load,
|
||||
on_secondary_use = x_bows.load,
|
||||
groups = {bow = 1, flammable = 1},
|
||||
-- range = 0
|
||||
})
|
||||
|
||||
-- charged bow
|
||||
minetest.register_tool(def.name_charged, {
|
||||
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' .. (1 / def.crit_chance) * 100 .. '%'),
|
||||
inventory_image = def.inventory_image_charged or 'x_bows_bow_wood_charged.png',
|
||||
on_use = x_bows.shoot,
|
||||
groups = {bow = 1, flammable = 1, not_in_creative_inventory = 1},
|
||||
})
|
||||
|
||||
-- recipes
|
||||
if def.recipe then
|
||||
minetest.register_craft({
|
||||
output = name,
|
||||
output = def.name,
|
||||
recipe = def.recipe
|
||||
})
|
||||
end
|
||||
|
||||
local charged_name = name .. "_charged"
|
||||
|
||||
minetest.register_tool(charged_name, {
|
||||
description = def.description .. " " .. S("(use to fire)"),
|
||||
inventory_image = def.base_texture .. "^" ..def.overlay_charged,
|
||||
groups = {not_in_creative_inventory=1},
|
||||
|
||||
on_use = function(itemstack, user, pointed_thing)
|
||||
return rcbows.launch_arrow(user, name, def, itemstack)
|
||||
end,
|
||||
|
||||
on_secondary_use = function(itemstack, user, pointed_thing) --viewfinder
|
||||
if not def.viewfinder then
|
||||
return
|
||||
end
|
||||
if user:get_fov() == 0 then
|
||||
user:set_fov(def.viewfinder.zoom or 15)
|
||||
if def.viewfinder.texture then
|
||||
local viewfinder_texture = def.viewfinder.texture
|
||||
if viewfinder_texture == "" then
|
||||
viewfinder_texture = "rcbows_viewfinder.png"
|
||||
end
|
||||
itemstack:get_meta():set_int("rcbows:viewfinder_id", show_viewfinder(user, viewfinder_texture))
|
||||
end
|
||||
else
|
||||
user:set_fov(0)
|
||||
if def.viewfinder.texture then
|
||||
remove_viewfinder(user, itemstack:get_meta():get_int( "rcbows:viewfinder_id"))
|
||||
end
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function rcbows.register_arrow(name, def)
|
||||
minetest.register_entity(name, {
|
||||
hp_max = 4, -- possible to catch the arrow (pro skills)
|
||||
physical = false, -- use Raycast
|
||||
collisionbox = {-0.1, -0.1, -0.1, 0.1, 0.1, 0.1},
|
||||
visual = "wielditem",
|
||||
textures = {def.inventory_arrow.name},
|
||||
visual_size = {x = 0.2, y = 0.15},
|
||||
old_pos = nil,
|
||||
velocity = nil,
|
||||
liquidflag = nil,
|
||||
shooter_name = "",
|
||||
waiting_for_removal = false,
|
||||
inventory_arrow_name = def.inventory_arrow.name,
|
||||
groups = {arrow = 1},
|
||||
function x_bows.register_arrow(name, def)
|
||||
if name == nil or name == '' then
|
||||
return false
|
||||
end
|
||||
|
||||
on_activate = function(self)
|
||||
self.object:set_acceleration({x = 0, y = -9.81, z = 0})
|
||||
end,
|
||||
def.name = 'x_bows:' .. name
|
||||
def.description = def.description or name
|
||||
|
||||
on_step = function(self, dtime)
|
||||
if self.waiting_for_removal then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
local pos = self.object:get_pos()
|
||||
self.old_pos = self.old_pos or pos
|
||||
local velocity = self.object:get_velocity()
|
||||
if def.sounds and not(def.sounds.soundfile_hit_arrow) then
|
||||
def.sounds.soundfile_hit_arrow = "rcbows_hit_arrow"
|
||||
end
|
||||
local cast = minetest.raycast(self.old_pos, pos, true, true)
|
||||
local thing = cast:next()
|
||||
while thing do
|
||||
if thing.type == "object" and thing.ref ~= self.object then
|
||||
if not thing.ref:is_player() or thing.ref:get_player_name() ~= self.shooter_name then
|
||||
thing.ref:punch(self.object, 1.0, {
|
||||
full_punch_interval = 0.5,
|
||||
damage_groups = {fleshy = def.damage or 1}
|
||||
})
|
||||
self.waiting_for_removal = true
|
||||
self.object:remove()
|
||||
if def.sounds then
|
||||
local thing_pos = thing.ref:get_pos()
|
||||
if thing_pos then
|
||||
rcbows.make_sound("pos", thing_pos, def.sounds.soundfile_hit_arrow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE)
|
||||
end
|
||||
end
|
||||
x_bows.registered_arrows[def.name] = def
|
||||
|
||||
-- no effects or not owner, nothing to do.
|
||||
-- some effects should also happen if hitting an other object. like tnt, water etc.
|
||||
if not def.effects or minetest.is_protected(pos, self.shooter_name) then
|
||||
return
|
||||
end
|
||||
|
||||
rcbows.boom_effect(def, pos) -- BOOM
|
||||
rcbows.water_effect(def, pos) -- water - extinguish fires
|
||||
|
||||
return
|
||||
end
|
||||
elseif thing.type == "node" then
|
||||
local node_name = minetest.get_node(thing.under).name
|
||||
local drawtype = minetest.registered_nodes[node_name]["drawtype"]
|
||||
if drawtype == 'liquid' then
|
||||
if not self.liquidflag then
|
||||
self.velocity = velocity
|
||||
self.liquidflag = true
|
||||
local liquidviscosity = minetest.registered_nodes[node_name]["liquid_viscosity"]
|
||||
local drag = 1/(liquidviscosity*6)
|
||||
self.object:set_velocity(vector.multiply(velocity, drag))
|
||||
self.object:set_acceleration({x = 0, y = -1.0, z = 0})
|
||||
rcbows.splash(self.old_pos, "rcbows_bubble.png")
|
||||
end
|
||||
elseif self.liquidflag then
|
||||
self.liquidflag = 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 minetest.registered_items[node_name].walkable then
|
||||
if not(def.drop) then
|
||||
minetest.item_drop(ItemStack(def.inventory_arrow), nil, vector.round(self.old_pos))
|
||||
else
|
||||
if not(def.drop == "") then
|
||||
minetest.item_drop(ItemStack(def.drop), nil, vector.round(self.old_pos))
|
||||
end
|
||||
end
|
||||
self.waiting_for_removal = true
|
||||
self.object:remove()
|
||||
|
||||
if def.sounds then
|
||||
if pos then
|
||||
rcbows.make_sound("pos", pos, def.sounds.soundfile_hit_arrow, DEFAULT_GAIN, DEFAULT_MAX_HEAR_DISTANCE)
|
||||
end
|
||||
end
|
||||
|
||||
-- no effects or not owner, nothing to do.
|
||||
if not def.effects or minetest.is_protected(pos, self.shooter_name) then
|
||||
return
|
||||
end
|
||||
|
||||
--replace node
|
||||
if def.effects.replace_node then
|
||||
minetest.set_node(pos, {name = def.effects.replace_node})
|
||||
end
|
||||
|
||||
rcbows.boom_effect(def, pos) -- BOOM
|
||||
rcbows.water_effect(def, pos) -- water - extinguish fires
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
thing = cast:next()
|
||||
end
|
||||
if def.effects and def.effects.trail_particle then
|
||||
rcbows.trail(self.old_pos, pos, def.effects.trail_particle)
|
||||
end
|
||||
self.old_pos = pos
|
||||
end,
|
||||
minetest.register_craftitem('x_bows:' .. name, {
|
||||
description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Damage: ' .. def.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', 'Charge Time: ' .. def.tool_capabilities.full_punch_interval .. 's'),
|
||||
inventory_image = def.inventory_image,
|
||||
groups = {arrow = 1, flammable = 1}
|
||||
})
|
||||
minetest.register_craftitem(def.inventory_arrow.name, {
|
||||
description = def.inventory_arrow.description,
|
||||
inventory_image = def.inventory_arrow.inventory_image,
|
||||
stack_max = def.stack_max or 99,
|
||||
})
|
||||
end
|
||||
|
||||
--SOUND SYSTEM
|
||||
|
||||
function rcbows.make_sound(dest_type, dest, soundfile, gain, max_hear_distance)
|
||||
if dest_type == "object" then
|
||||
minetest.sound_play(soundfile, {object = dest, gain = gain or DEFAULT_GAIN, max_hear_distance = max_hear_distance or DEFAULT_MAX_HEAR_DISTANCE,})
|
||||
elseif dest_type == "player" then
|
||||
local player_name = dest:get_player_name()
|
||||
minetest.sound_play(soundfile, {to_player = player_name, gain = gain or DEFAULT_GAIN, max_hear_distance = max_hear_distance or DEFAULT_MAX_HEAR_DISTANCE,})
|
||||
elseif dest_type == "pos" then
|
||||
minetest.sound_play(soundfile, {pos = dest, gain = gain or DEFAULT_GAIN, max_hear_distance = max_hear_distance or DEFAULT_MAX_HEAR_DISTANCE,})
|
||||
-- recipes
|
||||
if def.craft then
|
||||
minetest.register_craft({
|
||||
output = def.name ..' ' .. (def.craft_count or 4),
|
||||
recipe = def.craft
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
--ARROW EFFECTS
|
||||
function x_bows.load(itemstack, user, pointed_thing)
|
||||
local time_load = minetest.get_us_time()
|
||||
local inv = user:get_inventory()
|
||||
local inv_list = inv:get_list('main')
|
||||
local bow_name = itemstack:get_name()
|
||||
local bow_def = x_bows.registered_bows[bow_name .. '_charged']
|
||||
local itemstack_arrows = {}
|
||||
|
||||
function rcbows.boom_effect(def, pos)
|
||||
if def.effects.explosion and def.effects.explosion.mod then
|
||||
local mod_name = def.effects.explosion.mod
|
||||
if minetest.get_modpath(mod_name) ~= nil then
|
||||
if mod_name == "tnt" then
|
||||
tnt.boom(pos, {radius = def.effects.explosion.radius, damage_radius = def.effects.explosion.damage})
|
||||
elseif mod_name == "explosions" then
|
||||
explosions.explode(pos, {radius = def.effects.explosion.radius, strength = def.effects.explosion.damage})
|
||||
end
|
||||
if pointed_thing.under then
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
|
||||
if node_def and node_def.on_rightclick then
|
||||
node_def.on_rightclick(pointed_thing.under, node, user, itemstack, pointed_thing)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function rcbows.water_effect(def, pos)
|
||||
if def.effects.water then
|
||||
if def.effects.water.particles then
|
||||
rcbows.splash(pos, "rcbows_water.png")
|
||||
for k, st in ipairs(inv_list) do
|
||||
if not st:is_empty() and x_bows.registered_arrows[st:get_name()] then
|
||||
table.insert(itemstack_arrows, st)
|
||||
end
|
||||
local radius = def.effects.water.radius or 5
|
||||
local flames = minetest.find_nodes_in_area({x=pos.x -radius, y=pos.y -radius, z=pos.z -radius}, {x=pos.x+radius, y=pos.y+radius, z=pos.z+radius}, {def.effects.water.flame_node})
|
||||
if flames and #flames > 0 then
|
||||
for i=1, #flames do
|
||||
minetest.set_node(flames[i], {name="air"})
|
||||
if def.effects.water.particles then
|
||||
rcbows.splash(flames[i], "rcbows_water.png")
|
||||
end
|
||||
|
||||
-- take 1st found arrow in the list
|
||||
local itemstack_arrow = itemstack_arrows[1]
|
||||
|
||||
if itemstack_arrow and bow_def then
|
||||
local _tool_capabilities = x_bows.registered_arrows[itemstack_arrow:get_name()].tool_capabilities
|
||||
|
||||
minetest.after(0, function(v_user, v_bow_name, v_time_load)
|
||||
local wielded_item = v_user:get_wielded_item()
|
||||
local wielded_item_name = wielded_item:get_name()
|
||||
|
||||
if wielded_item_name == v_bow_name then
|
||||
local meta = wielded_item:get_meta()
|
||||
|
||||
meta:set_string('arrow', itemstack_arrow:get_name())
|
||||
meta:set_string('time_load', tostring(v_time_load))
|
||||
wielded_item:set_name(v_bow_name .. '_charged')
|
||||
v_user:set_wielded_item(wielded_item)
|
||||
|
||||
if not x_bows.is_creative(user:get_player_name()) then
|
||||
inv:remove_item('main', itemstack_arrow:get_name())
|
||||
end
|
||||
end
|
||||
end
|
||||
end, user, bow_name, time_load)
|
||||
|
||||
-- sound plays when charge time reaches full punch interval time
|
||||
-- @TODO: find a way to prevent this from playing when not fully charged
|
||||
minetest.after(_tool_capabilities.full_punch_interval, function(v_user, v_bow_name)
|
||||
local wielded_item = v_user:get_wielded_item()
|
||||
local wielded_item_name = wielded_item:get_name()
|
||||
|
||||
if wielded_item_name == v_bow_name .. '_charged' then
|
||||
minetest.sound_play('x_bows_bow_loaded', {
|
||||
to_player = user:get_player_name(),
|
||||
gain = 0.6
|
||||
})
|
||||
end
|
||||
end, user, bow_name)
|
||||
|
||||
minetest.sound_play('x_bows_bow_load', {
|
||||
to_player = user:get_player_name(),
|
||||
gain = 0.6
|
||||
})
|
||||
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
--PARTICLES EFFECTS
|
||||
function x_bows.shoot(itemstack, user, pointed_thing)
|
||||
local time_shoot = minetest.get_us_time();
|
||||
local meta = itemstack:get_meta()
|
||||
local meta_arrow = meta:get_string('arrow')
|
||||
local time_load = tonumber(meta:get_string('time_load'))
|
||||
local tflp = (time_shoot - time_load) / 1000000
|
||||
|
||||
function rcbows.trail(old_pos, pos, trail_particle)
|
||||
local texture, animation
|
||||
if type(trail_particle) == 'table' then
|
||||
texture = trail_particle.texture
|
||||
animation = trail_particle.animation
|
||||
else
|
||||
texture = trail_particle
|
||||
animation = ""
|
||||
if not x_bows.registered_arrows[meta_arrow] then
|
||||
return itemstack
|
||||
end
|
||||
minetest.add_particlespawner({
|
||||
texture = texture,
|
||||
amount = 20,
|
||||
time = 0.2,
|
||||
minpos = old_pos,
|
||||
maxpos = pos,
|
||||
--minvel = {x=1, y=0, z=1},
|
||||
--maxvel = {x=1, y=0, z=1},
|
||||
--minacc = {x=1, y=0, z=1},
|
||||
--maxacc = {x=1, y=0, z=1},
|
||||
minexptime = 0.2,
|
||||
maxexptime = 0.5,
|
||||
minsize = 0.5,
|
||||
maxsize = 1.5,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
glow = 14,
|
||||
animation = animation,
|
||||
})
|
||||
end
|
||||
|
||||
function rcbows.splash(old_pos, splash_particle)
|
||||
minetest.add_particlespawner({
|
||||
amount = 5,
|
||||
time = 1,
|
||||
minpos = old_pos,
|
||||
maxpos = old_pos,
|
||||
minvel = {x=1, y=1, z=0},
|
||||
maxvel = {x=1, y=1, z=0},
|
||||
minacc = {x=1, y=1, z=1},
|
||||
maxacc = {x=1, y=1, z=1},
|
||||
minexptime = 0.2,
|
||||
maxexptime = 0.5,
|
||||
minsize = 1,
|
||||
maxsize = 1,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = splash_particle,
|
||||
playername = "singleplayer"
|
||||
})
|
||||
end
|
||||
local bow_name_charged = itemstack:get_name()
|
||||
local bow_name = x_bows.registered_bows[bow_name_charged].name
|
||||
local uses = x_bows.registered_bows[bow_name_charged].uses
|
||||
local crit_chance = x_bows.registered_bows[bow_name_charged].crit_chance
|
||||
local _tool_capabilities = x_bows.registered_arrows[meta_arrow].tool_capabilities
|
||||
|
||||
rcbows.register_bow("rcbows:bow_wood", {
|
||||
description = S("Wooden Bow"),
|
||||
image = "rcbows_pulling_0.png",
|
||||
strength = 30,
|
||||
uses = 150,
|
||||
charge_time = 0.5,
|
||||
recipe = {
|
||||
{"", "group:wood", "farming:string"},
|
||||
{"group:wood", "", "farming:string"},
|
||||
{"", "group:wood", "farming:string"},
|
||||
},
|
||||
base_texture = "rcbows_pulling_0.png",
|
||||
overlay_empty = "rcbows_standby.png",
|
||||
overlay_charged = "rcbows_pulling_2.png",
|
||||
arrows = "rcbows:arrow",
|
||||
sounds = {
|
||||
max_hear_distance = 10,
|
||||
gain = 0.4,
|
||||
local staticdata = {
|
||||
arrow = meta_arrow,
|
||||
user_name = user:get_player_name(),
|
||||
is_critical_hit = false,
|
||||
_tool_capabilities = _tool_capabilities,
|
||||
_tflp = tflp,
|
||||
}
|
||||
})
|
||||
|
||||
rcbows.register_arrow("rcbows:arrow", {
|
||||
damage = 5,
|
||||
inventory_arrow = {
|
||||
name = "rcbows:inv_arrow",
|
||||
description = S("Arrow"),
|
||||
inventory_image = "rcbows_arrow.png"
|
||||
},
|
||||
sounds = {
|
||||
max_hear_distance = 10,
|
||||
gain = 0.4,
|
||||
},
|
||||
})
|
||||
-- crits, only on full punch interval
|
||||
if crit_chance and crit_chance > 1 and tflp >= _tool_capabilities.full_punch_interval then
|
||||
if math.random(1, crit_chance) == 1 then
|
||||
staticdata.is_critical_hit = true
|
||||
end
|
||||
end
|
||||
|
||||
local sound_name = 'x_bows_bow_shoot'
|
||||
if staticdata.is_critical_hit then
|
||||
sound_name = 'x_bows_bow_shoot_crit'
|
||||
end
|
||||
|
||||
meta:set_string('arrow', '')
|
||||
itemstack:set_name(bow_name)
|
||||
|
||||
local pos = user:get_pos()
|
||||
local dir = user:get_look_dir()
|
||||
local obj = minetest.add_entity({x = pos.x, y = pos.y + 1.5, z = pos.z}, 'x_bows:arrow_entity', minetest.serialize(staticdata))
|
||||
|
||||
if not obj then
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local lua_ent = obj:get_luaentity()
|
||||
local strength_multiplier = tflp
|
||||
|
||||
if strength_multiplier > _tool_capabilities.full_punch_interval then
|
||||
strength_multiplier = 1
|
||||
end
|
||||
|
||||
local strength = 30 * strength_multiplier
|
||||
|
||||
obj:set_velocity(vector.multiply(dir, strength))
|
||||
obj:set_acceleration({x = dir.x * -3, y = -10, z = dir.z * -3})
|
||||
obj:set_yaw(minetest.dir_to_yaw(dir))
|
||||
|
||||
if not x_bows.is_creative(user:get_player_name()) then
|
||||
itemstack:add_wear(65535 / uses)
|
||||
end
|
||||
|
||||
minetest.sound_play(sound_name, {
|
||||
gain = 0.3,
|
||||
pos = user:get_pos(),
|
||||
max_hear_distance = 10
|
||||
})
|
||||
|
||||
return itemstack
|
||||
end
|
||||
|
||||
function x_bows.particle_effect(pos, type)
|
||||
if type == 'arrow' then
|
||||
return minetest.add_particlespawner({
|
||||
amount = 1,
|
||||
time = 0.1,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minexptime = 1,
|
||||
maxexptime = 1,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
texture = 'x_bows_arrow_particle.png',
|
||||
animation = {
|
||||
type = 'vertical_frames',
|
||||
aspect_w = 8,
|
||||
aspect_h = 8,
|
||||
length = 1,
|
||||
},
|
||||
glow = 1
|
||||
})
|
||||
elseif type == 'arrow_crit' then
|
||||
return minetest.add_particlespawner({
|
||||
amount = 3,
|
||||
time = 0.1,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minexptime = 0.5,
|
||||
maxexptime = 0.5,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
texture = 'x_bows_arrow_particle.png^[colorize:#B22222:127',
|
||||
animation = {
|
||||
type = 'vertical_frames',
|
||||
aspect_w = 8,
|
||||
aspect_h = 8,
|
||||
length = 1,
|
||||
},
|
||||
glow = 1
|
||||
})
|
||||
elseif type == 'bubble' then
|
||||
return minetest.add_particlespawner({
|
||||
amount = 1,
|
||||
time = 1,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = {x=1, y=1, z=0},
|
||||
maxvel = {x=1, y=1, z=0},
|
||||
minacc = {x=1, y=1, z=1},
|
||||
maxacc = {x=1, y=1, z=1},
|
||||
minexptime = 0.2,
|
||||
maxexptime = 0.5,
|
||||
minsize = 0.5,
|
||||
maxsize = 1,
|
||||
texture = 'x_bows_bubble.png'
|
||||
})
|
||||
elseif type == 'arrow_tipped' then
|
||||
return minetest.add_particlespawner({
|
||||
amount = 5,
|
||||
time = 1,
|
||||
minpos = vector.subtract(pos, 0.5),
|
||||
maxpos = vector.add(pos, 0.5),
|
||||
minexptime = 0.4,
|
||||
maxexptime = 0.8,
|
||||
minvel = {x=-0.4, y=0.4, z=-0.4},
|
||||
maxvel = {x=0.4, y=0.6, z=0.4},
|
||||
minacc = {x=0.2, y=0.4, z=0.2},
|
||||
maxacc = {x=0.4, y=0.6, z=0.4},
|
||||
minsize = 4,
|
||||
maxsize = 6,
|
||||
texture = 'x_bows_arrow_tipped_particle.png^[colorize:#008000:127',
|
||||
animation = {
|
||||
type = 'vertical_frames',
|
||||
aspect_w = 8,
|
||||
aspect_h = 8,
|
||||
length = 1,
|
||||
},
|
||||
glow = 1
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- sneak, fov adjustments when bow is charged
|
||||
minetest.register_globalstep(function(dtime)
|
||||
bow_charged_timer = bow_charged_timer + dtime
|
||||
|
||||
if bow_charged_timer > 0.5 then
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
local name = player:get_player_name()
|
||||
local stack = player:get_wielded_item()
|
||||
local item = stack:get_name()
|
||||
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
|
||||
if not x_bows.player_bow_sneak[name] then
|
||||
x_bows.player_bow_sneak[name] = {}
|
||||
end
|
||||
|
||||
if item == 'x_bows:bow_wood_charged' and not x_bows.player_bow_sneak[name].sneak then
|
||||
if minetest.get_modpath('playerphysics') then
|
||||
playerphysics.add_physics_factor(player, 'speed', 'x_bows:bow_wood_charged', 0.25)
|
||||
end
|
||||
|
||||
x_bows.player_bow_sneak[name].sneak = true
|
||||
player:set_fov(0.9, true, 0.4)
|
||||
elseif item ~= 'x_bows:bow_wood_charged' and x_bows.player_bow_sneak[name].sneak then
|
||||
if minetest.get_modpath('playerphysics') then
|
||||
playerphysics.remove_physics_factor(player, 'speed', 'x_bows:bow_wood_charged')
|
||||
end
|
||||
|
||||
x_bows.player_bow_sneak[name].sneak = false
|
||||
player:set_fov(1, true, 0.4)
|
||||
end
|
||||
end
|
||||
|
||||
bow_charged_timer = 0
|
||||
end
|
||||
end)
|
||||
|
||||
local path = minetest.get_modpath('x_bows')
|
||||
|
||||
dofile(path .. '/nodes.lua')
|
||||
dofile(path .. '/arrow.lua')
|
||||
dofile(path .. '/items.lua')
|
||||
|
||||
local mod_end_time = (minetest.get_us_time() - mod_start_time) / 1000000
|
||||
|
||||
print('[Mod] x_bows loaded.. ['.. mod_end_time ..'s]')
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
x_bows.register_bow('bow_wood', {
|
||||
description = 'Wooden Bow',
|
||||
uses = 385,
|
||||
-- `crit_chance` 10% chance, 5 is 20% chance
|
||||
-- (1 / crit_chance) * 100 = % chance
|
||||
crit_chance = 10,
|
||||
recipe = {
|
||||
{'', 'default:stick', 'farming:string'},
|
||||
{'default:stick', '', 'farming:string'},
|
||||
{'', 'default:stick', 'farming:string'},
|
||||
}
|
||||
})
|
||||
|
||||
x_bows.register_arrow('arrow_wood', {
|
||||
description = 'Arrow Wood',
|
||||
inventory_image = 'x_bows_arrow_wood.png',
|
||||
craft = {
|
||||
{'', 'default:flint', ''},
|
||||
{'', 'group:stick', ''},
|
||||
{'', 'group:wool', ''}
|
||||
},
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1,
|
||||
max_drop_level = 0,
|
||||
damage_groups = {fleshy=2}
|
||||
}
|
||||
})
|
||||
|
||||
x_bows.register_arrow('arrow_stone', {
|
||||
description = 'Arrow Stone',
|
||||
inventory_image = 'x_bows_arrow_stone.png',
|
||||
craft = {
|
||||
{'', 'default:flint', ''},
|
||||
{'', 'group:stone', ''},
|
||||
{'', 'group:wool', ''}
|
||||
},
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 1.2,
|
||||
max_drop_level = 0,
|
||||
damage_groups = {fleshy=4}
|
||||
}
|
||||
})
|
||||
|
||||
x_bows.register_arrow('arrow_bronze', {
|
||||
description = 'Arrow Bronze',
|
||||
inventory_image = 'x_bows_arrow_bronze.png',
|
||||
craft = {
|
||||
{'', 'default:flint', ''},
|
||||
{'', 'default:bronze_ingot', ''},
|
||||
{'', 'group:wool', ''}
|
||||
},
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.8,
|
||||
max_drop_level = 1,
|
||||
damage_groups = {fleshy=6}
|
||||
}
|
||||
})
|
||||
|
||||
x_bows.register_arrow('arrow_steel', {
|
||||
description = 'Arrow Steel',
|
||||
inventory_image = 'x_bows_arrow_steel.png',
|
||||
craft = {
|
||||
{'', 'default:flint', ''},
|
||||
{'', 'default:steel_ingot', ''},
|
||||
{'', 'group:wool', ''}
|
||||
},
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.7,
|
||||
max_drop_level = 1,
|
||||
damage_groups = {fleshy=6}
|
||||
}
|
||||
})
|
||||
|
||||
x_bows.register_arrow('arrow_mese', {
|
||||
description = 'Arrow Mese',
|
||||
inventory_image = 'x_bows_arrow_mese.png',
|
||||
craft = {
|
||||
{'', 'default:flint', ''},
|
||||
{'', 'default:mese_crystal', ''},
|
||||
{'', 'group:wool', ''}
|
||||
},
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.7,
|
||||
max_drop_level = 1,
|
||||
damage_groups = {fleshy=7}
|
||||
}
|
||||
})
|
||||
|
||||
x_bows.register_arrow('arrow_diamond', {
|
||||
description = 'Arrow Diamond',
|
||||
inventory_image = 'x_bows_arrow_diamond.png',
|
||||
craft = {
|
||||
{'', 'default:flint', ''},
|
||||
{'', 'default:diamond', ''},
|
||||
{'', 'group:wool', ''}
|
||||
},
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.7,
|
||||
max_drop_level = 1,
|
||||
damage_groups = {fleshy=8}
|
||||
}
|
||||
})
|
||||
|
||||
x_bows.register_arrow('arrow_diamond_tipped_poison', {
|
||||
description = 'Arrow Diamond Tipped Poison (0:05)',
|
||||
inventory_image = 'x_bows_arrow_diamond_poison.png',
|
||||
craft = {
|
||||
{'', '', ''},
|
||||
{'', 'farming_addons:poisonouspotato', ''},
|
||||
{'', 'x_bows:arrow_wood', ''}
|
||||
},
|
||||
tool_capabilities = {
|
||||
full_punch_interval = 0.7,
|
||||
max_drop_level = 1,
|
||||
damage_groups = {fleshy=8}
|
||||
},
|
||||
craft_count = 1
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = 'fuel',
|
||||
recipe = 'x_bows:bow_wood',
|
||||
burntime = 3,
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = 'fuel',
|
||||
recipe = 'x_bows:arrow_wood',
|
||||
burntime = 1,
|
||||
})
|
|
@ -1,3 +0,0 @@
|
|||
# textdomain: rcbows
|
||||
(place to reload)=(dcha. ratón para recargar)
|
||||
(use to fire)=(izda. ratón para usar)
|
5
mod.conf
|
@ -1,3 +1,4 @@
|
|||
name = rcbows
|
||||
name = x_bows
|
||||
description = Adds bows to Minetest.
|
||||
depends =
|
||||
optional_depends = tnt, explosions
|
||||
optional_depends = farming, 3d_armor, hbhunger, mesecons, playerphysics
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
minetest.register_node('x_bows:arrow_node', {
|
||||
drawtype = 'nodebox',
|
||||
node_box = {
|
||||
type = 'fixed',
|
||||
fixed = {
|
||||
{-0.1875, 0, -0.5, 0.1875, 0, 0.5},
|
||||
{0, -0.1875, -0.5, 0, 0.1875, 0.5},
|
||||
{-0.5, -0.5, -0.5, 0.5, 0.5, -0.5},
|
||||
}
|
||||
},
|
||||
-- Textures of node; +Y, -Y, +X, -X, +Z, -Z
|
||||
-- Textures of node; top, bottom, right, left, front, back
|
||||
tiles = {
|
||||
'x_bows_arrow_tile_point_top.png',
|
||||
'x_bows_arrow_tile_point_bottom.png',
|
||||
'x_bows_arrow_tile_point_right.png',
|
||||
'x_bows_arrow_tile_point_left.png',
|
||||
'x_bows_arrow_tile_tail.png',
|
||||
'x_bows_arrow_tile_tail.png'
|
||||
},
|
||||
groups = {not_in_creative_inventory=1},
|
||||
sunlight_propagates = true,
|
||||
paramtype = 'light',
|
||||
collision_box = {0, 0, 0, 0, 0, 0},
|
||||
selection_box = {0, 0, 0, 0, 0, 0}
|
||||
})
|
||||
|
||||
minetest.register_node('x_bows:target', {
|
||||
description = 'Straw',
|
||||
tiles = {'x_bows_target.png'},
|
||||
is_ground_content = false,
|
||||
groups = {snappy=3, flammable=4, fall_damage_add_percent=-30},
|
||||
sounds = default.node_sound_leaves_defaults(),
|
||||
mesecons = {receptor = {state = 'off'}},
|
||||
on_timer = function (pos, elapsed)
|
||||
mesecon.receptor_off(pos)
|
||||
return false
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = 'fuel',
|
||||
recipe = 'x_bows:target',
|
||||
burntime = 3,
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = 'x_bows:target',
|
||||
recipe = {
|
||||
{'', 'default:mese_crystal', ''},
|
||||
{'default:mese_crystal', 'farming:straw', 'default:mese_crystal'},
|
||||
{'', 'default:mese_crystal', ''},
|
||||
}
|
||||
})
|
Before Width: | Height: | Size: 344 B |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 333 B |
Before Width: | Height: | Size: 341 B |
Before Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 666 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 138 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 200 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 205 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 165 B |
After Width: | Height: | Size: 161 B |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 135 B |
After Width: | Height: | Size: 194 B |
After Width: | Height: | Size: 182 B |
After Width: | Height: | Size: 179 B |
After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 118 B |
After Width: | Height: | Size: 146 B |
After Width: | Height: | Size: 264 B |